taskwarrior-tui-0.27.0/.cargo_vcs_info.json0000644000000001361046102023000143130ustar { "git": { "sha1": "aeb778fae3c7b1a5c29d0f9b4cc2febfe09797d6" }, "path_in_vcs": "" }taskwarrior-tui-0.27.0/.config/mise/tasks/taskwarrior-tui/build-taskwarrior000075500000000000000000000177551046102023000252470ustar 00000000000000#!/usr/bin/env -S uv run --script # [MISE] description="Clone/update upstream Taskwarrior and build the task binary from source" # [USAGE] about "Clone/update upstream Taskwarrior and build the task binary from source" # [USAGE] flag "--source " help="Taskwarrior tag or ref to build" # [USAGE] flag "--repo-url " help="Taskwarrior repository URL" # [USAGE] flag "--branch " help="Build a moving branch instead of the pinned source" { # [USAGE] choices "stable" "develop" # [USAGE] } # [USAGE] flag "--source-dir " help="Path to the Taskwarrior source checkout" # [USAGE] flag "--build-dir " help="Path to the CMake build directory" # [USAGE] flag "--install-dir " help="Path to the installation directory" # [USAGE] flag "--build-type " help="CMake build type" # /// script # requires-python = ">=3.11" # dependencies = [] # /// from __future__ import annotations import argparse import os import shlex import shutil import subprocess import sys from pathlib import Path BRANCH_CHOICES = ("stable", "develop") def fail(message: str) -> None: print(message, file=sys.stderr) raise SystemExit(1) def ensure_command(name: str) -> None: if shutil.which(name) is None: fail(f"Required command not found on PATH: {name}") def format_command(args: list[str]) -> str: return " ".join(shlex.quote(arg) for arg in args) def run(args: list[str], *, cwd: Path | None = None) -> None: print(f"+ {format_command(args)}") subprocess.run(args, cwd=cwd, check=True) def capture(args: list[str], *, cwd: Path | None = None) -> str: completed = subprocess.run( args, cwd=cwd, check=True, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) return completed.stdout.strip() def env_or(value: str | None, env_var: str) -> str | None: return value if value is not None else os.getenv(env_var) def env_or_fail(value: str | None, env_var: str, option: str) -> str: resolved = env_or(value, env_var) if resolved: return resolved fail(f"Missing required configuration: set {env_var} or pass {option}.") def resolve_path(value: str) -> Path: return Path(value).expanduser() def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser( description="Clone/update upstream Taskwarrior and build the task binary from source." ) source_group = parser.add_mutually_exclusive_group() source_group.add_argument("--source", help="Taskwarrior tag or ref to build.") parser.add_argument("--repo-url") source_group.add_argument( "--branch", choices=BRANCH_CHOICES, help="Build a moving branch for testing instead of the pinned source configured for this repo.", ) parser.add_argument("--source-dir") parser.add_argument("--build-dir") parser.add_argument("--install-dir") parser.add_argument("--build-type") return parser.parse_args() def parse_usage_args() -> argparse.Namespace | None: fields = ( "source", "repo_url", "branch", "source_dir", "build_dir", "install_dir", "build_type", ) values = {field: os.getenv(f"usage_{field}") for field in fields} if not any(value is not None for value in values.values()): return None if values["source"] and values["branch"]: fail("Only one of --source or --branch may be provided.") return argparse.Namespace(**values) def resolve_source(args: argparse.Namespace) -> str: if args.branch is not None: return args.branch if args.source is not None: return args.source source = os.getenv("TASKWARRIOR_SOURCE") if source: return source legacy_branch = os.getenv("TASKWARRIOR_BRANCH") if legacy_branch: return legacy_branch fail( "Missing required configuration: set TASKWARRIOR_SOURCE or pass --source/--branch." ) def derive_source_scoped_dir(env_var: str, fallback_prefix: str, source: str) -> str: env_value = os.getenv(env_var) if env_value: return os.fspath(resolve_path(env_value).parent / source) return f"{fallback_prefix}/{source}" def main() -> None: args = parse_usage_args() or parse_args() repo_url = env_or_fail(args.repo_url, "TASKWARRIOR_REPO_URL", "--repo-url") source = resolve_source(args) source_dir = resolve_path( env_or_fail(args.source_dir, "TASKWARRIOR_SOURCE_DIR", "--source-dir") ) if args.build_dir is not None: build_dir_value = args.build_dir elif args.branch is not None or args.source is not None: build_dir_value = derive_source_scoped_dir( "TASKWARRIOR_BUILD_DIR", "target/taskwarrior-build", source ) else: build_dir_value = env_or_fail(None, "TASKWARRIOR_BUILD_DIR", "--build-dir") if args.install_dir is not None: install_dir_value = args.install_dir elif args.branch is not None or args.source is not None: install_dir_value = derive_source_scoped_dir( "TASKWARRIOR_INSTALL_DIR", "target/taskwarrior-install", source ) else: install_dir_value = env_or_fail( None, "TASKWARRIOR_INSTALL_DIR", "--install-dir" ) build_dir = resolve_path(build_dir_value) install_dir = resolve_path(install_dir_value) if args.build_type is not None: build_type = args.build_type else: build_type = env_or(None, "TASKWARRIOR_BUILD_TYPE") or ( "Debug" if source == "develop" else "Release" ) ensure_command("git") ensure_command("cmake") source_dir.parent.mkdir(parents=True, exist_ok=True) if not source_dir.exists(): run(["git", "clone", "--recurse-submodules", repo_url, os.fspath(source_dir)]) elif not (source_dir / ".git").is_dir(): fail(f"TASKWARRIOR_SOURCE_DIR={source_dir} exists but is not a git checkout.") if capture(["git", "status", "--porcelain"], cwd=source_dir): fail(f"Refusing to update {source_dir} because it has local changes.") run(["git", "fetch", "--prune", "--tags", "origin"], cwd=source_dir) if source in BRANCH_CHOICES: remote_branch_ref = f"refs/remotes/origin/{source}" has_remote_branch = ( subprocess.run( ["git", "show-ref", "--verify", "--quiet", remote_branch_ref], cwd=source_dir, check=False, ).returncode == 0 ) if not has_remote_branch: fail(f"Remote branch not found: origin/{source}") checkout_ref = f"origin/{source}" resolved_source = source else: tag_ref = f"refs/tags/{source}" has_tag = ( subprocess.run( ["git", "show-ref", "--verify", "--quiet", tag_ref], cwd=source_dir, check=False, ).returncode == 0 ) if not has_tag: fail(f"Tag not found: {source}") checkout_ref = source resolved_source = source run(["git", "switch", "--detach", checkout_ref], cwd=source_dir) run(["git", "submodule", "sync", "--recursive"], cwd=source_dir) run(["git", "submodule", "update", "--init", "--recursive"], cwd=source_dir) run( [ "cmake", "-S", os.fspath(source_dir), "-B", os.fspath(build_dir), f"-DCMAKE_BUILD_TYPE={build_type}", f"-DCMAKE_INSTALL_PREFIX={install_dir}", ] ) run(["cmake", "--build", os.fspath(build_dir), "--parallel"]) run(["cmake", "--install", os.fspath(build_dir)]) task_bin = install_dir / "bin" / ("task.exe" if os.name == "nt" else "task") if not task_bin.is_file(): fail(f"Expected built task binary at {task_bin}") commit = capture(["git", "rev-parse", "--short", "HEAD"], cwd=source_dir) print(f"Built Taskwarrior from {resolved_source} ({commit}): {task_bin}") run([os.fspath(task_bin), "--version"]) if __name__ == "__main__": main() taskwarrior-tui-0.27.0/.config/mise/tasks/taskwarrior-tui/clean-demo-taskdata000075500000000000000000000004171046102023000253630ustar 00000000000000#!/usr/bin/env bash #MISE description="Remove local demo fixture data used by VHS tapes" #USAGE about "Remove the generated Taskwarrior demo data used by VHS tapes" set -euo pipefail rm -rf "${TASKDATA}" "${TASKRC}" "${TASKWARRIOR_TUI_DATA}" "${TASKWARRIOR_TUI_CONFIG}" taskwarrior-tui-0.27.0/.config/mise/tasks/taskwarrior-tui/ensure-rust-toolchain000075500000000000000000000032011046102023000260310ustar 00000000000000#!/usr/bin/env -S uv run --script # [MISE] description="Install Rust toolchain components and optional targets via rustup" # [USAGE] about "Install Rust toolchain components and optional targets via rustup" # /// script # requires-python = ">=3.11" # /// from __future__ import annotations import os import shlex import subprocess DEFAULT_PROFILE = "default" DEFAULT_COMPONENTS = ("clippy", "rustfmt", "llvm-tools") def split_csv(value: str) -> list[str]: return [item.strip() for item in value.split(",") if item.strip()] def run(cmd: list[str]) -> None: print("+", " ".join(shlex.quote(part) for part in cmd), flush=True) subprocess.run(cmd, check=True) def resolve_toolchain() -> str: value = os.environ.get("RUSTUP_TOOLCHAIN", "").strip() if value: return value result = subprocess.run( ["rustup", "show", "active-toolchain"], check=True, capture_output=True, text=True, ) toolchain = result.stdout.strip().split()[0] if toolchain: return toolchain raise RuntimeError("unable to determine active Rust toolchain") def main() -> int: toolchain = resolve_toolchain() profile = DEFAULT_PROFILE components = list(DEFAULT_COMPONENTS) targets = split_csv(os.environ.get("RUST_TOOLCHAIN_TARGETS", "")) run(["rustup", "toolchain", "install", toolchain, "--profile", profile]) if components: run(["rustup", "component", "add", f"--toolchain={toolchain}", *components]) if targets: run(["rustup", "target", "add", f"--toolchain={toolchain}", *targets]) return 0 if __name__ == "__main__": raise SystemExit(main()) taskwarrior-tui-0.27.0/.config/mise/tasks/taskwarrior-tui/run-taskdata000075500000000000000000000016401046102023000241620ustar 00000000000000#!/usr/bin/env bash #MISE description="Import local fixture data, run the TUI, and clean tests/data/.task on exit" #USAGE about "Import local fixture data, run the TUI, and clean tests/data/.task on exit" #USAGE flag "--release" help="Run the TUI with cargo's release profile" #MISE depends=["taskwarrior-tui:import-taskdata"] #MISE depends_post=["taskwarrior-tui:clean-taskdata"] #MISE interactive=true set -euo pipefail if [ -n "${usage_release:-}" ]; then set -- if [ "$usage_release" = "true" ]; then set -- --release fi else if [ "$#" -gt 1 ]; then echo "usage: run-taskdata [--release]" >&2 exit 2 fi if [ "$#" -eq 1 ] && [ "$1" != "--release" ]; then echo "usage: run-taskdata [--release]" >&2 exit 2 fi fi cleanup() { local status=$? set +e mise run taskwarrior-tui:clean-taskdata exit "$status" } trap cleanup EXIT cargo run "$@" taskwarrior-tui-0.27.0/.config/mise/tasks/taskwarrior-tui/setup-demo-taskdata000075500000000000000000000074261046102023000254500ustar 00000000000000#!/usr/bin/env bash #MISE description="Prepare local demo fixture data used by VHS tapes" #USAGE about "Write demo Taskwarrior config and import the local demo fixture data used by VHS tapes" set -euo pipefail taskrc="${TASKRC:?TASKRC is required; run via mise or export it first.}" taskdata="${TASKDATA:?TASKDATA is required; run via mise or export it first.}" tui_data="${TASKWARRIOR_TUI_DATA:?TASKWARRIOR_TUI_DATA is required; run via mise or export it first.}" tui_config="${TASKWARRIOR_TUI_CONFIG:?TASKWARRIOR_TUI_CONFIG is required; run via mise or export it first.}" if [ -n "${TASKWARRIOR_TUI_TASKWARRIOR_CLI:-}" ]; then task_cli="${TASKWARRIOR_TUI_TASKWARRIOR_CLI}" else install_dir="${TASKWARRIOR_INSTALL_DIR:?TASKWARRIOR_INSTALL_DIR is required; run via mise or export it first.}" task_cli="${install_dir}/bin/task" fi if [ ! -x "${task_cli}" ]; then printf "Taskwarrior CLI not found: %s\n" "${task_cli}" >&2 exit 1 fi rm -rf "${taskdata}" "${taskrc}" "${tui_data}" "${tui_config}" mkdir -p "$(dirname "${taskrc}")" "${tui_data}" "${tui_config}" cat > "${taskrc}" </dev/null "${task_cli}" add rc.hooks=0 "Review quarterly budget" project:demo +finance >/dev/null "${task_cli}" context none >/dev/null 2>&1 || true taskwarrior-tui-0.27.0/.config/mise/tasks/taskwarrior-tui/setup-testdata000075500000000000000000000015511046102023000245340ustar 00000000000000#!/usr/bin/env bash #MISE description="Fetch pinned test fixtures used by cargo test" #USAGE about "Fetch pinned test fixtures used by cargo test" #MISE depends=["taskwarrior-tui:clean-tests"] set -euo pipefail repo_url="${TASKWARRIOR_TESTDATA_REPO_URL:?TASKWARRIOR_TESTDATA_REPO_URL is required; run via mise or export it first.}" ref="${TASKWARRIOR_TESTDATA_REF:?TASKWARRIOR_TESTDATA_REF is required; run via mise or export it first.}" testdata_dir="${TASKWARRIOR_TESTDATA_DIR:?TASKWARRIOR_TESTDATA_DIR is required; run via mise or export it first.}" rm -rf "$testdata_dir" mkdir -p "$(dirname "$testdata_dir")" git init -q "$testdata_dir" git -C "$testdata_dir" remote add origin "$repo_url" git -C "$testdata_dir" fetch --depth 1 origin "$ref" git -C "$testdata_dir" checkout --detach FETCH_HEAD echo "Prepared taskwarrior-testdata at $testdata_dir pinned to $ref" taskwarrior-tui-0.27.0/.config/mise/tasks/taskwarrior-tui/vhs000075500000000000000000000021161046102023000223630ustar 00000000000000#!/usr/bin/env bash #MISE description="Build taskwarrior-tui and render a VHS tape against local demo data" #USAGE about "Build taskwarrior-tui, prepare local demo data, and render a VHS tape" #MISE depends=["taskwarrior-tui:setup-demo-taskdata"] #MISE depends_post=["taskwarrior-tui:clean-demo-taskdata"] set -euo pipefail usage() { echo "usage: vhs PATH/TO/TAPE" >&2 } if [ "$#" -ne 1 ]; then usage exit 2 fi tape="$1" if [ ! -f "${tape}" ]; then echo "Tape not found: ${tape}" >&2 exit 1 fi if ! command -v vhs >/dev/null 2>&1; then printf "vhs is required on PATH to render %s.\n" "${tape}" >&2 exit 1 fi if [ -n "${TASKWARRIOR_TUI_TASKWARRIOR_CLI:-}" ]; then task_cli="${TASKWARRIOR_TUI_TASKWARRIOR_CLI}" else install_dir="${TASKWARRIOR_INSTALL_DIR:?TASKWARRIOR_INSTALL_DIR is required; run via mise or export it first.}" task_cli="${install_dir}/bin/task" fi if [ ! -x "${task_cli}" ]; then printf "Taskwarrior CLI not found: %s\n" "${task_cli}" >&2 exit 1 fi export TASKWARRIOR_TUI_TASKWARRIOR_CLI="${task_cli}" cargo build vhs "${tape}" taskwarrior-tui-0.27.0/.config/mise.lock000064400000000000000000000166231046102023000162230ustar 00000000000000# @generated - this file is auto-generated by `mise lock` https://mise.jdx.dev/dev-tools/mise-lock.html [[tools."aqua:Kitware/CMake"]] version = "4.3.0" backend = "aqua:Kitware/CMake" [tools."aqua:Kitware/CMake"."platforms.linux-arm64"] checksum = "sha256:26fe3011f497eb9398115dcabcc094685e634b1841f7c01dc01c5a89b8b0ea0d" url = "https://github.com/Kitware/CMake/releases/download/v4.3.0/cmake-4.3.0-linux-aarch64.tar.gz" [tools."aqua:Kitware/CMake"."platforms.linux-arm64-musl"] checksum = "sha256:26fe3011f497eb9398115dcabcc094685e634b1841f7c01dc01c5a89b8b0ea0d" url = "https://github.com/Kitware/CMake/releases/download/v4.3.0/cmake-4.3.0-linux-aarch64.tar.gz" [tools."aqua:Kitware/CMake"."platforms.linux-x64"] checksum = "sha256:201bdabe17a54e017f119cffa247648e9c44327e52473c2cc60a88fded94652a" url = "https://github.com/Kitware/CMake/releases/download/v4.3.0/cmake-4.3.0-linux-x86_64.tar.gz" [tools."aqua:Kitware/CMake"."platforms.linux-x64-musl"] checksum = "sha256:201bdabe17a54e017f119cffa247648e9c44327e52473c2cc60a88fded94652a" url = "https://github.com/Kitware/CMake/releases/download/v4.3.0/cmake-4.3.0-linux-x86_64.tar.gz" [tools."aqua:Kitware/CMake"."platforms.macos-arm64"] checksum = "sha256:5bd933daf6e9234a53a9a43092746993870d9f162b6c399fd6e4a05cdd475e67" url = "https://github.com/Kitware/CMake/releases/download/v4.3.0/cmake-4.3.0-macos-universal.tar.gz" [tools."aqua:Kitware/CMake"."platforms.macos-x64"] checksum = "sha256:5bd933daf6e9234a53a9a43092746993870d9f162b6c399fd6e4a05cdd475e67" url = "https://github.com/Kitware/CMake/releases/download/v4.3.0/cmake-4.3.0-macos-universal.tar.gz" [tools."aqua:Kitware/CMake"."platforms.windows-x64"] checksum = "sha256:0f664f44eeb5b35b77f83e0984459c0dfa70539bd215c54ae1059604fd4f77ea" url = "https://github.com/Kitware/CMake/releases/download/v4.3.0/cmake-4.3.0-windows-x86_64.zip" [[tools."cargo:cargo-aur"]] version = "1.7.1" backend = "cargo:cargo-aur" [[tools."cargo:cargo-deb"]] version = "3.6.3" backend = "cargo:cargo-deb" [[tools."cargo:cargo-rpm"]] version = "0.8.0" backend = "cargo:cargo-rpm" [[tools."cargo:cross"]] version = "0.2.5" backend = "cargo:cross" [[tools."cargo:grcov"]] version = "0.10.7" backend = "cargo:grcov" [[tools."github:jgm/pandoc"]] version = "3.9.0.2" backend = "github:jgm/pandoc" [tools."github:jgm/pandoc"."platforms.linux-arm64"] checksum = "sha256:b6d21e8f9c3b15744f5a7ab40248019157ed7793875dbe0383d4c82ff572b528" url = "https://github.com/jgm/pandoc/releases/download/3.9.0.2/pandoc-3.9.0.2-linux-arm64.tar.gz" url_api = "https://api.github.com/repos/jgm/pandoc/releases/assets/377165670" [tools."github:jgm/pandoc"."platforms.linux-arm64-musl"] checksum = "sha256:b6d21e8f9c3b15744f5a7ab40248019157ed7793875dbe0383d4c82ff572b528" url = "https://github.com/jgm/pandoc/releases/download/3.9.0.2/pandoc-3.9.0.2-linux-arm64.tar.gz" url_api = "https://api.github.com/repos/jgm/pandoc/releases/assets/377165670" [tools."github:jgm/pandoc"."platforms.linux-x64"] checksum = "sha256:a69abfababda8a56969a254b09f9553a7be89ddec00d4e0fe9fd585d71a67508" url = "https://github.com/jgm/pandoc/releases/download/3.9.0.2/pandoc-3.9.0.2-linux-amd64.tar.gz" url_api = "https://api.github.com/repos/jgm/pandoc/releases/assets/377165366" [tools."github:jgm/pandoc"."platforms.linux-x64-musl"] checksum = "sha256:a69abfababda8a56969a254b09f9553a7be89ddec00d4e0fe9fd585d71a67508" url = "https://github.com/jgm/pandoc/releases/download/3.9.0.2/pandoc-3.9.0.2-linux-amd64.tar.gz" url_api = "https://api.github.com/repos/jgm/pandoc/releases/assets/377165366" [tools."github:jgm/pandoc"."platforms.macos-arm64"] checksum = "sha256:6e9eca844076bcbb599bbeebbba78a70f93b5307782b85c2c272872812c88875" url = "https://github.com/jgm/pandoc/releases/download/3.9.0.2/pandoc-3.9.0.2-arm64-macOS.zip" url_api = "https://api.github.com/repos/jgm/pandoc/releases/assets/377165034" [tools."github:jgm/pandoc"."platforms.macos-x64"] checksum = "sha256:b9fbceabccbc8f34ac021a50483fc32f8160568d0b4b2c22d81bb29e3054fd82" url = "https://github.com/jgm/pandoc/releases/download/3.9.0.2/pandoc-3.9.0.2-x86_64-macOS.zip" url_api = "https://api.github.com/repos/jgm/pandoc/releases/assets/377167038" [tools."github:jgm/pandoc"."platforms.windows-x64"] checksum = "sha256:c97542f2800f446e788d9f74237856d995421ad1bb3cc8324286840c5f272d3a" url = "https://github.com/jgm/pandoc/releases/download/3.9.0.2/pandoc-3.9.0.2-windows-x86_64.zip" url_api = "https://api.github.com/repos/jgm/pandoc/releases/assets/377166311" [[tools.node]] version = "25.8.2" backend = "core:node" [tools.node."platforms.linux-arm64"] checksum = "sha256:2f823ecd4f9331d6492fedbe50c5a610be3084521f3a5af146875e00a52f2e63" url = "https://nodejs.org/dist/v25.8.2/node-v25.8.2-linux-arm64.tar.gz" [tools.node."platforms.linux-arm64-musl"] checksum = "sha256:2f823ecd4f9331d6492fedbe50c5a610be3084521f3a5af146875e00a52f2e63" url = "https://nodejs.org/dist/v25.8.2/node-v25.8.2-linux-arm64.tar.gz" [tools.node."platforms.linux-x64"] checksum = "sha256:e06c7069012d40914c57b31157c69d4ce83ea1fe9d63bbb7d26e0509a4535d21" url = "https://nodejs.org/dist/v25.8.2/node-v25.8.2-linux-x64.tar.gz" [tools.node."platforms.linux-x64-musl"] checksum = "sha256:e06c7069012d40914c57b31157c69d4ce83ea1fe9d63bbb7d26e0509a4535d21" url = "https://nodejs.org/dist/v25.8.2/node-v25.8.2-linux-x64.tar.gz" [tools.node."platforms.macos-arm64"] checksum = "sha256:fb8dabfda3232ef90d992e6439824fc3237356c04d182f3fa883bebeef31e871" url = "https://nodejs.org/dist/v25.8.2/node-v25.8.2-darwin-arm64.tar.gz" [tools.node."platforms.macos-x64"] checksum = "sha256:530ffb419789f843215375a65b8fcf4cf010735e99276f512a241a31ba8e5e13" url = "https://nodejs.org/dist/v25.8.2/node-v25.8.2-darwin-x64.tar.gz" [tools.node."platforms.windows-x64"] checksum = "sha256:51815d5b0256b947d27d614de04060fcfdbdb830d2c86e63e6f33dbf7964cca7" url = "https://nodejs.org/dist/v25.8.2/node-v25.8.2-win-x64.zip" [[tools.rust]] version = "1.94.0" backend = "core:rust" [[tools.uv]] version = "0.11.1" backend = "aqua:astral-sh/uv" [tools.uv."platforms.linux-arm64"] checksum = "sha256:bd04ffce77ee8d77f39823c13606183581847c2f5dcd704f2ea0f15e376b1a27" url = "https://github.com/astral-sh/uv/releases/download/0.11.1/uv-aarch64-unknown-linux-musl.tar.gz" [tools.uv."platforms.linux-arm64-musl"] checksum = "sha256:bd04ffce77ee8d77f39823c13606183581847c2f5dcd704f2ea0f15e376b1a27" url = "https://github.com/astral-sh/uv/releases/download/0.11.1/uv-aarch64-unknown-linux-musl.tar.gz" [tools.uv."platforms.linux-x64"] checksum = "sha256:4e949471a95b37088a1ff1a585f69abed4d3cd3f921f50709a46b6ba62986d38" url = "https://github.com/astral-sh/uv/releases/download/0.11.1/uv-x86_64-unknown-linux-musl.tar.gz" [tools.uv."platforms.linux-x64-musl"] checksum = "sha256:4e949471a95b37088a1ff1a585f69abed4d3cd3f921f50709a46b6ba62986d38" url = "https://github.com/astral-sh/uv/releases/download/0.11.1/uv-x86_64-unknown-linux-musl.tar.gz" [tools.uv."platforms.macos-arm64"] checksum = "sha256:f7815f739ed5d0e4202e6292acedb8659b9ae7de663d07188d8c6cbd7f96303f" url = "https://github.com/astral-sh/uv/releases/download/0.11.1/uv-aarch64-apple-darwin.tar.gz" [tools.uv."platforms.macos-x64"] checksum = "sha256:2103670e8e949605e51926c7b953923ff6f6befbfb55aee928f5e760c9c910f8" url = "https://github.com/astral-sh/uv/releases/download/0.11.1/uv-x86_64-apple-darwin.tar.gz" [tools.uv."platforms.windows-x64"] checksum = "sha256:6659250cebbd3bb6ee48bcb21a3f0c6656450d63fb97f0f069bcb532bdb688ed" url = "https://github.com/astral-sh/uv/releases/download/0.11.1/uv-x86_64-pc-windows-msvc.zip" taskwarrior-tui-0.27.0/.config/mise.toml000064400000000000000000000077301046102023000162450ustar 00000000000000[settings] env_shell_expand = true status.show_env = true [env] RUST_TEST_THREADS = "1" TASKWARRIOR_REPO_URL = "https://github.com/GothenburgBitFactory/taskwarrior.git" TASKWARRIOR_SOURCE = "v3.3.0" TASKWARRIOR_SOURCE_DIR = "{{config_root}}/tmp/taskwarrior-src" TASKWARRIOR_BUILD_DIR = "{{config_root}}/tmp/taskwarrior-build/{{env.TASKWARRIOR_SOURCE}}" TASKWARRIOR_INSTALL_DIR = "{{config_root}}/tmp/taskwarrior-install/{{env.TASKWARRIOR_SOURCE}}" TASKWARRIOR_TESTDATA_REPO_URL = "https://github.com/kdheepak/taskwarrior-testdata" TASKWARRIOR_TESTDATA_REF = "cdcdb20de1e9aefb2e2b50de7bbd27388b41f80e" TASKWARRIOR_TESTDATA_DIR = "{{config_root}}/tests/data" TASKRC = "{{config_root}}/tests/data/.taskrc" TASKDATA = "{{config_root}}/tests/data/.task" TASKWARRIOR_TUI_CONFIG = "{{config_root}}/tests/data/.config" TASKWARRIOR_TUI_DATA = "{{config_root}}/tests/data/.data" TASKWARRIOR_TUI_LOG_LEVEL = "debug" _.path = "{{env.TASKWARRIOR_INSTALL_DIR}}/bin" [tools] rust = { version = "latest", postinstall = "mise run taskwarrior-tui:ensure-rust-toolchain" } node = "25.8.2" uv = "latest" "aqua:Kitware/CMake" = "latest" "github:jgm/pandoc" = "latest" "cargo:cross" = "latest" "cargo:grcov" = "latest" "cargo:cargo-deb" = "latest" "cargo:cargo-rpm" = "latest" "cargo:cargo-aur" = "latest" [tasks."taskwarrior-tui:cargo-fmt"] depends=["taskwarrior-tui:ensure-rust-toolchain"] description = "Check Rust formatting" run = "cargo fmt --all -- --check" [tasks."taskwarrior-tui:cargo-check"] depends=["taskwarrior-tui:ensure-rust-toolchain"] description = "Run cargo check" run = "cargo check" [tasks."taskwarrior-tui:cargo-test"] description = "Run cargo test against pinned test fixtures" depends=["taskwarrior-tui:ensure-rust-toolchain", "taskwarrior-tui:setup-testdata"] run = "cargo test --workspace -- --nocapture" [tasks."taskwarrior-tui:cargo-clippy"] depends=["taskwarrior-tui:ensure-rust-toolchain"] description = "Run clippy with warnings denied" run = "cargo clippy -- -D warnings" [tasks."taskwarrior-tui:cargo-clean"] depends=["taskwarrior-tui:ensure-rust-toolchain"] description = "Clean the cargo build artifacts" run = "cargo clean" [tasks."taskwarrior-tui:clean-taskdata"] description = "Remove tests/data/.task" run = "rm -rf \"$TASKDATA\"" [tasks."taskwarrior-tui:clean-tests"] description = "Delete cloned test fixtures" run = "rm -rf \"$TASKWARRIOR_TESTDATA_DIR\"" [tasks."taskwarrior-tui:import-taskdata"] description = "Import tests/data/export.json into tests/data/.task" depends=["taskwarrior-tui:setup-testdata"] run = "task import rc.hooks=0 tests/data/export.json" [tasks."taskwarrior-tui:man"] description = "Generate the roff man page from Markdown" run = "pandoc --standalone --to=man packaging/man/taskwarrior-tui.1.md -o packaging/man/taskwarrior-tui.1" [tasks."taskwarrior-tui:package-aur"] description = "Build the AUR package metadata" run = "cargo aur" [tasks."taskwarrior-tui:package-deb"] description = "Build the Debian package" run = "cargo deb -p taskwarrior-tui -o target/debian/taskwarrior-tui.deb" [tasks."taskwarrior-tui:package-rpm"] description = "Build the RPM package" run = "cargo rpm build" [tasks."taskwarrior-tui:coverage"] depends=["taskwarrior-tui:ensure-rust-toolchain"] description = "Generate lcov output with grcov" run = [ "mkdir -p ./target/debug/coverage/", "grcov . -s . --binary-path ./target/debug/ -t lcov --branch --ignore-not-existing -o ./target/debug/coverage/", ] [tasks."taskwarrior-tui:ci"] description = "CI - format, check, test, clippy" depends=["taskwarrior-tui:ensure-rust-toolchain"] run = [ { task = "taskwarrior-tui:cargo-fmt" }, { task = "taskwarrior-tui:cargo-check" }, { task = "taskwarrior-tui:cargo-test" }, { task = "taskwarrior-tui:cargo-clippy" }, ] [tasks."taskwarrior-tui:docs-build"] description = "Build the documentation" run = """ cd {{config_root}}/docs && npm install && npm run build """ [tasks."taskwarrior-tui:docs-preview"] description = "Preview the documentation" run = """ cd {{config_root}}/docs && npm install && npm run dev """ taskwarrior-tui-0.27.0/.editorconfig000064400000000000000000000002251046102023000155350ustar 00000000000000# configuration for https://editorconfig.org root = true [*.rs] indent_style = space indent_size = 2 [*.yml] indent_style = space indent_size = 2 taskwarrior-tui-0.27.0/.github/CODEOWNERS000064400000000000000000000000341046102023000160110ustar 00000000000000. @kdheepak @RedEtherbloomtaskwarrior-tui-0.27.0/.github/FUNDING.yml000064400000000000000000000000231046102023000162310ustar 00000000000000github: [kdheepak] taskwarrior-tui-0.27.0/.github/ISSUE_TEMPLATE/bug_report.md000064400000000000000000000053151046102023000213020ustar 00000000000000--- name: Bug report about: Create a bug report title: "" labels: bug assignees: "" --- **Describe the bug** **To Reproduce** - [ ] Reproducible using the test data located here: Steps to reproduce the behavior: **Environment (please complete the following information):** - Operating System: - Installation: - taskwarrior-tui version: ```bash taskwarrior-tui --version ``` - task version: ```bash task --version ``` **Additional context and information** taskwarrior-tui-0.27.0/.github/ISSUE_TEMPLATE/feature_request.md000064400000000000000000000011421046102023000223270ustar 00000000000000--- name: Feature request about: Suggest an idea title: "" labels: enhancement assignees: "" --- **Description** **Solution** **Additional context** taskwarrior-tui-0.27.0/.github/dependabot.yml000064400000000000000000000011201046102023000172430ustar 00000000000000# To get started with Dependabot version updates, you'll need to specify which # package ecosystems to update and where the package manifests are located. # Please see the documentation for all configuration options: # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates version: 2 updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" - package-ecosystem: "cargo" directory: "/" schedule: interval: "weekly" groups: cargo-dependencies: patterns: ["*"] taskwarrior-tui-0.27.0/.github/workflows/build.yml000064400000000000000000000103631046102023000203030ustar 00000000000000name: Build on: pull_request: workflow_call: permissions: contents: read concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true jobs: build: permissions: contents: read runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: include: - os: macOS-latest target: x86_64-apple-darwin rust_flags: "" features: "" binary_postfix: "" - os: macOS-latest target: aarch64-apple-darwin rust_flags: "" features: "" binary_postfix: "" - os: ubuntu-latest target: x86_64-unknown-linux-gnu rust_flags: "" features: "" binary_postfix: "" upx_args: --best --lzma strip: true - os: ubuntu-latest target: x86_64-unknown-linux-musl rust_flags: "" features: "" binary_postfix: "" upx_args: --best --lzma strip: true - os: windows-latest target: x86_64-pc-windows-gnu rust_flags: -C target-feature=+crt-static features: "" binary_postfix: ".exe" upx_args: -9 strip: false - os: windows-latest target: x86_64-pc-windows-msvc rust_flags: -C target-feature=+crt-static features: "" binary_postfix: ".exe" upx_args: -9 strip: false - os: windows-latest target: i686-pc-windows-msvc rust_flags: -C target-feature=+crt-static features: "" binary_postfix: ".exe" upx_args: -9 strip: false env: RUSTFLAGS: ${{ matrix.rust_flags }} MACOSX_DEPLOYMENT_TARGET: 10.7 steps: - uses: actions/checkout@v6 - uses: jdx/mise-action@v4 - uses: Swatinem/rust-cache@v2 with: shared-key: cargo-${{ runner.os }}-${{ matrix.target }} - name: Install Rust target run: mise run taskwarrior-tui:ensure-rust-toolchain env: RUST_TOOLCHAIN_TARGETS: ${{ matrix.target }} - name: Build release binary run: cargo build --release ${{ matrix.features }} --target=${{ matrix.target }} - name: Compress binaries uses: svenstaro/upx-action@v3 with: files: target/${{ matrix.target }}/release/taskwarrior-tui${{ matrix.binary_postfix }} args: ${{ matrix.upx_args }} strip: ${{ matrix.strip }} # MacOS Compression disabled due to bug https://github.com/upx/upx/blob/44e4bd0b5454ff8aee1ff3376974dfe6014300d9/NEWS#L31 if: ${{ matrix.os != 'macOS-latest' }} - name: Packaging binary shell: bash run: | cd target/${{ matrix.target }}/release tar czvf taskwarrior-tui-${{ matrix.target }}.tar.gz taskwarrior-tui${{ matrix.binary_postfix }} if [[ ${{ runner.os }} == 'Windows' ]]; then certutil -hashfile taskwarrior-tui-${{ matrix.target }}.tar.gz sha256 | grep -E [A-Fa-f0-9]{64} > taskwarrior-tui-${{ matrix.target }}.sha256 else shasum -a 256 taskwarrior-tui-${{ matrix.target }}.tar.gz > taskwarrior-tui-${{ matrix.target }}.sha256 fi - name: Upload artifacts uses: actions/upload-artifact@v7 with: name: taskwarrior-tui-${{ matrix.target }} path: | target/${{ matrix.target }}/release/taskwarrior-tui-${{ matrix.target }}.tar.gz target/${{ matrix.target }}/release/taskwarrior-tui-${{ matrix.target }}.sha256 release-assets: if: startsWith(github.ref, 'refs/tags/') needs: build permissions: contents: write runs-on: ubuntu-latest steps: - name: Download build artifacts uses: actions/download-artifact@v8 with: path: release-artifacts pattern: taskwarrior-tui-* merge-multiple: true - name: Releasing assets uses: softprops/action-gh-release@v2 with: files: | release-artifacts/*.tar.gz release-artifacts/*.sha256 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} taskwarrior-tui-0.27.0/.github/workflows/cd.yml000064400000000000000000000053101046102023000175660ustar 00000000000000name: CD on: release: types: [published] permissions: contents: write jobs: build: uses: ./.github/workflows/build.yml generate_coverage: uses: ./.github/workflows/generate_coverage.yml homebrew: name: Bump Homebrew formula runs-on: macos-latest continue-on-error: true steps: - name: Update Homebrew formula if: startsWith(github.ref, 'refs/tags/') uses: dawidd6/action-homebrew-bump-formula@v7 with: token: ${{secrets.HOMEBREW_TOKEN}} formula: taskwarrior-tui deb: name: Publish deb package runs-on: ubuntu-latest steps: - name: Check out Git repository uses: actions/checkout@v6 - uses: jdx/mise-action@v4 - uses: Swatinem/rust-cache@v2 with: shared-key: cargo-${{ runner.os }} - run: cargo build --release - name: Build deb package run: mise run taskwarrior-tui:package-deb - name: Upload artifacts uses: actions/upload-artifact@v7 with: name: taskwarrior-tui-deb path: target/debian/taskwarrior-tui.deb - name: Releasing assets if: startsWith(github.ref, 'refs/tags/') uses: softprops/action-gh-release@v2 with: files: | target/debian/*.deb env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} rpm: name: Publish rpm package runs-on: ubuntu-latest steps: - name: Check out Git repository uses: actions/checkout@v6 - uses: jdx/mise-action@v4 - uses: Swatinem/rust-cache@v2 with: shared-key: cargo-${{ runner.os }} - run: cargo build --release - name: Install rpm run: sudo apt-get update && sudo apt-get install -y rpm - name: Build rpm package run: mise run taskwarrior-tui:package-rpm - name: Upload artifacts uses: actions/upload-artifact@v7 with: name: taskwarrior-tui-rpm path: target/release/rpmbuild/RPMS/x86_64/taskwarrior-tui-*.x86_64.rpm - name: Releasing assets if: startsWith(github.ref, 'refs/tags/') uses: softprops/action-gh-release@v2 with: files: | target/release/rpmbuild/RPMS/x86_64/taskwarrior-tui-*.x86_64.rpm env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} aur: name: Publish aur package runs-on: ubuntu-latest steps: - name: Check out Git repository uses: actions/checkout@v6 - uses: jdx/mise-action@v4 - uses: Swatinem/rust-cache@v2 with: shared-key: cargo-${{ runner.os }} - run: cargo build --release - name: Build aur package run: mise run taskwarrior-tui:package-aur taskwarrior-tui-0.27.0/.github/workflows/check-pr.yml000064400000000000000000000070731046102023000207040ustar 00000000000000name: Check Pull Requests on: pull_request_target: types: - opened - edited - synchronize - labeled - unlabeled merge_group: permissions: issues: write pull-requests: write concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true jobs: check-title: runs-on: ubuntu-latest steps: - name: Check PR title if: github.event_name == 'pull_request_target' uses: amannn/action-semantic-pull-request@v6 id: check_pr_title env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Add comment indicating we require pull request titles to follow conventional commits specification - uses: marocchino/sticky-pull-request-comment@v3 if: always() && (steps.check_pr_title.outputs.error_message != null) with: header: pr-title-lint-error message: | Thank you for opening this pull request! We require pull request titles to follow the [Conventional Commits specification](https://www.conventionalcommits.org/en/v1.0.0/) and it looks like your proposed title needs to be adjusted. Details: > ${{ steps.check_pr_title.outputs.error_message }} # Delete a previous comment when the issue has been resolved - if: ${{ steps.check_pr_title.outputs.error_message == null }} uses: marocchino/sticky-pull-request-comment@v3 with: header: pr-title-lint-error delete: true check-breaking-change-label: if: github.event_name == 'pull_request_target' runs-on: ubuntu-latest env: # use an environment variable to pass untrusted input to the script # see https://securitylab.github.com/research/github-actions-untrusted-input/ PR_TITLE: ${{ github.event.pull_request.title }} steps: - name: Check breaking change label id: check_breaking_change run: | pattern='^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(\(\w+\))?!:' # Check if pattern matches if echo "${PR_TITLE}" | grep -qE "$pattern"; then echo "breaking_change=true" >> $GITHUB_OUTPUT else echo "breaking_change=false" >> $GITHUB_OUTPUT fi - name: Sync breaking-change label uses: actions/github-script@v8 env: BREAKING_CHANGE: ${{ steps.check_breaking_change.outputs.breaking_change }} with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | const label = 'Type: Breaking Change'; const shouldHaveLabel = process.env.BREAKING_CHANGE === 'true'; const params = { issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, }; if (shouldHaveLabel) { await github.rest.issues.addLabels({ ...params, labels: [label], }); return; } try { await github.rest.issues.removeLabel({ ...params, name: label, }); } catch (error) { if (error.status !== 404) { throw error; } } do-not-merge: if: ${{ contains(github.event.*.labels.*.name, 'do not merge') }} name: Prevent Merging runs-on: ubuntu-latest steps: - name: Check for label run: | echo "Pull request is labeled as 'do not merge'" echo "This workflow fails so that the pull request cannot be merged" exit 1 taskwarrior-tui-0.27.0/.github/workflows/ci.yml000064400000000000000000000041571046102023000176030ustar 00000000000000on: [pull_request] name: CI permissions: contents: read concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true jobs: check: name: Check runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: jdx/mise-action@v4 - uses: Swatinem/rust-cache@v2 with: shared-key: cargo-${{ runner.os }} - run: mise run taskwarrior-tui:cargo-check test: name: Test Suite runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: jdx/mise-action@v4 - uses: Swatinem/rust-cache@v2 with: shared-key: cargo-${{ runner.os }} - name: Restore cached Taskwarrior build id: taskwarrior-cache uses: actions/cache@v5 with: path: tmp/taskwarrior-install key: ${{ runner.os }}-taskwarrior-${{ hashFiles('.config/mise.toml', '.config/mise/tasks/taskwarrior-tui/build-taskwarrior') }} - name: Install Taskwarrior build dependencies run: sudo apt-get update && sudo apt-get install -y uuid-dev uuid-runtime - name: Build Taskwarrior from source if: steps.taskwarrior-cache.outputs.cache-hit != 'true' run: mise run taskwarrior-tui:build-taskwarrior - run: | # prepare taskwarrior, initial setup mise exec -- task rc.confirmation=off || echo 0 - run: mise run taskwarrior-tui:cargo-test env: RUST_BACKTRACE: full generate_coverage: uses: ./.github/workflows/generate_coverage.yml needs: test fmt: name: Rustfmt runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: jdx/mise-action@v4 - uses: Swatinem/rust-cache@v2 with: shared-key: cargo-${{ runner.os }} - run: mise run taskwarrior-tui:cargo-fmt clippy: name: Clippy runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: jdx/mise-action@v4 - uses: Swatinem/rust-cache@v2 with: shared-key: cargo-${{ runner.os }} - run: mise run taskwarrior-tui:cargo-clippy taskwarrior-tui-0.27.0/.github/workflows/docs.yml000064400000000000000000000021361046102023000201330ustar 00000000000000name: Docs on: push: pull_request: types: [opened, synchronize] concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true jobs: build-site: permissions: contents: read runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: jdx/mise-action@v4 - name: Build site run: mise run taskwarrior-tui:docs-build - name: Upload site artifact uses: actions/upload-artifact@v7 with: name: site path: site deploy-pages: if: github.event_name == 'push' && github.ref == 'refs/heads/main' needs: build-site permissions: contents: write runs-on: ubuntu-latest steps: - name: Download site artifact uses: actions/download-artifact@v8 with: name: site path: site - name: Github Pages Deploy uses: peaceiris/actions-gh-pages@v4 with: personal_token: ${{ secrets.GITHUB_TOKEN }} publish_branch: gh-pages publish_dir: ./site force_orphan: true taskwarrior-tui-0.27.0/.github/workflows/generate_coverage.yml000064400000000000000000000032131046102023000226450ustar 00000000000000name: generate_coverage on: workflow_call permissions: contents: read jobs: grcov: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: jdx/mise-action@v4 - uses: Swatinem/rust-cache@v2 with: shared-key: coverage-${{ runner.os }} - name: Restore cached Taskwarrior build id: taskwarrior-cache uses: actions/cache@v5 with: path: tmp/taskwarrior-install key: ${{ runner.os }}-taskwarrior-${{ hashFiles('.config/mise.toml', '.config/mise/tasks/taskwarrior-tui/build-taskwarrior') }} - name: Install Taskwarrior build dependencies run: sudo apt-get update && sudo apt-get install -y uuid-dev uuid-runtime - name: Build Taskwarrior from source if: steps.taskwarrior-cache.outputs.cache-hit != 'true' run: mise run taskwarrior-tui:build-taskwarrior - run: | # prepare taskwarrior, initial setup mise exec -- task rc.confirmation=off || echo 0 - name: Build taskwarrior-tui with coverage run: cargo build env: RUSTFLAGS: "-Cinstrument-coverage" - name: Run taskwarrior-tui tests in coverage environment run: mise run taskwarrior-tui:cargo-test env: RUST_BACKTRACE: full RUSTFLAGS: "-Cinstrument-coverage" LLVM_PROFILE_FILE: "your_name-%p-%m.profraw" - name: Gather coverage data run: mise run taskwarrior-tui:coverage - name: Coveralls upload uses: coverallsapp/github-action@v2 with: github-token: ${{ secrets.GITHUB_TOKEN }} file: ./target/debug/coverage/lcov taskwarrior-tui-0.27.0/.github/workflows/release-plz.yml000064400000000000000000000011271046102023000214250ustar 00000000000000name: Release Plz permissions: pull-requests: write contents: write on: push: branches: - main jobs: release-plz: name: Release-plz runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v6 with: fetch-depth: 0 - name: Install Rust toolchain uses: dtolnay/rust-toolchain@stable - name: Run release-plz uses: MarcoIeni/release-plz-action@v0.5 env: GITHUB_TOKEN: ${{ secrets.RELEASE_PLZ_TOKEN }} CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} taskwarrior-tui-0.27.0/.gitignore000064400000000000000000000005641046102023000150560ustar 00000000000000### Rust ### # Generated by Cargo # will have compiled files and executables target/** # These are backup files generated by rustfmt **/*.rs.bk # End of https://www.toptal.com/developers/gitignore/api/rust tests/data # Direnv (Developer with Nixos) flake.nix flake.lock .direnv/ .envrc Makefile justfile .justfile tmp docs/.astro/ docs/node_modules/ site/ *.gif *.mp4 taskwarrior-tui-0.27.0/.rpm/taskwarrior-tui.spec000064400000000000000000000011261046102023000177600ustar 00000000000000%define __spec_install_post %{nil} %define __os_install_post %{_dbpath}/brp-compress %define debug_package %{nil} Name: taskwarrior-tui Summary: A Taskwarrior Terminal User Interface Version: @@VERSION@@ Release: @@RELEASE@@%{?dist} License: MIT Group: Applications/System Source0: %{name}-%{version}.tar.gz URL: https://kdheepak.com/taskwarrior-tui BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root %description %{summary} %prep %setup -q %install rm -rf %{buildroot} mkdir -p %{buildroot} cp -a * %{buildroot} %clean rm -rf %{buildroot} %files %defattr(-,root,root,-) %{_bindir}/* taskwarrior-tui-0.27.0/.rustfmt.toml000064400000000000000000000000371046102023000155400ustar 00000000000000max_width = 150 tab_spaces = 2 taskwarrior-tui-0.27.0/CHANGELOG.md000064400000000000000000000237061046102023000147020ustar 00000000000000# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] ## [0.27.0](https://github.com/kdheepak/taskwarrior-tui/compare/v0.26.12...v0.27.0) - 2026-04-09 ### Added - [**breaking**] auto functionality for task info show ([#743](https://github.com/kdheepak/taskwarrior-tui/pull/743)) ### Other - Add information about quick-tag to documentation ([#746](https://github.com/kdheepak/taskwarrior-tui/pull/746)) - Update README.md ([#745](https://github.com/kdheepak/taskwarrior-tui/pull/745)) - Add demo vhs tape ([#742](https://github.com/kdheepak/taskwarrior-tui/pull/742)) ## [0.26.12](https://github.com/kdheepak/taskwarrior-tui/compare/v0.26.11...v0.26.12) - 2026-04-08 ### Added - scroll task list with mouse wheel ([#738](https://github.com/kdheepak/taskwarrior-tui/pull/738)) ## [0.26.11](https://github.com/kdheepak/taskwarrior-tui/compare/v0.26.10...v0.26.11) - 2026-04-08 ### Added - add "timesheet" tab ([#728](https://github.com/kdheepak/taskwarrior-tui/pull/728)) ### Other - *(deps)* narrow `tokio`'s featureset; remove unused `tokio-stream` ([#731](https://github.com/kdheepak/taskwarrior-tui/pull/731)) - *(deps)* `cassowary` -> `kasuari` ([#734](https://github.com/kdheepak/taskwarrior-tui/pull/734)) - *(deps)* remove unused features; remove unused deps ([#732](https://github.com/kdheepak/taskwarrior-tui/pull/732)) ## [0.26.10](https://github.com/kdheepak/taskwarrior-tui/compare/v0.26.9...v0.26.10) - 2026-04-02 ### Fixed - Fix calendar crash on small windows ([#727](https://github.com/kdheepak/taskwarrior-tui/pull/727)) - *(config)* parse color keys with spaces in attribute names ([#726](https://github.com/kdheepak/taskwarrior-tui/pull/726)) - add auto-select for context and report menus ([#725](https://github.com/kdheepak/taskwarrior-tui/pull/725)) - make enter select filtered report items when select-on-move ([#722](https://github.com/kdheepak/taskwarrior-tui/pull/722)) ## [0.26.9](https://github.com/kdheepak/taskwarrior-tui/compare/v0.26.8...v0.26.9) - 2026-04-01 ### Added - add support for `color.keyword` attribute ([#719](https://github.com/kdheepak/taskwarrior-tui/pull/719)) - add support for bracketed paste and handle paste events ([#716](https://github.com/kdheepak/taskwarrior-tui/pull/716)) - add caching for task details based on modified date and width ([#707](https://github.com/kdheepak/taskwarrior-tui/pull/707)) ### Fixed - use `due` from config rather than hardcoded 7 days ([#718](https://github.com/kdheepak/taskwarrior-tui/pull/718)) - color rendering bugs with modifiers ([#709](https://github.com/kdheepak/taskwarrior-tui/pull/709)) ### Other - include taskwarrior-tui specific colors in the docs ([#667](https://github.com/kdheepak/taskwarrior-tui/pull/667)) - *(deps)* remove `lazy_static` ([#710](https://github.com/kdheepak/taskwarrior-tui/pull/710)) - *(deps)* bump the cargo-dependencies group with 2 updates ([#714](https://github.com/kdheepak/taskwarrior-tui/pull/714)) - *(deps)* bump actions/cache from 4 to 5 ([#712](https://github.com/kdheepak/taskwarrior-tui/pull/712)) - *(deps)* bump actions/download-artifact from 4 to 8 ([#713](https://github.com/kdheepak/taskwarrior-tui/pull/713)) - *(deps)* bump jdx/mise-action from 3 to 4 ([#711](https://github.com/kdheepak/taskwarrior-tui/pull/711)) ## [0.26.8](https://github.com/kdheepak/taskwarrior-tui/compare/v0.26.7...v0.26.8) - 2026-03-30 ### Added - add close-on-select options for context and report menus ([#706](https://github.com/kdheepak/taskwarrior-tui/pull/706)) - implement alternating line colors a la taskwarrior ([#702](https://github.com/kdheepak/taskwarrior-tui/pull/702)) - set custom `task` executable location using an environment variable ([#696](https://github.com/kdheepak/taskwarrior-tui/pull/696)) - *(ui)* add ghost text for unique completion result. ([#685](https://github.com/kdheepak/taskwarrior-tui/pull/685)) - show `tag:` as a completion option for the filter ([#690](https://github.com/kdheepak/taskwarrior-tui/pull/690)) ### Fixed - *(date)* correct local datetime conversion and formatting logic ([#704](https://github.com/kdheepak/taskwarrior-tui/pull/704)) ### Other - *(datetime)* extract and centralize datetime formatting functions ([#705](https://github.com/kdheepak/taskwarrior-tui/pull/705)) - add troubleshooting guide ([#701](https://github.com/kdheepak/taskwarrior-tui/pull/701)) - *(ci)* update concurrency group in docs workflow - *(docs)* migrate docs to Astro Starlight ([#699](https://github.com/kdheepak/taskwarrior-tui/pull/699)) - migrate taskwarrior-tui tasks to toml config ([#698](https://github.com/kdheepak/taskwarrior-tui/pull/698)) - update to rust 2024 edition ([#695](https://github.com/kdheepak/taskwarrior-tui/pull/695)) - *(ci)* add i686-pc-windows-msvc target to build matrix ([#694](https://github.com/kdheepak/taskwarrior-tui/pull/694)) - better locally reproducible mise tasks for testing and development ([#693](https://github.com/kdheepak/taskwarrior-tui/pull/693)) - add tasks for managing and running test fixture data ([#691](https://github.com/kdheepak/taskwarrior-tui/pull/691)) - *(ci)* update release-plz workflow to use rust-toolchain action instead of mise ([#689](https://github.com/kdheepak/taskwarrior-tui/pull/689)) - common tooling for ci and local development using mise ([#687](https://github.com/kdheepak/taskwarrior-tui/pull/687)) - Add continue-on-error to Homebrew bump step ([#686](https://github.com/kdheepak/taskwarrior-tui/pull/686)) ## [0.26.7](https://github.com/kdheepak/taskwarrior-tui/compare/v0.26.6...v0.26.7) - 2026-03-24 ### Fixed - perform full redraw after opening external applications ([#683](https://github.com/kdheepak/taskwarrior-tui/pull/683)) ## [0.26.6](https://github.com/kdheepak/taskwarrior-tui/compare/v0.26.5...v0.26.6) - 2026-01-26 ### Fixed - Apply color.uda.* configuration for all UDAs ([#652](https://github.com/kdheepak/taskwarrior-tui/pull/652)) ### Other - *(deps)* bump clechasseur/rs-cargo from 2 to 4 ([#665](https://github.com/kdheepak/taskwarrior-tui/pull/665)) - *(deps)* bump unicode-truncate from 2.0.0 to 2.0.1 in the cargo-dependencies group ([#663](https://github.com/kdheepak/taskwarrior-tui/pull/663)) - *(deps)* bump dawidd6/action-homebrew-bump-formula from 3 to 7 ([#662](https://github.com/kdheepak/taskwarrior-tui/pull/662)) - *(deps)* bump actions/github-script from 7 to 8 ([#661](https://github.com/kdheepak/taskwarrior-tui/pull/661)) - *(deps)* bump amannn/action-semantic-pull-request from 5 to 6 ([#660](https://github.com/kdheepak/taskwarrior-tui/pull/660)) - *(deps)* bump actions/setup-python from 5 to 6 ([#658](https://github.com/kdheepak/taskwarrior-tui/pull/658)) - *(deps)* bump actions/checkout from 4 to 6 ([#659](https://github.com/kdheepak/taskwarrior-tui/pull/659)) - *(deps)* bump the cargo-dependencies group across 1 directory with 21 updates ([#657](https://github.com/kdheepak/taskwarrior-tui/pull/657)) - *(deps)* bump actions/upload-artifact from 4 to 6 ([#576](https://github.com/kdheepak/taskwarrior-tui/pull/576)) - update ratatui to `0.30.0` ([#655](https://github.com/kdheepak/taskwarrior-tui/pull/655)) ## [0.26.5](https://github.com/kdheepak/taskwarrior-tui/compare/v0.26.4...v0.26.5) - 2025-12-18 ### Added - add desktop entry under docs ([#650](https://github.com/kdheepak/taskwarrior-tui/pull/650)) - help screen shows user keybindings ([#645](https://github.com/kdheepak/taskwarrior-tui/pull/645)) ### Fixed - Fixes duplicate key bug when assigning key to `edit` ([#644](https://github.com/kdheepak/taskwarrior-tui/pull/644)) - README.md typo ([#640](https://github.com/kdheepak/taskwarrior-tui/pull/640)) ### Other - *(setup)* add just recipes for local test data ([#646](https://github.com/kdheepak/taskwarrior-tui/pull/646)) ## [0.26.4](https://github.com/kdheepak/taskwarrior-tui/compare/v0.26.3...v0.26.4) - 2024-11-14 ### Added - Add code coverage to CI - Cache taskwarrior compilation - Build taskwarrior CI against stable ### Fixed - Disable broken MacOS compression - Append target for unique artifact names - Update the upload-artifacts due to deprecation of v2 - Deprecation warning ### Other - Add us as Co-Maintainers ([#606](https://github.com/kdheepak/taskwarrior-tui/pull/606)) - Apply clippy lint - Merge build workflows - Modernize CI/CD components - Use config for selection mark/unmark symbols ([#594](https://github.com/kdheepak/taskwarrior-tui/pull/594)) - *(deps)* bump the cargo-dependencies group with 4 updates ([#584](https://github.com/kdheepak/taskwarrior-tui/pull/584)) - *(deps)* bump tokio from 1.37.0 to 1.38.0 in the cargo-dependencies group ([#582](https://github.com/kdheepak/taskwarrior-tui/pull/582)) - *(deps)* bump the cargo-dependencies group with 3 updates ([#580](https://github.com/kdheepak/taskwarrior-tui/pull/580)) - Update taskwarrior-tui.bash - *(deps)* bump the cargo-dependencies group across 1 directory with 20 updates ([#573](https://github.com/kdheepak/taskwarrior-tui/pull/573)) - *(deps)* Bump to ratatui v0.26 - *(deps)* bump actions/checkout from 2 to 4 ([#569](https://github.com/kdheepak/taskwarrior-tui/pull/569)) - *(deps)* bump peaceiris/actions-gh-pages from 3 to 4 ([#568](https://github.com/kdheepak/taskwarrior-tui/pull/568)) - *(deps)* bump actions/setup-python from 1 to 5 ([#566](https://github.com/kdheepak/taskwarrior-tui/pull/566)) - *(deps)* bump actions/setup-python from 1 to 5 - Add dependabot.yml ## [0.26.3](https://github.com/kdheepak/taskwarrior-tui/compare/v0.26.2...v0.26.3) - 2024-05-12 ### Other - Update cd.yml ## [0.26.2](https://github.com/kdheepak/taskwarrior-tui/compare/v0.26.1...v0.26.2) - 2024-05-12 ### Added - Add task duplicate - Add scheduled - Add scheduled countdown - Add recur to autocomplete options ### Other - fix clippy issues - Update release-plz.yml with token ## [0.26.1](https://github.com/kdheepak/taskwarrior-tui/compare/v0.26.0...v0.26.1) - 2024-05-12 ### Other - Remove snap, appimage and crates.io taskwarrior-tui-0.27.0/Cargo.lock0000644000002074101046102023000122720ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "addr2line" version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" dependencies = [ "gimli", ] [[package]] name = "adler2" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "aho-corasick" version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] [[package]] name = "allocator-api2" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "android_system_properties" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" dependencies = [ "libc", ] [[package]] name = "anstream" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anstyle-parse" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ "windows-sys 0.61.2", ] [[package]] name = "anstyle-wincon" version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", "windows-sys 0.61.2", ] [[package]] name = "anyhow" version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "arc-swap" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a07d1f37ff60921c83bdfc7407723bdefe89b44b98a9b772f225c8f9d67141a6" dependencies = [ "rustversion", ] [[package]] name = "atomic" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89cbf775b137e9b968e67227ef7f775587cde3fd31b0d8599dbd0f598a48340" dependencies = [ "bytemuck", ] [[package]] name = "autocfg" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "backtrace" version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" dependencies = [ "addr2line", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", "windows-link", ] [[package]] name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "better-panic" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fa9e1d11a268684cbd90ed36370d7577afb6c62d912ddff5c15fc34343e5036" dependencies = [ "backtrace", "console", ] [[package]] name = "bit-set" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" dependencies = [ "bit-vec", ] [[package]] name = "bit-vec" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" [[package]] name = "block-buffer" version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array", ] [[package]] name = "bumpalo" version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "bytemuck" version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" [[package]] name = "bytes" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" [[package]] name = "castaway" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" dependencies = [ "rustversion", ] [[package]] name = "cc" version = "1.2.58" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1e928d4b69e3077709075a938a05ffbedfa53a84c8f766efbf8220bb1ff60e1" dependencies = [ "find-msvc-tools", "shlex", ] [[package]] name = "cfg-if" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cfg_aliases" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" version = "0.4.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" dependencies = [ "iana-time-zone", "js-sys", "num-traits", "wasm-bindgen", "windows-link", ] [[package]] name = "clap" version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" dependencies = [ "anstream", "anstyle", "clap_lex", "strsim 0.11.1", ] [[package]] name = "clap_complete" version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19c9f1dde76b736e3681f28cec9d5a61299cbaae0fce80a68e43724ad56031eb" dependencies = [ "clap", ] [[package]] name = "clap_lex" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "clipboard-win" version = "5.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4" dependencies = [ "error-code", ] [[package]] name = "colorchoice" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "compact_str" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb1325a1cece981e8a296ab8f0f9b63ae357bd0784a9faaf548cc7b480707a" dependencies = [ "castaway", "cfg-if", "itoa", "rustversion", "ryu", "static_assertions", ] [[package]] name = "console" version = "0.15.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" dependencies = [ "encode_unicode", "libc", "once_cell", "windows-sys 0.59.0", ] [[package]] name = "convert_case" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" dependencies = [ "unicode-segmentation", ] [[package]] name = "core-foundation-sys" version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] [[package]] name = "crossterm" version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" dependencies = [ "bitflags 2.11.0", "crossterm_winapi", "derive_more", "document-features", "futures-core", "mio", "parking_lot", "rustix", "signal-hook", "signal-hook-mio", "winapi", ] [[package]] name = "crossterm_winapi" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" dependencies = [ "winapi", ] [[package]] name = "crypto-common" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", "typenum", ] [[package]] name = "csscolorparser" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb2a7d3066da2de787b7f032c736763eb7ae5d355f81a68bab2675a96008b0bf" dependencies = [ "lab", "phf", ] [[package]] name = "darling" version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" dependencies = [ "darling_core 0.14.4", "darling_macro 0.14.4", ] [[package]] name = "darling" version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" dependencies = [ "darling_core 0.23.0", "darling_macro 0.23.0", ] [[package]] name = "darling_core" version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim 0.10.0", "syn 1.0.109", ] [[package]] name = "darling_core" version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" dependencies = [ "ident_case", "proc-macro2", "quote", "strsim 0.11.1", "syn 2.0.117", ] [[package]] name = "darling_macro" version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" dependencies = [ "darling_core 0.14.4", "quote", "syn 1.0.109", ] [[package]] name = "darling_macro" version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" dependencies = [ "darling_core 0.23.0", "quote", "syn 2.0.117", ] [[package]] name = "deltae" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5729f5117e208430e437df2f4843f5e5952997175992d1414f94c57d61e270b4" [[package]] name = "deranged" version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ "powerfmt", ] [[package]] name = "derive_builder" version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d67778784b508018359cbc8696edb3db78160bab2c2a28ba7f56ef6932997f8" dependencies = [ "derive_builder_macro", ] [[package]] name = "derive_builder_core" version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c11bdc11a0c47bc7d37d582b5285da6849c96681023680b906673c5707af7b0f" dependencies = [ "darling 0.14.4", "proc-macro2", "quote", "syn 1.0.109", ] [[package]] name = "derive_builder_macro" version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebcda35c7a396850a55ffeac740804b40ffec779b98fffbb1738f4033f0ee79e" dependencies = [ "derive_builder_core", "syn 1.0.109", ] [[package]] name = "derive_more" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" dependencies = [ "derive_more-impl", ] [[package]] name = "derive_more-impl" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" dependencies = [ "convert_case", "proc-macro2", "quote", "rustc_version", "syn 2.0.117", "unicode-xid", ] [[package]] name = "destructure_traitobject" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c877555693c14d2f84191cfd3ad8582790fc52b5e2274b40b59cf5f5cea25c7" [[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", ] [[package]] name = "dirs" version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" dependencies = [ "dirs-sys", ] [[package]] name = "dirs-sys" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" dependencies = [ "libc", "option-ext", "redox_users", "windows-sys 0.61.2", ] [[package]] name = "document-features" version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" dependencies = [ "litrs", ] [[package]] name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "encode_unicode" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" [[package]] name = "endian-type" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "869b0adbda23651a9c5c0c3d270aac9fcb52e8622a8f2b17e57802d7791962f2" [[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", "windows-sys 0.61.2", ] [[package]] name = "error-code" version = "3.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" [[package]] name = "euclid" version = "0.22.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1a05365e3b1c6d1650318537c7460c6923f1abdd272ad6842baa2b509957a06" dependencies = [ "num-traits", ] [[package]] name = "fancy-regex" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b95f7c0680e4142284cf8b22c14a476e87d61b004a3a0861872b32ef7ead40a2" dependencies = [ "bit-set", "regex", ] [[package]] name = "filedescriptor" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e40758ed24c9b2eeb76c35fb0aebc66c626084edd827e07e1552279814c6682d" dependencies = [ "libc", "thiserror 1.0.69", "winapi", ] [[package]] name = "find-msvc-tools" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "finl_unicode" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9844ddc3a6e533d62bba727eb6c28b5d360921d5175e9ff0f1e621a5c590a4d5" [[package]] name = "fixedbitset" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foldhash" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "foldhash" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" [[package]] name = "futures" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" dependencies = [ "futures-channel", "futures-core", "futures-executor", "futures-io", "futures-sink", "futures-task", "futures-util", ] [[package]] name = "futures-channel" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", "futures-sink", ] [[package]] name = "futures-core" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-executor" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" dependencies = [ "futures-core", "futures-task", "futures-util", ] [[package]] name = "futures-io" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" [[package]] name = "futures-macro" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", "syn 2.0.117", ] [[package]] name = "futures-sink" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] name = "futures-task" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-util" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-channel", "futures-core", "futures-io", "futures-macro", "futures-sink", "futures-task", "memchr", "pin-project-lite", "slab", ] [[package]] name = "generic-array" version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", ] [[package]] name = "getrandom" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "libc", "wasi", ] [[package]] name = "getrandom" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", "r-efi 5.3.0", "wasip2", ] [[package]] name = "getrandom" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" dependencies = [ "cfg-if", "libc", "r-efi 6.0.0", "wasip2", "wasip3", ] [[package]] name = "gimli" version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" [[package]] name = "hashbrown" version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "foldhash 0.1.5", ] [[package]] name = "hashbrown" version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" dependencies = [ "allocator-api2", "equivalent", "foldhash 0.2.0", ] [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "home" version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" dependencies = [ "windows-sys 0.61.2", ] [[package]] name = "humantime" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" [[package]] name = "iana-time-zone" version = "0.1.65" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "log", "wasm-bindgen", "windows-core", ] [[package]] name = "iana-time-zone-haiku" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" dependencies = [ "cc", ] [[package]] name = "id-arena" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" [[package]] name = "ident_case" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "indexmap" version = "2.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45a8a2b9cb3e0b0c1803dbb0758ffac5de2f425b23c28f518faabd9d805342ff" dependencies = [ "equivalent", "hashbrown 0.16.1", "serde", "serde_core", ] [[package]] name = "indoc" version = "2.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" dependencies = [ "rustversion", ] [[package]] name = "instability" version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5eb2d60ef19920a3a9193c3e371f726ec1dafc045dac788d0fb3704272458971" dependencies = [ "darling 0.23.0", "indoc", "proc-macro2", "quote", "syn 2.0.117", ] [[package]] name = "is_terminal_polyfill" version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "itertools" version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" dependencies = [ "either", ] [[package]] name = "itoa" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "js-sys" version = "0.3.94" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e04e2ef80ce82e13552136fabeef8a5ed1f985a96805761cbb9a2c34e7664d9" dependencies = [ "once_cell", "wasm-bindgen", ] [[package]] name = "kasuari" version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bde5057d6143cc94e861d90f591b9303d6716c6b9602309150bd068853c10899" dependencies = [ "hashbrown 0.16.1", "portable-atomic", "thiserror 2.0.18", ] [[package]] name = "lab" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf36173d4167ed999940f804952e6b08197cae5ad5d572eb4db150ce8ad5d58f" [[package]] name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "leb128fmt" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" version = "0.2.184" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" [[package]] name = "libredox" version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ddbf48fd451246b1f8c2610bd3b4ac0cc6e149d89832867093ab69a17194f08" dependencies = [ "libc", ] [[package]] name = "line-clipping" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f50e8f47623268b5407192d26876c4d7f89d686ca130fdc53bced4814cd29f8" dependencies = [ "bitflags 2.11.0", ] [[package]] name = "linux-raw-sys" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litrs" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" [[package]] name = "lock_api" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ "scopeguard", ] [[package]] name = "log" version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" dependencies = [ "serde_core", ] [[package]] name = "log-mdc" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a94d21414c1f4a51209ad204c1776a3d0765002c76c6abcb602a6f09f1e881c7" [[package]] name = "log4rs" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e947bb896e702c711fccc2bf02ab2abb6072910693818d1d6b07ee2b9dfd86c" dependencies = [ "anyhow", "arc-swap", "chrono", "derive_more", "fnv", "humantime", "libc", "log", "log-mdc", "mock_instant", "parking_lot", "rand 0.9.2", "serde", "serde-value", "serde_json", "serde_yaml", "thiserror 2.0.18", "thread-id", "typemap-ors", "unicode-segmentation", "winapi", ] [[package]] name = "lru" version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593" dependencies = [ "hashbrown 0.16.1", ] [[package]] name = "mac_address" version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0aeb26bf5e836cc1c341c8106051b573f1766dfa05aa87f0b98be5e51b02303" dependencies = [ "nix 0.29.0", "winapi", ] [[package]] name = "memchr" version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "memmem" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a64a92489e2744ce060c349162be1c5f33c6969234104dbd99ddb5feb08b8c15" [[package]] name = "memoffset" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" dependencies = [ "autocfg", ] [[package]] name = "minimal-lexical" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", ] [[package]] name = "mio" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" dependencies = [ "libc", "log", "wasi", "windows-sys 0.61.2", ] [[package]] name = "mock_instant" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dce6dd36094cac388f119d2e9dc82dc730ef91c32a6222170d630e5414b956e6" [[package]] name = "nibble_vec" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" dependencies = [ "smallvec", ] [[package]] name = "nix" version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ "bitflags 2.11.0", "cfg-if", "cfg_aliases", "libc", "memoffset", ] [[package]] name = "nix" version = "0.31.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d6d0705320c1e6ba1d912b5e37cf18071b6c2e9b7fa8215a1e8a7651966f5d3" dependencies = [ "bitflags 2.11.0", "cfg-if", "cfg_aliases", "libc", ] [[package]] name = "nom" version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ "memchr", "minimal-lexical", ] [[package]] name = "nom" version = "8.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" dependencies = [ "memchr", ] [[package]] name = "num-conv" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" [[package]] name = "num-derive" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", "syn 2.0.117", ] [[package]] name = "num-traits" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] [[package]] name = "num_threads" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" dependencies = [ "libc", ] [[package]] name = "object" version = "0.37.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" dependencies = [ "memchr", ] [[package]] name = "once_cell" version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "once_cell_polyfill" version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "option-ext" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] name = "ordered-float" version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" dependencies = [ "num-traits", ] [[package]] name = "ordered-float" version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951" dependencies = [ "num-traits", ] [[package]] name = "parking_lot" version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", "parking_lot_core", ] [[package]] name = "parking_lot_core" version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", "windows-link", ] [[package]] name = "path-clean" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17359afc20d7ab31fdb42bb844c8b3bb1dabd7dcf7e68428492da7f16966fcef" [[package]] name = "pest" version = "2.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662" dependencies = [ "memchr", "ucd-trie", ] [[package]] name = "pest_derive" version = "2.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11f486f1ea21e6c10ed15d5a7c77165d0ee443402f0780849d1768e7d9d6fe77" dependencies = [ "pest", "pest_generator", ] [[package]] name = "pest_generator" version = "2.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8040c4647b13b210a963c1ed407c1ff4fdfa01c31d6d2a098218702e6664f94f" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", "syn 2.0.117", ] [[package]] name = "pest_meta" version = "2.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89815c69d36021a140146f26659a81d6c2afa33d216d736dd4be5381a7362220" dependencies = [ "pest", "sha2", ] [[package]] name = "phf" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" dependencies = [ "phf_macros", "phf_shared", ] [[package]] name = "phf_codegen" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" dependencies = [ "phf_generator", "phf_shared", ] [[package]] name = "phf_generator" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ "phf_shared", "rand 0.8.5", ] [[package]] name = "phf_macros" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" dependencies = [ "phf_generator", "phf_shared", "proc-macro2", "quote", "syn 2.0.117", ] [[package]] name = "phf_shared" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" dependencies = [ "siphasher", ] [[package]] name = "pin-project-lite" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "portable-atomic" version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "powerfmt" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ "zerocopy", ] [[package]] name = "prettyplease" version = "0.2.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", "syn 2.0.117", ] [[package]] name = "proc-macro2" version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] [[package]] name = "r-efi" version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "r-efi" version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "radix_trie" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b4431027dcd37fc2a73ef740b5f233aa805897935b8bce0195e41bbf9a3289a" dependencies = [ "endian-type", "nibble_vec", ] [[package]] name = "rand" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "rand_core 0.6.4", ] [[package]] name = "rand" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha", "rand_core 0.9.5", ] [[package]] name = "rand_chacha" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", "rand_core 0.9.5", ] [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" [[package]] name = "rand_core" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" dependencies = [ "getrandom 0.3.4", ] [[package]] name = "ratatui" version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d1ce67fb8ba4446454d1c8dbaeda0557ff5e94d39d5e5ed7f10a65eb4c8266bc" dependencies = [ "instability", "ratatui-core", "ratatui-crossterm", "ratatui-macros", "ratatui-termwiz", "ratatui-widgets", ] [[package]] name = "ratatui-core" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ef8dea09a92caaf73bff7adb70b76162e5937524058a7e5bff37869cbbec293" dependencies = [ "bitflags 2.11.0", "compact_str", "hashbrown 0.16.1", "indoc", "itertools", "kasuari", "lru", "strum", "thiserror 2.0.18", "unicode-segmentation", "unicode-truncate", "unicode-width", ] [[package]] name = "ratatui-crossterm" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "577c9b9f652b4c121fb25c6a391dd06406d3b092ba68827e6d2f09550edc54b3" dependencies = [ "cfg-if", "crossterm", "instability", "ratatui-core", ] [[package]] name = "ratatui-macros" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7f1342a13e83e4bb9d0b793d0ea762be633f9582048c892ae9041ef39c936f4" dependencies = [ "ratatui-core", "ratatui-widgets", ] [[package]] name = "ratatui-termwiz" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f76fe0bd0ed4295f0321b1676732e2454024c15a35d01904ddb315afd3d545c" dependencies = [ "ratatui-core", "termwiz", ] [[package]] name = "ratatui-widgets" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7dbfa023cd4e604c2553483820c5fe8aa9d71a42eea5aa77c6e7f35756612db" dependencies = [ "bitflags 2.11.0", "hashbrown 0.16.1", "indoc", "instability", "itertools", "line-clipping", "ratatui-core", "strum", "time", "unicode-segmentation", "unicode-width", ] [[package]] name = "redox_syscall" version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ "bitflags 2.11.0", ] [[package]] name = "redox_users" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ "getrandom 0.2.17", "libredox", "thiserror 2.0.18", ] [[package]] name = "regex" version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "rustc-demangle" version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" [[package]] name = "rustc_version" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ "semver", ] [[package]] name = "rustix" version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ "bitflags 2.11.0", "errno", "libc", "linux-raw-sys", "windows-sys 0.61.2", ] [[package]] name = "rustversion" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "rustyline" version = "18.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a990b25f351b25139ddc7f21ee3f6f56f86d6846b74ac8fad3a719a287cd4a0" dependencies = [ "bitflags 2.11.0", "cfg-if", "clipboard-win", "home", "libc", "log", "memchr", "nix 0.31.2", "radix_trie", "unicode-segmentation", "unicode-width", "utf8parse", "windows-sys 0.61.2", ] [[package]] name = "ryu" version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "semver" version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" [[package]] name = "serde" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", "serde_derive", ] [[package]] name = "serde-value" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" dependencies = [ "ordered-float 2.10.1", "serde", ] [[package]] name = "serde_core" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", "syn 2.0.117", ] [[package]] name = "serde_json" version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", "memchr", "serde", "serde_core", "zmij", ] [[package]] name = "serde_yaml" version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ "indexmap", "itoa", "ryu", "serde", "unsafe-libyaml", ] [[package]] name = "sha2" version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures", "digest", ] [[package]] name = "shellexpand" version = "3.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32824fab5e16e6c4d86dc1ba84489390419a39f97699852b66480bb87d297ed8" dependencies = [ "dirs", ] [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook" version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" dependencies = [ "libc", "signal-hook-registry", ] [[package]] name = "signal-hook-mio" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc" dependencies = [ "libc", "mio", "signal-hook", ] [[package]] name = "signal-hook-registry" version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" dependencies = [ "errno", "libc", ] [[package]] name = "siphasher" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" [[package]] name = "slab" version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "strsim" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "strum" version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" dependencies = [ "strum_macros", ] [[package]] name = "strum_macros" version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" dependencies = [ "heck", "proc-macro2", "quote", "syn 2.0.117", ] [[package]] name = "syn" version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "syn" version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "task-hookrs" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f056b8dfe5514f40e6a5118b48a4584b9c091458387ef321d10e22fe19ae354e" dependencies = [ "chrono", "derive_builder", "log", "serde", "serde_json", "thiserror 1.0.69", "uuid", ] [[package]] name = "taskwarrior-tui" version = "0.27.0" dependencies = [ "anyhow", "better-panic", "chrono", "clap", "clap_complete", "crossterm", "dirs", "futures", "itertools", "kasuari", "log", "log4rs", "path-clean", "ratatui", "regex", "rustyline", "serde", "shellexpand", "shlex", "task-hookrs", "tokio", "unicode-segmentation", "unicode-truncate", "unicode-width", "uuid", "versions", ] [[package]] name = "terminfo" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4ea810f0692f9f51b382fff5893887bb4580f5fa246fde546e0b13e7fcee662" dependencies = [ "fnv", "nom 7.1.3", "phf", "phf_codegen", ] [[package]] name = "termios" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "411c5bf740737c7918b8b1fe232dca4dc9f8e754b8ad5e20966814001ed0ac6b" dependencies = [ "libc", ] [[package]] name = "termwiz" version = "0.23.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4676b37242ccbd1aabf56edb093a4827dc49086c0ffd764a5705899e0f35f8f7" dependencies = [ "anyhow", "base64", "bitflags 2.11.0", "fancy-regex", "filedescriptor", "finl_unicode", "fixedbitset", "hex", "lazy_static", "libc", "log", "memmem", "nix 0.29.0", "num-derive", "num-traits", "ordered-float 4.6.0", "pest", "pest_derive", "phf", "sha2", "signal-hook", "siphasher", "terminfo", "termios", "thiserror 1.0.69", "ucd-trie", "unicode-segmentation", "vtparse", "wezterm-bidi", "wezterm-blob-leases", "wezterm-color-types", "wezterm-dynamic", "wezterm-input-types", "winapi", ] [[package]] name = "thiserror" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ "thiserror-impl 1.0.69", ] [[package]] name = "thiserror" version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ "thiserror-impl 2.0.18", ] [[package]] name = "thiserror-impl" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", "syn 2.0.117", ] [[package]] name = "thiserror-impl" version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", "syn 2.0.117", ] [[package]] name = "thread-id" version = "5.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2010d27add3f3240c1fef7959f46c814487b216baee662af53be645ba7831c07" dependencies = [ "libc", "windows-sys 0.61.2", ] [[package]] name = "time" version = "0.3.47" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" dependencies = [ "deranged", "libc", "num-conv", "num_threads", "powerfmt", "serde_core", "time-core", ] [[package]] name = "time-core" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" [[package]] name = "tokio" version = "1.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bd1c4c0fc4a7ab90fc15ef6daaa3ec3b893f004f915f2392557ed23237820cd" dependencies = [ "bytes", "libc", "mio", "pin-project-lite", "signal-hook-registry", "tokio-macros", "windows-sys 0.61.2", ] [[package]] name = "tokio-macros" version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", "syn 2.0.117", ] [[package]] name = "typemap-ors" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a68c24b707f02dd18f1e4ccceb9d49f2058c2fb86384ef9972592904d7a28867" dependencies = [ "unsafe-any-ors", ] [[package]] name = "typenum" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "ucd-trie" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] name = "unicode-ident" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-segmentation" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" [[package]] name = "unicode-truncate" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16b380a1238663e5f8a691f9039c73e1cdae598a30e9855f541d29b08b53e9a5" dependencies = [ "itertools", "unicode-segmentation", "unicode-width", ] [[package]] name = "unicode-width" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" [[package]] name = "unicode-xid" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "unsafe-any-ors" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0a303d30665362d9680d7d91d78b23f5f899504d4f08b3c4cf08d055d87c0ad" dependencies = [ "destructure_traitobject", ] [[package]] name = "unsafe-libyaml" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" [[package]] name = "utf8parse" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" version = "1.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9" dependencies = [ "atomic", "getrandom 0.4.2", "js-sys", "serde_core", "wasm-bindgen", ] [[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "versions" version = "7.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80a7e511ce1795821207a837b7b1c8d8aca0c648810966ad200446ae58f6667f" dependencies = [ "itertools", "nom 8.0.0", ] [[package]] name = "vtparse" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d9b2acfb050df409c972a37d3b8e08cdea3bddb0c09db9d53137e504cfabed0" dependencies = [ "utf8parse", ] [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" version = "1.0.2+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" dependencies = [ "wit-bindgen", ] [[package]] name = "wasip3" version = "0.4.0+wasi-0.3.0-rc-2026-01-06" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" dependencies = [ "wit-bindgen", ] [[package]] name = "wasm-bindgen" version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0551fc1bb415591e3372d0bc4780db7e587d84e2a7e79da121051c5c4b89d0b0" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fbdf9a35adf44786aecd5ff89b4563a90325f9da0923236f6104e603c7e86be" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dca9693ef2bab6d4e6707234500350d8dad079eb508dca05530c85dc3a529ff2" dependencies = [ "bumpalo", "proc-macro2", "quote", "syn 2.0.117", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39129a682a6d2d841b6c429d0c51e5cb0ed1a03829d8b3d1e69a011e62cb3d3b" dependencies = [ "unicode-ident", ] [[package]] name = "wasm-encoder" version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" dependencies = [ "leb128fmt", "wasmparser", ] [[package]] name = "wasm-metadata" version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" dependencies = [ "anyhow", "indexmap", "wasm-encoder", "wasmparser", ] [[package]] name = "wasmparser" version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ "bitflags 2.11.0", "hashbrown 0.15.5", "indexmap", "semver", ] [[package]] name = "wezterm-bidi" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c0a6e355560527dd2d1cf7890652f4f09bb3433b6aadade4c9b5ed76de5f3ec" dependencies = [ "log", "wezterm-dynamic", ] [[package]] name = "wezterm-blob-leases" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "692daff6d93d94e29e4114544ef6d5c942a7ed998b37abdc19b17136ea428eb7" dependencies = [ "getrandom 0.3.4", "mac_address", "sha2", "thiserror 1.0.69", "uuid", ] [[package]] name = "wezterm-color-types" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7de81ef35c9010270d63772bebef2f2d6d1f2d20a983d27505ac850b8c4b4296" dependencies = [ "csscolorparser", "deltae", "lazy_static", "wezterm-dynamic", ] [[package]] name = "wezterm-dynamic" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f2ab60e120fd6eaa68d9567f3226e876684639d22a4219b313ff69ec0ccd5ac" dependencies = [ "log", "ordered-float 4.6.0", "strsim 0.11.1", "thiserror 1.0.69", "wezterm-dynamic-derive", ] [[package]] name = "wezterm-dynamic-derive" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46c0cf2d539c645b448eaffec9ec494b8b19bd5077d9e58cb1ae7efece8d575b" dependencies = [ "proc-macro2", "quote", "syn 1.0.109", ] [[package]] name = "wezterm-input-types" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7012add459f951456ec9d6c7e6fc340b1ce15d6fc9629f8c42853412c029e57e" dependencies = [ "bitflags 1.3.2", "euclid", "lazy_static", "serde", "wezterm-dynamic", ] [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-core" version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ "windows-implement", "windows-interface", "windows-link", "windows-result", "windows-strings", ] [[package]] name = "windows-implement" version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", "syn 2.0.117", ] [[package]] name = "windows-interface" version = "0.59.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", "syn 2.0.117", ] [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-result" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ "windows-link", ] [[package]] name = "windows-strings" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ "windows-link", ] [[package]] name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets", ] [[package]] name = "windows-sys" version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ "windows-link", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_gnullvm", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "wit-bindgen" version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" dependencies = [ "wit-bindgen-rust-macro", ] [[package]] name = "wit-bindgen-core" version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" dependencies = [ "anyhow", "heck", "wit-parser", ] [[package]] name = "wit-bindgen-rust" version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" dependencies = [ "anyhow", "heck", "indexmap", "prettyplease", "syn 2.0.117", "wasm-metadata", "wit-bindgen-core", "wit-component", ] [[package]] name = "wit-bindgen-rust-macro" version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" dependencies = [ "anyhow", "prettyplease", "proc-macro2", "quote", "syn 2.0.117", "wit-bindgen-core", "wit-bindgen-rust", ] [[package]] name = "wit-component" version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", "bitflags 2.11.0", "indexmap", "log", "serde", "serde_derive", "serde_json", "wasm-encoder", "wasm-metadata", "wasmparser", "wit-parser", ] [[package]] name = "wit-parser" version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" dependencies = [ "anyhow", "id-arena", "indexmap", "log", "semver", "serde", "serde_derive", "serde_json", "unicode-xid", "wasmparser", ] [[package]] name = "zerocopy" version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" dependencies = [ "proc-macro2", "quote", "syn 2.0.117", ] [[package]] name = "zmij" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" taskwarrior-tui-0.27.0/Cargo.toml0000644000000053031046102023000123120ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2024" name = "taskwarrior-tui" version = "0.27.0" authors = [ "Dheepak Krishnamurthy ", "Etherbloom ", ] build = "build.rs" autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "A Taskwarrior Terminal User Interface" homepage = "https://kdheepak.com/taskwarrior-tui" readme = "README.md" keywords = [ "taskwarrior", "tui", ] categories = ["command-line-utilities"] license = "MIT" repository = "https://github.com/kdheepak/taskwarrior-tui/" [package.metadata.rpm] package = "taskwarrior-tui" [package.metadata.rpm.cargo] buildflags = ["--release"] [package.metadata.rpm.targets.taskwarrior-tui] path = "/usr/bin/taskwarrior-tui" [[bin]] name = "taskwarrior-tui" path = "src/main.rs" [dependencies.anyhow] version = "1.0.102" [dependencies.better-panic] version = "0.3.0" [dependencies.chrono] version = "0.4.44" [dependencies.clap] version = "4.6.0" [dependencies.crossterm] version = "0.29.0" features = ["event-stream"] [dependencies.dirs] version = "6.0.0" [dependencies.futures] version = "0.3.32" [dependencies.itertools] version = "0.14.0" [dependencies.kasuari] version = "0.4.12" [dependencies.log] version = "0.4.29" [dependencies.log4rs] version = "1.4.0" [dependencies.path-clean] version = "1.0.1" [dependencies.ratatui] version = "0.30" [dependencies.regex] version = "1.12.3" [dependencies.rustyline] version = "18.0.0" features = ["with-file-history"] [dependencies.serde] version = "1.0.193" features = ["derive"] [dependencies.shellexpand] version = "3.1.2" [dependencies.shlex] version = "1.3.0" [dependencies.task-hookrs] version = "0.9.0" [dependencies.tokio] version = "1.50.0" features = [ "macros", "process", "rt-multi-thread", "sync", "time", ] [dependencies.unicode-segmentation] version = "1.13.2" [dependencies.unicode-truncate] version = "2.0.1" [dependencies.unicode-width] version = "0.2.2" [dependencies.uuid] version = "1.20.0" [dependencies.versions] version = "7.0.0" [build-dependencies.clap] version = "4.6.0" [build-dependencies.clap_complete] version = "4.6.0" [build-dependencies.shlex] version = "1.3.0" [profile.release] lto = "fat" debug = 1 incremental = true taskwarrior-tui-0.27.0/Cargo.toml.orig000064400000000000000000000027241046102023000157550ustar 00000000000000[package] name = "taskwarrior-tui" version = "0.27.0" license = "MIT" description = "A Taskwarrior Terminal User Interface" repository = "https://github.com/kdheepak/taskwarrior-tui/" homepage = "https://kdheepak.com/taskwarrior-tui" readme = "README.md" authors = ["Dheepak Krishnamurthy ", "Etherbloom "] edition = "2024" keywords = ["taskwarrior", "tui"] categories = ["command-line-utilities"] [dependencies] anyhow = "1.0.102" better-panic = "0.3.0" chrono = "0.4.44" clap = "4.6.0" crossterm = { version = "0.29.0", features = ["event-stream"] } dirs = "6.0.0" futures = "0.3.32" itertools = "0.14.0" kasuari = "0.4.12" log = "0.4.29" log4rs = "1.4.0" path-clean = "1.0.1" regex = "1.12.3" rustyline = { version = "18.0.0", features = ["with-file-history"] } serde = { version = "1.0.193", features = ["derive"] } shellexpand = "3.1.2" shlex = "1.3.0" task-hookrs = "0.9.0" tokio = { version = "1.50.0", features = ["macros", "process", "rt-multi-thread", "sync", "time"] } ratatui = "0.30" unicode-segmentation = "1.13.2" unicode-truncate = "2.0.1" unicode-width = "0.2.2" uuid = "1.20.0" versions = "7.0.0" [package.metadata.rpm] package = "taskwarrior-tui" [package.metadata.rpm.cargo] buildflags = ["--release"] [package.metadata.rpm.targets] taskwarrior-tui = { path = "/usr/bin/taskwarrior-tui" } [profile.release] debug = 1 incremental = true lto = "fat" [build-dependencies] clap = "4.6.0" clap_complete = "4.6.0" shlex = "1.3.0" taskwarrior-tui-0.27.0/LICENSE000064400000000000000000000020661046102023000140720ustar 00000000000000MIT License Copyright (c) 2020 Dheepak Krishnamurthy 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 Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 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 FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. taskwarrior-tui-0.27.0/README.md000064400000000000000000000150341046102023000143430ustar 00000000000000# `taskwarrior-tui` > [!IMPORTANT] > [`taskwarrior` v3.x](https://github.com/GothenburgBitFactory/taskwarrior/releases/tag/v3.0.0) may break `taskwarrior-tui` features in unexpected ways. Please file a bug report if you encounter a bug. > > taskwarrior-tui [v0.25.4](https://github.com/kdheepak/taskwarrior-tui/releases/tag/v0.25.4) is the last version supporting taskwarrior v2.x as backend. [![CI](https://github.com/kdheepak/taskwarrior-tui/workflows/CI/badge.svg)](https://github.com/kdheepak/taskwarrior-tui/actions?query=workflow%3ACI) [![License](https://img.shields.io/github/license/kdheepak/taskwarrior-tui)](https://github.com/kdheepak/taskwarrior-tui/blob/main/LICENSE) [![Release](https://img.shields.io/github/v/release/kdheepak/taskwarrior-tui)](https://github.com/kdheepak/taskwarrior-tui/releases/latest) [![Platform](https://img.shields.io/static/v1?label=platform&message=linux-64%20|%20osx-64%20|%20win-32%20|%20win-64&color=lightgrey)](https://github.com/kdheepak/taskwarrior-tui/releases/latest) [![Rust](https://img.shields.io/github/languages/top/kdheepak/taskwarrior-tui)](https://github.com/kdheepak/taskwarrior-tui) [![Coverage](https://img.shields.io/coveralls/github/kdheepak/taskwarrior-tui)](https://coveralls.io/github/kdheepak/taskwarrior-tui) [![Docs](https://img.shields.io/badge/taskwarrior--tui-docs-red)](https://kdheepak.com/taskwarrior-tui) [![Downloads](https://img.shields.io/github/downloads/kdheepak/taskwarrior-tui/total)](https://github.com/kdheepak/taskwarrior-tui/releases/latest) A Terminal User Interface (TUI) for [Taskwarrior](https://taskwarrior.org/) that you didn't know you wanted. ### Features - vim-like navigation - live filter updates - add, delete, complete, log tasks - multiple selection - tab completion - colors based on taskwarrior https://github.com/user-attachments/assets/a3c4f79b-3967-4904-b614-8bbd500c54c2 ### Documentation
See https://kdheepak.com/taskwarrior-tui for documentation.

See https://kdheepak.com/taskwarrior-tui/installation/ for installation instructions for your platform.

See https://kdheepak.com/taskwarrior-tui/quick_start/ to get started.

See https://kdheepak.com/taskwarrior-tui/troubleshooting/ for logs, timing checks, and debugging steps.

See https://kdheepak.com/taskwarrior-tui/configuration/keys or https://kdheepak.com/taskwarrior-tui/configuration/colors/ for customization options.

### Installation Unless otherwise specified, you will need to install the latest version of `taskwarrior` first. See for more information. Pre-compiled releases are available on the [GitHub repo](https://github.com/kdheepak/taskwarrior-tui): 1. Download the tar.gz file for your OS from [the latest release](https://github.com/kdheepak/taskwarrior-tui/releases/latest). 2. Unzip the tar.gz file 3. Run with `./taskwarrior-tui`. See on instructions for using package managers on various platforms. If you are compiling from source, you'll need the most recent stable rust compiler. ### Configuration `taskwarrior-tui` uses `taskwarrior`'s `.taskrc` for configuration. See the documentation for more information: - - -
Here is an example `.taskrc` ```.taskrc ### taskwarrior configuration options # taskwarrior's configuration data.location=.task verbose=affected,blank,context,edit,header,footnote,label,new-id,project,special,sync,recur uda.priority.values=H,M,,L color.alternate= # taskwarrior-tui reads color attributes from the following to display the same colors of tasks as the CLI color.tagged=black on rgb444 # Remove age, tags from task next report. # taskwarrior-tui reads the labels and columns from these options to display tasks the same way taskwarrior does report.next.labels=ID,Active,Age,Deps,P,Project,Tag,Recur,S,Due,Until,Description,Urg report.next.columns=id,start.age,entry.age,depends,priority,project,tags,recur,scheduled.countdown,due.relative,until.remaining,description.truncated_count,urgency report.next.filter=(status:pending or status:waiting) page:limit ### taskwarrior-tui configuration options uda.taskwarrior-tui.keyconfig.done=x uda.taskwarrior-tui.keyconfig.delete=d uda.taskwarrior-tui.task-report.use-alternate-style=false uda.taskwarrior-tui.shortcuts.1=~/local/bin/task-sync.sh uda.taskwarrior-tui.report.next.filter=(status:pending or status:waiting) ```
### References / Resources If you like `taskwarrior-tui`, please consider donating to - [`kdheepak`](https://github.com/sponsors/kdheepak) - [`@GothenburgBitFactory`](https://github.com/sponsors/GothenburgBitFactory) - and/or a charity of your choice.
Additional resources
taskwarrior-tui-0.27.0/build.rs000064400000000000000000000017301046102023000145270ustar 00000000000000#![allow(dead_code)] use std::process::{Command, Output}; use clap_complete::{ generate_to, shells::{Bash, Fish, PowerShell, Zsh}, }; include!("src/cli.rs"); fn run_pandoc() -> Result { let mut cmd = Command::new("pandoc"); if let Some(args) = shlex::split("--standalone --to=man packaging/man/taskwarrior-tui.1.md -o packaging/man/taskwarrior-tui.1") { for arg in args { cmd.arg(arg); } } cmd.output() } fn main() { let mut app = generate_cli_app(); let name = app.get_name().to_string(); let outdir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("completions/"); dbg!(&outdir); generate_to(Bash, &mut app, &name, &outdir).unwrap(); generate_to(Zsh, &mut app, &name, &outdir).unwrap(); generate_to(Fish, &mut app, &name, &outdir).unwrap(); generate_to(PowerShell, &mut app, &name, &outdir).unwrap(); if run_pandoc().is_err() { dbg!("Unable to run pandoc to generate man page documentation"); } } taskwarrior-tui-0.27.0/completions/_taskwarrior-tui000064400000000000000000000026041046102023000206500ustar 00000000000000#compdef taskwarrior-tui autoload -U is-at-least _taskwarrior-tui() { typeset -A opt_args typeset -a _arguments_options local ret=1 if is-at-least 5.2; then _arguments_options=(-s -S -C) else _arguments_options=(-s -C) fi local context curcontext="$curcontext" state line _arguments "${_arguments_options[@]}" : \ '-d+[Sets the data folder for taskwarrior-tui]:FOLDER:_default' \ '--data=[Sets the data folder for taskwarrior-tui]:FOLDER:_default' \ '-c+[Sets the config folder for taskwarrior-tui (currently not used)]:FOLDER:_default' \ '--config=[Sets the config folder for taskwarrior-tui (currently not used)]:FOLDER:_default' \ '--taskdata=[Sets the .task folder using the TASKDATA environment variable for taskwarrior]:FOLDER:_default' \ '--taskrc=[Sets the .taskrc file using the TASKRC environment variable for taskwarrior]:FILE:_default' \ '-r+[Sets default report]:STRING:_default' \ '--report=[Sets default report]:STRING:_default' \ '-h[Print help]' \ '--help[Print help]' \ '-V[Print version]' \ '--version[Print version]' \ && ret=0 } (( $+functions[_taskwarrior-tui_commands] )) || _taskwarrior-tui_commands() { local commands; commands=() _describe -t commands 'taskwarrior-tui commands' commands "$@" } if [ "$funcstack[1]" = "_taskwarrior-tui" ]; then _taskwarrior-tui "$@" else compdef _taskwarrior-tui taskwarrior-tui fi taskwarrior-tui-0.27.0/completions/_taskwarrior-tui.ps1000064400000000000000000000050541046102023000213540ustar 00000000000000 using namespace System.Management.Automation using namespace System.Management.Automation.Language Register-ArgumentCompleter -Native -CommandName 'taskwarrior-tui' -ScriptBlock { param($wordToComplete, $commandAst, $cursorPosition) $commandElements = $commandAst.CommandElements $command = @( 'taskwarrior-tui' for ($i = 1; $i -lt $commandElements.Count; $i++) { $element = $commandElements[$i] if ($element -isnot [StringConstantExpressionAst] -or $element.StringConstantType -ne [StringConstantType]::BareWord -or $element.Value.StartsWith('-') -or $element.Value -eq $wordToComplete) { break } $element.Value }) -join ';' $completions = @(switch ($command) { 'taskwarrior-tui' { [CompletionResult]::new('-d', '-d', [CompletionResultType]::ParameterName, 'Sets the data folder for taskwarrior-tui') [CompletionResult]::new('--data', '--data', [CompletionResultType]::ParameterName, 'Sets the data folder for taskwarrior-tui') [CompletionResult]::new('-c', '-c', [CompletionResultType]::ParameterName, 'Sets the config folder for taskwarrior-tui (currently not used)') [CompletionResult]::new('--config', '--config', [CompletionResultType]::ParameterName, 'Sets the config folder for taskwarrior-tui (currently not used)') [CompletionResult]::new('--taskdata', '--taskdata', [CompletionResultType]::ParameterName, 'Sets the .task folder using the TASKDATA environment variable for taskwarrior') [CompletionResult]::new('--taskrc', '--taskrc', [CompletionResultType]::ParameterName, 'Sets the .taskrc file using the TASKRC environment variable for taskwarrior') [CompletionResult]::new('-r', '-r', [CompletionResultType]::ParameterName, 'Sets default report') [CompletionResult]::new('--report', '--report', [CompletionResultType]::ParameterName, 'Sets default report') [CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'Print help') [CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'Print help') [CompletionResult]::new('-V', '-V ', [CompletionResultType]::ParameterName, 'Print version') [CompletionResult]::new('--version', '--version', [CompletionResultType]::ParameterName, 'Print version') break } }) $completions.Where{ $_.CompletionText -like "$wordToComplete*" } | Sort-Object -Property ListItemText } taskwarrior-tui-0.27.0/completions/taskwarrior-tui.bash000064400000000000000000000044021046102023000214230ustar 00000000000000_taskwarrior-tui() { local i cur prev opts cmd COMPREPLY=() if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then cur="$2" else cur="${COMP_WORDS[COMP_CWORD]}" fi prev="$3" cmd="" opts="" for i in "${COMP_WORDS[@]:0:COMP_CWORD}" do case "${cmd},${i}" in ",$1") cmd="taskwarrior__tui" ;; *) ;; esac done case "${cmd}" in taskwarrior__tui) opts="-d -c -r -h -V --data --config --taskdata --taskrc --report --help --version" if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 fi case "${prev}" in --data) COMPREPLY=($(compgen -f "${cur}")) return 0 ;; -d) COMPREPLY=($(compgen -f "${cur}")) return 0 ;; --config) COMPREPLY=($(compgen -f "${cur}")) return 0 ;; -c) COMPREPLY=($(compgen -f "${cur}")) return 0 ;; --taskdata) COMPREPLY=($(compgen -f "${cur}")) return 0 ;; --taskrc) COMPREPLY=($(compgen -f "${cur}")) return 0 ;; --report) COMPREPLY=($(compgen -f "${cur}")) return 0 ;; -r) COMPREPLY=($(compgen -f "${cur}")) return 0 ;; *) COMPREPLY=() ;; esac COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 ;; esac } if [[ "${BASH_VERSINFO[0]}" -eq 4 && "${BASH_VERSINFO[1]}" -ge 4 || "${BASH_VERSINFO[0]}" -gt 4 ]]; then complete -F _taskwarrior-tui -o nosort -o bashdefault -o default taskwarrior-tui else complete -F _taskwarrior-tui -o bashdefault -o default taskwarrior-tui fi taskwarrior-tui-0.27.0/completions/taskwarrior-tui.fish000064400000000000000000000012041046102023000214340ustar 00000000000000complete -c taskwarrior-tui -s d -l data -d 'Sets the data folder for taskwarrior-tui' -r complete -c taskwarrior-tui -s c -l config -d 'Sets the config folder for taskwarrior-tui (currently not used)' -r complete -c taskwarrior-tui -l taskdata -d 'Sets the .task folder using the TASKDATA environment variable for taskwarrior' -r complete -c taskwarrior-tui -l taskrc -d 'Sets the .taskrc file using the TASKRC environment variable for taskwarrior' -r complete -c taskwarrior-tui -s r -l report -d 'Sets default report' -r complete -c taskwarrior-tui -s h -l help -d 'Print help' complete -c taskwarrior-tui -s V -l version -d 'Print version' taskwarrior-tui-0.27.0/docs/astro.config.mjs000064400000000000000000000021771046102023000171270ustar 00000000000000import { defineConfig } from "astro/config"; import starlight from "@astrojs/starlight"; export default defineConfig({ site: "https://kdheepak.com", base: "/taskwarrior-tui", trailingSlash: "always", outDir: "../site", integrations: [ starlight({ title: "taskwarrior-tui", description: "A terminal user interface for Taskwarrior with keyboard-first workflows.", favicon: "/favicon.svg", disable404Route: true, editLink: { baseUrl: "https://github.com/kdheepak/taskwarrior-tui/edit/main/docs/", }, social: [ { icon: "github", label: "GitHub", href: "https://github.com/kdheepak/taskwarrior-tui", }, ], sidebar: [ { label: "Getting Started", items: ["installation", "quick_start", "keybindings", "troubleshooting", "faqs"], }, { label: "Configuration", items: ["configuration/keys", "configuration/colors", "configuration/advanced"], }, { label: "Developer Guide", items: ["developer/guide"], }, ], }), ], }); taskwarrior-tui-0.27.0/docs/package-lock.json000064400000000000000000007233631046102023000172430ustar 00000000000000{ "name": "taskwarrior-tui-docs", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "taskwarrior-tui-docs", "devDependencies": { "@astrojs/starlight": "^0.38.2", "astro": "^6.1.0", "zod": "^3.25.76" }, "engines": { "node": ">=22.11.0" } }, "node_modules/@astrojs/compiler": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@astrojs/compiler/-/compiler-3.0.1.tgz", "integrity": "sha512-z97oYbdebO5aoWzuJ/8q5hLK232+17KcLZ7cJ8BCWk6+qNzVxn/gftC0KzMBUTD8WAaBkPpNSQK6PXLnNrZ0CA==", "dev": true, "license": "MIT" }, "node_modules/@astrojs/internal-helpers": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/@astrojs/internal-helpers/-/internal-helpers-0.8.0.tgz", "integrity": "sha512-J56GrhEiV+4dmrGLPNOl2pZjpHXAndWVyiVDYGDuw6MWKpBSEMLdFxHzeM/6sqaknw9M+HFfHZAcvi3OfT3D/w==", "dev": true, "license": "MIT", "dependencies": { "picomatch": "^4.0.3" } }, "node_modules/@astrojs/markdown-remark": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/@astrojs/markdown-remark/-/markdown-remark-7.1.0.tgz", "integrity": "sha512-P+HnCsu2js3BoTc8kFmu+E9gOcFeMdPris75g+Zl4sY8+bBRbSQV6xzcBDbZ27eE7yBGEGQoqjpChx+KJYIPYQ==", "dev": true, "license": "MIT", "dependencies": { "@astrojs/internal-helpers": "0.8.0", "@astrojs/prism": "4.0.1", "github-slugger": "^2.0.0", "hast-util-from-html": "^2.0.3", "hast-util-to-text": "^4.0.2", "js-yaml": "^4.1.1", "mdast-util-definitions": "^6.0.0", "rehype-raw": "^7.0.0", "rehype-stringify": "^10.0.1", "remark-gfm": "^4.0.1", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.2", "remark-smartypants": "^3.0.2", "retext-smartypants": "^6.2.0", "shiki": "^4.0.0", "smol-toml": "^1.6.0", "unified": "^11.0.5", "unist-util-remove-position": "^5.0.0", "unist-util-visit": "^5.1.0", "unist-util-visit-parents": "^6.0.2", "vfile": "^6.0.3" } }, "node_modules/@astrojs/markdown-remark/node_modules/@shikijs/core": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-4.0.2.tgz", "integrity": "sha512-hxT0YF4ExEqB8G/qFdtJvpmHXBYJ2lWW7qTHDarVkIudPFE6iCIrqdgWxGn5s+ppkGXI0aEGlibI0PAyzP3zlw==", "dev": true, "license": "MIT", "dependencies": { "@shikijs/primitive": "4.0.2", "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" }, "engines": { "node": ">=20" } }, "node_modules/@astrojs/markdown-remark/node_modules/@shikijs/engine-javascript": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-4.0.2.tgz", "integrity": "sha512-7PW0Nm49DcoUIQEXlJhNNBHyoGMjalRETTCcjMqEaMoJRLljy1Bi/EGV3/qLBgLKQejdspiiYuHGQW6dX94Nag==", "dev": true, "license": "MIT", "dependencies": { "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.4" }, "engines": { "node": ">=20" } }, "node_modules/@astrojs/markdown-remark/node_modules/@shikijs/engine-oniguruma": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-4.0.2.tgz", "integrity": "sha512-UpCB9Y2sUKlS9z8juFSKz7ZtysmeXCgnRF0dlhXBkmQnek7lAToPte8DkxmEYGNTMii72zU/lyXiCB6StuZeJg==", "dev": true, "license": "MIT", "dependencies": { "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2" }, "engines": { "node": ">=20" } }, "node_modules/@astrojs/markdown-remark/node_modules/@shikijs/langs": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-4.0.2.tgz", "integrity": "sha512-KaXby5dvoeuZzN0rYQiPMjFoUrz4hgwIE+D6Du9owcHcl6/g16/yT5BQxSW5cGt2MZBz6Hl0YuRqf12omRfUUg==", "dev": true, "license": "MIT", "dependencies": { "@shikijs/types": "4.0.2" }, "engines": { "node": ">=20" } }, "node_modules/@astrojs/markdown-remark/node_modules/@shikijs/themes": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-4.0.2.tgz", "integrity": "sha512-mjCafwt8lJJaVSsQvNVrJumbnnj1RI8jbUKrPKgE6E3OvQKxnuRoBaYC51H4IGHePsGN/QtALglWBU7DoKDFnA==", "dev": true, "license": "MIT", "dependencies": { "@shikijs/types": "4.0.2" }, "engines": { "node": ">=20" } }, "node_modules/@astrojs/markdown-remark/node_modules/@shikijs/types": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-4.0.2.tgz", "integrity": "sha512-qzbeRooUTPnLE+sHD/Z8DStmaDgnbbc/pMrU203950aRqjX/6AFHeDYT+j00y2lPdz0ywJKx7o/7qnqTivtlXg==", "dev": true, "license": "MIT", "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" }, "engines": { "node": ">=20" } }, "node_modules/@astrojs/markdown-remark/node_modules/shiki": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/shiki/-/shiki-4.0.2.tgz", "integrity": "sha512-eAVKTMedR5ckPo4xne/PjYQYrU3qx78gtJZ+sHlXEg5IHhhoQhMfZVzetTYuaJS0L2Ef3AcCRzCHV8T0WI6nIQ==", "dev": true, "license": "MIT", "dependencies": { "@shikijs/core": "4.0.2", "@shikijs/engine-javascript": "4.0.2", "@shikijs/engine-oniguruma": "4.0.2", "@shikijs/langs": "4.0.2", "@shikijs/themes": "4.0.2", "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" }, "engines": { "node": ">=20" } }, "node_modules/@astrojs/mdx": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/@astrojs/mdx/-/mdx-5.0.3.tgz", "integrity": "sha512-zv/OlM5sZZvyjHqJjR3FjJvoCgbxdqj3t4jO/gSEUNcck3BjdtMgNQw8UgPfAGe4yySdG4vjZ3OC5wUxhu7ckg==", "dev": true, "license": "MIT", "dependencies": { "@astrojs/markdown-remark": "7.1.0", "@mdx-js/mdx": "^3.1.1", "acorn": "^8.16.0", "es-module-lexer": "^2.0.0", "estree-util-visit": "^2.0.0", "hast-util-to-html": "^9.0.5", "piccolore": "^0.1.3", "rehype-raw": "^7.0.0", "remark-gfm": "^4.0.1", "remark-smartypants": "^3.0.2", "source-map": "^0.7.6", "unist-util-visit": "^5.1.0", "vfile": "^6.0.3" }, "engines": { "node": ">=22.12.0" }, "peerDependencies": { "astro": "^6.0.0" } }, "node_modules/@astrojs/prism": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@astrojs/prism/-/prism-4.0.1.tgz", "integrity": "sha512-nksZQVjlferuWzhPsBpQ1JE5XuKAf1id1/9Hj4a9KG4+ofrlzxUUwX4YGQF/SuDiuiGKEnzopGOt38F3AnVWsQ==", "dev": true, "license": "MIT", "dependencies": { "prismjs": "^1.30.0" }, "engines": { "node": ">=22.12.0" } }, "node_modules/@astrojs/sitemap": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/@astrojs/sitemap/-/sitemap-3.7.2.tgz", "integrity": "sha512-PqkzkcZTb5ICiyIR8VoKbIAP/laNRXi5tw616N1Ckk+40oNB8Can1AzVV56lrbC5GKSZFCyJYUVYqVivMisvpA==", "dev": true, "license": "MIT", "dependencies": { "sitemap": "^9.0.0", "stream-replace-string": "^2.0.0", "zod": "^4.3.6" } }, "node_modules/@astrojs/sitemap/node_modules/zod": { "version": "4.3.6", "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" } }, "node_modules/@astrojs/starlight": { "version": "0.38.2", "resolved": "https://registry.npmjs.org/@astrojs/starlight/-/starlight-0.38.2.tgz", "integrity": "sha512-7AsrvG4EsXUmJT5uqiXJN4oZqKaY0wc/Ip7C6/zGnShHRVoTAA4jxeYIZ3wqbqA6zv4cnp9qk31vB2m2dUcmfg==", "dev": true, "license": "MIT", "dependencies": { "@astrojs/markdown-remark": "^7.0.0", "@astrojs/mdx": "^5.0.0", "@astrojs/sitemap": "^3.7.1", "@pagefind/default-ui": "^1.3.0", "@types/hast": "^3.0.4", "@types/js-yaml": "^4.0.9", "@types/mdast": "^4.0.4", "astro-expressive-code": "^0.41.6", "bcp-47": "^2.1.0", "hast-util-from-html": "^2.0.1", "hast-util-select": "^6.0.2", "hast-util-to-string": "^3.0.0", "hastscript": "^9.0.0", "i18next": "^23.11.5", "js-yaml": "^4.1.0", "klona": "^2.0.6", "magic-string": "^0.30.17", "mdast-util-directive": "^3.0.0", "mdast-util-to-markdown": "^2.1.0", "mdast-util-to-string": "^4.0.0", "pagefind": "^1.3.0", "rehype": "^13.0.1", "rehype-format": "^5.0.0", "remark-directive": "^3.0.0", "ultrahtml": "^1.6.0", "unified": "^11.0.5", "unist-util-visit": "^5.0.0", "vfile": "^6.0.2" }, "peerDependencies": { "astro": "^6.0.0" } }, "node_modules/@astrojs/telemetry": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/@astrojs/telemetry/-/telemetry-3.3.0.tgz", "integrity": "sha512-UFBgfeldP06qu6khs/yY+q1cDAaArM2/7AEIqQ9Cuvf7B1hNLq0xDrZkct+QoIGyjq56y8IaE2I3CTvG99mlhQ==", "dev": true, "license": "MIT", "dependencies": { "ci-info": "^4.2.0", "debug": "^4.4.0", "dlv": "^1.1.3", "dset": "^3.1.4", "is-docker": "^3.0.0", "is-wsl": "^3.1.0", "which-pm-runs": "^1.1.0" }, "engines": { "node": "18.20.8 || ^20.3.0 || >=22.0.0" } }, "node_modules/@babel/helper-string-parser": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { "version": "7.29.2", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.29.0" }, "bin": { "parser": "bin/babel-parser.js" }, "engines": { "node": ">=6.0.0" } }, "node_modules/@babel/runtime": { "version": "7.29.2", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/types": { "version": "7.29.0", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@capsizecss/unpack": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@capsizecss/unpack/-/unpack-4.0.0.tgz", "integrity": "sha512-VERIM64vtTP1C4mxQ5thVT9fK0apjPFobqybMtA1UdUujWka24ERHbRHFGmpbbhp73MhV+KSsHQH9C6uOTdEQA==", "dev": true, "license": "MIT", "dependencies": { "fontkitten": "^1.0.0" }, "engines": { "node": ">=18" } }, "node_modules/@clack/core": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@clack/core/-/core-1.1.0.tgz", "integrity": "sha512-SVcm4Dqm2ukn64/8Gub2wnlA5nS2iWJyCkdNHcvNHPIeBTGojpdJ+9cZKwLfmqy7irD4N5qLteSilJlE0WLAtA==", "dev": true, "license": "MIT", "dependencies": { "sisteransi": "^1.0.5" } }, "node_modules/@clack/prompts": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-1.1.0.tgz", "integrity": "sha512-pkqbPGtohJAvm4Dphs2M8xE29ggupihHdy1x84HNojZuMtFsHiUlRvqD24tM2+XmI+61LlfNceM3Wr7U5QES5g==", "dev": true, "license": "MIT", "dependencies": { "@clack/core": "1.1.0", "sisteransi": "^1.0.5" } }, "node_modules/@ctrl/tinycolor": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-4.2.0.tgz", "integrity": "sha512-kzyuwOAQnXJNLS9PSyrk0CWk35nWJW/zl/6KvnTBMFK65gm7U1/Z5BqjxeapjZCIhQcM/DsrEmcbRwDyXyXK4A==", "dev": true, "license": "MIT", "engines": { "node": ">=14" } }, "node_modules/@emnapi/runtime": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.1.tgz", "integrity": "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==", "dev": true, "license": "MIT", "optional": true, "dependencies": { "tslib": "^2.4.0" } }, "node_modules/@esbuild/aix-ppc64": { "version": "0.27.4", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz", "integrity": "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==", "cpu": [ "ppc64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "aix" ], "engines": { "node": ">=18" } }, "node_modules/@esbuild/android-arm": { "version": "0.27.4", "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.4.tgz", "integrity": "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==", "cpu": [ "arm" ], "dev": true, "license": "MIT", "optional": true, "os": [ "android" ], "engines": { "node": ">=18" } }, "node_modules/@esbuild/android-arm64": { "version": "0.27.4", "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.4.tgz", "integrity": "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==", "cpu": [ "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "android" ], "engines": { "node": ">=18" } }, "node_modules/@esbuild/android-x64": { "version": "0.27.4", "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.4.tgz", "integrity": "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==", "cpu": [ "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "android" ], "engines": { "node": ">=18" } }, "node_modules/@esbuild/darwin-arm64": { "version": "0.27.4", "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.4.tgz", "integrity": "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==", "cpu": [ "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "darwin" ], "engines": { "node": ">=18" } }, "node_modules/@esbuild/darwin-x64": { "version": "0.27.4", "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.4.tgz", "integrity": "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==", "cpu": [ "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "darwin" ], "engines": { "node": ">=18" } }, "node_modules/@esbuild/freebsd-arm64": { "version": "0.27.4", "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.4.tgz", "integrity": "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==", "cpu": [ "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "freebsd" ], "engines": { "node": ">=18" } }, "node_modules/@esbuild/freebsd-x64": { "version": "0.27.4", "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.4.tgz", "integrity": "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==", "cpu": [ "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "freebsd" ], "engines": { "node": ">=18" } }, "node_modules/@esbuild/linux-arm": { "version": "0.27.4", "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.4.tgz", "integrity": "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==", "cpu": [ "arm" ], "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { "node": ">=18" } }, "node_modules/@esbuild/linux-arm64": { "version": "0.27.4", "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.4.tgz", "integrity": "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==", "cpu": [ "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { "node": ">=18" } }, "node_modules/@esbuild/linux-ia32": { "version": "0.27.4", "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.4.tgz", "integrity": "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==", "cpu": [ "ia32" ], "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { "node": ">=18" } }, "node_modules/@esbuild/linux-loong64": { "version": "0.27.4", "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.4.tgz", "integrity": "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==", "cpu": [ "loong64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { "node": ">=18" } }, "node_modules/@esbuild/linux-mips64el": { "version": "0.27.4", "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.4.tgz", "integrity": "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==", "cpu": [ "mips64el" ], "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { "node": ">=18" } }, "node_modules/@esbuild/linux-ppc64": { "version": "0.27.4", "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.4.tgz", "integrity": "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==", "cpu": [ "ppc64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { "node": ">=18" } }, "node_modules/@esbuild/linux-riscv64": { "version": "0.27.4", "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.4.tgz", "integrity": "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==", "cpu": [ "riscv64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { "node": ">=18" } }, "node_modules/@esbuild/linux-s390x": { "version": "0.27.4", "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.4.tgz", "integrity": "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==", "cpu": [ "s390x" ], "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { "node": ">=18" } }, "node_modules/@esbuild/linux-x64": { "version": "0.27.4", "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.4.tgz", "integrity": "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==", "cpu": [ "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { "node": ">=18" } }, "node_modules/@esbuild/netbsd-arm64": { "version": "0.27.4", "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.4.tgz", "integrity": "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==", "cpu": [ "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "netbsd" ], "engines": { "node": ">=18" } }, "node_modules/@esbuild/netbsd-x64": { "version": "0.27.4", "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.4.tgz", "integrity": "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==", "cpu": [ "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "netbsd" ], "engines": { "node": ">=18" } }, "node_modules/@esbuild/openbsd-arm64": { "version": "0.27.4", "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.4.tgz", "integrity": "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==", "cpu": [ "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "openbsd" ], "engines": { "node": ">=18" } }, "node_modules/@esbuild/openbsd-x64": { "version": "0.27.4", "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.4.tgz", "integrity": "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==", "cpu": [ "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "openbsd" ], "engines": { "node": ">=18" } }, "node_modules/@esbuild/openharmony-arm64": { "version": "0.27.4", "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.4.tgz", "integrity": "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg==", "cpu": [ "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "openharmony" ], "engines": { "node": ">=18" } }, "node_modules/@esbuild/sunos-x64": { "version": "0.27.4", "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.4.tgz", "integrity": "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==", "cpu": [ "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "sunos" ], "engines": { "node": ">=18" } }, "node_modules/@esbuild/win32-arm64": { "version": "0.27.4", "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.4.tgz", "integrity": "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==", "cpu": [ "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "win32" ], "engines": { "node": ">=18" } }, "node_modules/@esbuild/win32-ia32": { "version": "0.27.4", "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.4.tgz", "integrity": "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==", "cpu": [ "ia32" ], "dev": true, "license": "MIT", "optional": true, "os": [ "win32" ], "engines": { "node": ">=18" } }, "node_modules/@esbuild/win32-x64": { "version": "0.27.4", "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.4.tgz", "integrity": "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==", "cpu": [ "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "win32" ], "engines": { "node": ">=18" } }, "node_modules/@expressive-code/core": { "version": "0.41.7", "resolved": "https://registry.npmjs.org/@expressive-code/core/-/core-0.41.7.tgz", "integrity": "sha512-ck92uZYZ9Wba2zxkiZLsZGi9N54pMSAVdrI9uW3Oo9AtLglD5RmrdTwbYPCT2S/jC36JGB2i+pnQtBm/Ib2+dg==", "dev": true, "license": "MIT", "dependencies": { "@ctrl/tinycolor": "^4.0.4", "hast-util-select": "^6.0.2", "hast-util-to-html": "^9.0.1", "hast-util-to-text": "^4.0.1", "hastscript": "^9.0.0", "postcss": "^8.4.38", "postcss-nested": "^6.0.1", "unist-util-visit": "^5.0.0", "unist-util-visit-parents": "^6.0.1" } }, "node_modules/@expressive-code/plugin-frames": { "version": "0.41.7", "resolved": "https://registry.npmjs.org/@expressive-code/plugin-frames/-/plugin-frames-0.41.7.tgz", "integrity": "sha512-diKtxjQw/979cTglRFaMCY/sR6hWF0kSMg8jsKLXaZBSfGS0I/Hoe7Qds3vVEgeoW+GHHQzMcwvgx/MOIXhrTA==", "dev": true, "license": "MIT", "dependencies": { "@expressive-code/core": "^0.41.7" } }, "node_modules/@expressive-code/plugin-shiki": { "version": "0.41.7", "resolved": "https://registry.npmjs.org/@expressive-code/plugin-shiki/-/plugin-shiki-0.41.7.tgz", "integrity": "sha512-DL605bLrUOgqTdZ0Ot5MlTaWzppRkzzqzeGEu7ODnHF39IkEBbFdsC7pbl3LbUQ1DFtnfx6rD54k/cdofbW6KQ==", "dev": true, "license": "MIT", "dependencies": { "@expressive-code/core": "^0.41.7", "shiki": "^3.2.2" } }, "node_modules/@expressive-code/plugin-text-markers": { "version": "0.41.7", "resolved": "https://registry.npmjs.org/@expressive-code/plugin-text-markers/-/plugin-text-markers-0.41.7.tgz", "integrity": "sha512-Ewpwuc5t6eFdZmWlFyeuy3e1PTQC0jFvw2Q+2bpcWXbOZhPLsT7+h8lsSIJxb5mS7wZko7cKyQ2RLYDyK6Fpmw==", "dev": true, "license": "MIT", "dependencies": { "@expressive-code/core": "^0.41.7" } }, "node_modules/@img/colour": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", "dev": true, "license": "MIT", "optional": true, "engines": { "node": ">=18" } }, "node_modules/@img/sharp-darwin-arm64": { "version": "0.34.5", "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", "cpu": [ "arm64" ], "dev": true, "license": "Apache-2.0", "optional": true, "os": [ "darwin" ], "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.2.4" } }, "node_modules/@img/sharp-darwin-x64": { "version": "0.34.5", "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", "cpu": [ "x64" ], "dev": true, "license": "Apache-2.0", "optional": true, "os": [ "darwin" ], "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.2.4" } }, "node_modules/@img/sharp-libvips-darwin-arm64": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", "cpu": [ "arm64" ], "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ "darwin" ], "funding": { "url": "https://opencollective.com/libvips" } }, "node_modules/@img/sharp-libvips-darwin-x64": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", "cpu": [ "x64" ], "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ "darwin" ], "funding": { "url": "https://opencollective.com/libvips" } }, "node_modules/@img/sharp-libvips-linux-arm": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", "cpu": [ "arm" ], "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" ], "funding": { "url": "https://opencollective.com/libvips" } }, "node_modules/@img/sharp-libvips-linux-arm64": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", "cpu": [ "arm64" ], "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" ], "funding": { "url": "https://opencollective.com/libvips" } }, "node_modules/@img/sharp-libvips-linux-ppc64": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", "cpu": [ "ppc64" ], "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" ], "funding": { "url": "https://opencollective.com/libvips" } }, "node_modules/@img/sharp-libvips-linux-riscv64": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", "cpu": [ "riscv64" ], "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" ], "funding": { "url": "https://opencollective.com/libvips" } }, "node_modules/@img/sharp-libvips-linux-s390x": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", "cpu": [ "s390x" ], "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" ], "funding": { "url": "https://opencollective.com/libvips" } }, "node_modules/@img/sharp-libvips-linux-x64": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", "cpu": [ "x64" ], "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" ], "funding": { "url": "https://opencollective.com/libvips" } }, "node_modules/@img/sharp-libvips-linuxmusl-arm64": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", "cpu": [ "arm64" ], "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" ], "funding": { "url": "https://opencollective.com/libvips" } }, "node_modules/@img/sharp-libvips-linuxmusl-x64": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", "cpu": [ "x64" ], "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" ], "funding": { "url": "https://opencollective.com/libvips" } }, "node_modules/@img/sharp-linux-arm": { "version": "0.34.5", "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", "cpu": [ "arm" ], "dev": true, "license": "Apache-2.0", "optional": true, "os": [ "linux" ], "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.2.4" } }, "node_modules/@img/sharp-linux-arm64": { "version": "0.34.5", "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", "cpu": [ "arm64" ], "dev": true, "license": "Apache-2.0", "optional": true, "os": [ "linux" ], "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.2.4" } }, "node_modules/@img/sharp-linux-ppc64": { "version": "0.34.5", "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", "cpu": [ "ppc64" ], "dev": true, "license": "Apache-2.0", "optional": true, "os": [ "linux" ], "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { "@img/sharp-libvips-linux-ppc64": "1.2.4" } }, "node_modules/@img/sharp-linux-riscv64": { "version": "0.34.5", "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", "cpu": [ "riscv64" ], "dev": true, "license": "Apache-2.0", "optional": true, "os": [ "linux" ], "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { "@img/sharp-libvips-linux-riscv64": "1.2.4" } }, "node_modules/@img/sharp-linux-s390x": { "version": "0.34.5", "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", "cpu": [ "s390x" ], "dev": true, "license": "Apache-2.0", "optional": true, "os": [ "linux" ], "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.2.4" } }, "node_modules/@img/sharp-linux-x64": { "version": "0.34.5", "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", "cpu": [ "x64" ], "dev": true, "license": "Apache-2.0", "optional": true, "os": [ "linux" ], "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.2.4" } }, "node_modules/@img/sharp-linuxmusl-arm64": { "version": "0.34.5", "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", "cpu": [ "arm64" ], "dev": true, "license": "Apache-2.0", "optional": true, "os": [ "linux" ], "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" } }, "node_modules/@img/sharp-linuxmusl-x64": { "version": "0.34.5", "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", "cpu": [ "x64" ], "dev": true, "license": "Apache-2.0", "optional": true, "os": [ "linux" ], "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.2.4" } }, "node_modules/@img/sharp-wasm32": { "version": "0.34.5", "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", "cpu": [ "wasm32" ], "dev": true, "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", "optional": true, "dependencies": { "@emnapi/runtime": "^1.7.0" }, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { "url": "https://opencollective.com/libvips" } }, "node_modules/@img/sharp-win32-arm64": { "version": "0.34.5", "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", "cpu": [ "arm64" ], "dev": true, "license": "Apache-2.0 AND LGPL-3.0-or-later", "optional": true, "os": [ "win32" ], "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { "url": "https://opencollective.com/libvips" } }, "node_modules/@img/sharp-win32-ia32": { "version": "0.34.5", "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", "cpu": [ "ia32" ], "dev": true, "license": "Apache-2.0 AND LGPL-3.0-or-later", "optional": true, "os": [ "win32" ], "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { "url": "https://opencollective.com/libvips" } }, "node_modules/@img/sharp-win32-x64": { "version": "0.34.5", "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", "cpu": [ "x64" ], "dev": true, "license": "Apache-2.0 AND LGPL-3.0-or-later", "optional": true, "os": [ "win32" ], "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { "url": "https://opencollective.com/libvips" } }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "dev": true, "license": "MIT" }, "node_modules/@mdx-js/mdx": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-3.1.1.tgz", "integrity": "sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ==", "dev": true, "license": "MIT", "dependencies": { "@types/estree": "^1.0.0", "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdx": "^2.0.0", "acorn": "^8.0.0", "collapse-white-space": "^2.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "estree-util-scope": "^1.0.0", "estree-walker": "^3.0.0", "hast-util-to-jsx-runtime": "^2.0.0", "markdown-extensions": "^2.0.0", "recma-build-jsx": "^1.0.0", "recma-jsx": "^1.0.0", "recma-stringify": "^1.0.0", "rehype-recma": "^1.0.0", "remark-mdx": "^3.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.0.0", "source-map": "^0.7.0", "unified": "^11.0.0", "unist-util-position-from-estree": "^2.0.0", "unist-util-stringify-position": "^4.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/@oslojs/encoding": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@oslojs/encoding/-/encoding-1.1.0.tgz", "integrity": "sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==", "dev": true, "license": "MIT" }, "node_modules/@pagefind/darwin-arm64": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@pagefind/darwin-arm64/-/darwin-arm64-1.4.0.tgz", "integrity": "sha512-2vMqkbv3lbx1Awea90gTaBsvpzgRs7MuSgKDxW0m9oV1GPZCZbZBJg/qL83GIUEN2BFlY46dtUZi54pwH+/pTQ==", "cpu": [ "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "darwin" ] }, "node_modules/@pagefind/darwin-x64": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@pagefind/darwin-x64/-/darwin-x64-1.4.0.tgz", "integrity": "sha512-e7JPIS6L9/cJfow+/IAqknsGqEPjJnVXGjpGm25bnq+NPdoD3c/7fAwr1OXkG4Ocjx6ZGSCijXEV4ryMcH2E3A==", "cpu": [ "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "darwin" ] }, "node_modules/@pagefind/default-ui": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@pagefind/default-ui/-/default-ui-1.4.0.tgz", "integrity": "sha512-wie82VWn3cnGEdIjh4YwNESyS1G6vRHwL6cNjy9CFgNnWW/PGRjsLq300xjVH5sfPFK3iK36UxvIBymtQIEiSQ==", "dev": true, "license": "MIT" }, "node_modules/@pagefind/freebsd-x64": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@pagefind/freebsd-x64/-/freebsd-x64-1.4.0.tgz", "integrity": "sha512-WcJVypXSZ+9HpiqZjFXMUobfFfZZ6NzIYtkhQ9eOhZrQpeY5uQFqNWLCk7w9RkMUwBv1HAMDW3YJQl/8OqsV0Q==", "cpu": [ "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "freebsd" ] }, "node_modules/@pagefind/linux-arm64": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@pagefind/linux-arm64/-/linux-arm64-1.4.0.tgz", "integrity": "sha512-PIt8dkqt4W06KGmQjONw7EZbhDF+uXI7i0XtRLN1vjCUxM9vGPdtJc2mUyVPevjomrGz5M86M8bqTr6cgDp1Uw==", "cpu": [ "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@pagefind/linux-x64": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@pagefind/linux-x64/-/linux-x64-1.4.0.tgz", "integrity": "sha512-z4oddcWwQ0UHrTHR8psLnVlz6USGJ/eOlDPTDYZ4cI8TK8PgwRUPQZp9D2iJPNIPcS6Qx/E4TebjuGJOyK8Mmg==", "cpu": [ "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@pagefind/windows-x64": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@pagefind/windows-x64/-/windows-x64-1.4.0.tgz", "integrity": "sha512-NkT+YAdgS2FPCn8mIA9bQhiBs+xmniMGq1LFPDhcFn0+2yIUEiIG06t7bsZlhdjknEQRTSdT7YitP6fC5qwP0g==", "cpu": [ "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "win32" ] }, "node_modules/@rollup/pluginutils": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", "dev": true, "license": "MIT", "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "engines": { "node": ">=14.0.0" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "peerDependenciesMeta": { "rollup": { "optional": true } } }, "node_modules/@rollup/pluginutils/node_modules/estree-walker": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", "dev": true, "license": "MIT" }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.60.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.0.tgz", "integrity": "sha512-WOhNW9K8bR3kf4zLxbfg6Pxu2ybOUbB2AjMDHSQx86LIF4rH4Ft7vmMwNt0loO0eonglSNy4cpD3MKXXKQu0/A==", "cpu": [ "arm" ], "dev": true, "license": "MIT", "optional": true, "os": [ "android" ] }, "node_modules/@rollup/rollup-android-arm64": { "version": "4.60.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.0.tgz", "integrity": "sha512-u6JHLll5QKRvjciE78bQXDmqRqNs5M/3GVqZeMwvmjaNODJih/WIrJlFVEihvV0MiYFmd+ZyPr9wxOVbPAG2Iw==", "cpu": [ "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "android" ] }, "node_modules/@rollup/rollup-darwin-arm64": { "version": "4.60.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.0.tgz", "integrity": "sha512-qEF7CsKKzSRc20Ciu2Zw1wRrBz4g56F7r/vRwY430UPp/nt1x21Q/fpJ9N5l47WWvJlkNCPJz3QRVw008fi7yA==", "cpu": [ "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "darwin" ] }, "node_modules/@rollup/rollup-darwin-x64": { "version": "4.60.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.0.tgz", "integrity": "sha512-WADYozJ4QCnXCH4wPB+3FuGmDPoFseVCUrANmA5LWwGmC6FL14BWC7pcq+FstOZv3baGX65tZ378uT6WG8ynTw==", "cpu": [ "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "darwin" ] }, "node_modules/@rollup/rollup-freebsd-arm64": { "version": "4.60.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.0.tgz", "integrity": "sha512-6b8wGHJlDrGeSE3aH5mGNHBjA0TTkxdoNHik5EkvPHCt351XnigA4pS7Wsj/Eo9Y8RBU6f35cjN9SYmCFBtzxw==", "cpu": [ "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "freebsd" ] }, "node_modules/@rollup/rollup-freebsd-x64": { "version": "4.60.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.0.tgz", "integrity": "sha512-h25Ga0t4jaylMB8M/JKAyrvvfxGRjnPQIR8lnCayyzEjEOx2EJIlIiMbhpWxDRKGKF8jbNH01NnN663dH638mA==", "cpu": [ "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "freebsd" ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { "version": "4.60.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.0.tgz", "integrity": "sha512-RzeBwv0B3qtVBWtcuABtSuCzToo2IEAIQrcyB/b2zMvBWVbjo8bZDjACUpnaafaxhTw2W+imQbP2BD1usasK4g==", "cpu": [ "arm" ], "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { "version": "4.60.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.0.tgz", "integrity": "sha512-Sf7zusNI2CIU1HLzuu9Tc5YGAHEZs5Lu7N1ssJG4Tkw6e0MEsN7NdjUDDfGNHy2IU+ENyWT+L2obgWiguWibWQ==", "cpu": [ "arm" ], "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { "version": "4.60.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.0.tgz", "integrity": "sha512-DX2x7CMcrJzsE91q7/O02IJQ5/aLkVtYFryqCjduJhUfGKG6yJV8hxaw8pZa93lLEpPTP/ohdN4wFz7yp/ry9A==", "cpu": [ "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { "version": "4.60.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.0.tgz", "integrity": "sha512-09EL+yFVbJZlhcQfShpswwRZ0Rg+z/CsSELFCnPt3iK+iqwGsI4zht3secj5vLEs957QvFFXnzAT0FFPIxSrkQ==", "cpu": [ "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { "version": "4.60.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.0.tgz", "integrity": "sha512-i9IcCMPr3EXm8EQg5jnja0Zyc1iFxJjZWlb4wr7U2Wx/GrddOuEafxRdMPRYVaXjgbhvqalp6np07hN1w9kAKw==", "cpu": [ "loong64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-loong64-musl": { "version": "4.60.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.0.tgz", "integrity": "sha512-DGzdJK9kyJ+B78MCkWeGnpXJ91tK/iKA6HwHxF4TAlPIY7GXEvMe8hBFRgdrR9Ly4qebR/7gfUs9y2IoaVEyog==", "cpu": [ "loong64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { "version": "4.60.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.0.tgz", "integrity": "sha512-RwpnLsqC8qbS8z1H1AxBA1H6qknR4YpPR9w2XX0vo2Sz10miu57PkNcnHVaZkbqyw/kUWfKMI73jhmfi9BRMUQ==", "cpu": [ "ppc64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-ppc64-musl": { "version": "4.60.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.0.tgz", "integrity": "sha512-Z8pPf54Ly3aqtdWC3G4rFigZgNvd+qJlOE52fmko3KST9SoGfAdSRCwyoyG05q1HrrAblLbk1/PSIV+80/pxLg==", "cpu": [ "ppc64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { "version": "4.60.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.0.tgz", "integrity": "sha512-3a3qQustp3COCGvnP4SvrMHnPQ9d1vzCakQVRTliaz8cIp/wULGjiGpbcqrkv0WrHTEp8bQD/B3HBjzujVWLOA==", "cpu": [ "riscv64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { "version": "4.60.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.0.tgz", "integrity": "sha512-pjZDsVH/1VsghMJ2/kAaxt6dL0psT6ZexQVrijczOf+PeP2BUqTHYejk3l6TlPRydggINOeNRhvpLa0AYpCWSQ==", "cpu": [ "riscv64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { "version": "4.60.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.0.tgz", "integrity": "sha512-3ObQs0BhvPgiUVZrN7gqCSvmFuMWvWvsjG5ayJ3Lraqv+2KhOsp+pUbigqbeWqueGIsnn+09HBw27rJ+gYK4VQ==", "cpu": [ "s390x" ], "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { "version": "4.60.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.0.tgz", "integrity": "sha512-EtylprDtQPdS5rXvAayrNDYoJhIz1/vzN2fEubo3yLE7tfAw+948dO0g4M0vkTVFhKojnF+n6C8bDNe+gDRdTg==", "cpu": [ "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-x64-musl": { "version": "4.60.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.0.tgz", "integrity": "sha512-k09oiRCi/bHU9UVFqD17r3eJR9bn03TyKraCrlz5ULFJGdJGi7VOmm9jl44vOJvRJ6P7WuBi/s2A97LxxHGIdw==", "cpu": [ "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-openbsd-x64": { "version": "4.60.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.0.tgz", "integrity": "sha512-1o/0/pIhozoSaDJoDcec+IVLbnRtQmHwPV730+AOD29lHEEo4F5BEUB24H0OBdhbBBDwIOSuf7vgg0Ywxdfiiw==", "cpu": [ "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "openbsd" ] }, "node_modules/@rollup/rollup-openharmony-arm64": { "version": "4.60.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.0.tgz", "integrity": "sha512-pESDkos/PDzYwtyzB5p/UoNU/8fJo68vcXM9ZW2V0kjYayj1KaaUfi1NmTUTUpMn4UhU4gTuK8gIaFO4UGuMbA==", "cpu": [ "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "openharmony" ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { "version": "4.60.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.0.tgz", "integrity": "sha512-hj1wFStD7B1YBeYmvY+lWXZ7ey73YGPcViMShYikqKT1GtstIKQAtfUI6yrzPjAy/O7pO0VLXGmUVWXQMaYgTQ==", "cpu": [ "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "win32" ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { "version": "4.60.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.0.tgz", "integrity": "sha512-SyaIPFoxmUPlNDq5EHkTbiKzmSEmq/gOYFI/3HHJ8iS/v1mbugVa7dXUzcJGQfoytp9DJFLhHH4U3/eTy2Bq4w==", "cpu": [ "ia32" ], "dev": true, "license": "MIT", "optional": true, "os": [ "win32" ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { "version": "4.60.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.0.tgz", "integrity": "sha512-RdcryEfzZr+lAr5kRm2ucN9aVlCCa2QNq4hXelZxb8GG0NJSazq44Z3PCCc8wISRuCVnGs0lQJVX5Vp6fKA+IA==", "cpu": [ "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "win32" ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { "version": "4.60.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.0.tgz", "integrity": "sha512-PrsWNQ8BuE00O3Xsx3ALh2Df8fAj9+cvvX9AIA6o4KpATR98c9mud4XtDWVvsEuyia5U4tVSTKygawyJkjm60w==", "cpu": [ "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "win32" ] }, "node_modules/@shikijs/core": { "version": "3.23.0", "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-3.23.0.tgz", "integrity": "sha512-NSWQz0riNb67xthdm5br6lAkvpDJRTgB36fxlo37ZzM2yq0PQFFzbd8psqC2XMPgCzo1fW6cVi18+ArJ44wqgA==", "dev": true, "license": "MIT", "dependencies": { "@shikijs/types": "3.23.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "node_modules/@shikijs/engine-javascript": { "version": "3.23.0", "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-3.23.0.tgz", "integrity": "sha512-aHt9eiGFobmWR5uqJUViySI1bHMqrAgamWE1TYSUoftkAeCCAiGawPMwM+VCadylQtF4V3VNOZ5LmfItH5f3yA==", "dev": true, "license": "MIT", "dependencies": { "@shikijs/types": "3.23.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.4" } }, "node_modules/@shikijs/engine-oniguruma": { "version": "3.23.0", "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.23.0.tgz", "integrity": "sha512-1nWINwKXxKKLqPibT5f4pAFLej9oZzQTsby8942OTlsJzOBZ0MWKiwzMsd+jhzu8YPCHAswGnnN1YtQfirL35g==", "dev": true, "license": "MIT", "dependencies": { "@shikijs/types": "3.23.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "node_modules/@shikijs/langs": { "version": "3.23.0", "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.23.0.tgz", "integrity": "sha512-2Ep4W3Re5aB1/62RSYQInK9mM3HsLeB91cHqznAJMuylqjzNVAVCMnNWRHFtcNHXsoNRayP9z1qj4Sq3nMqYXg==", "dev": true, "license": "MIT", "dependencies": { "@shikijs/types": "3.23.0" } }, "node_modules/@shikijs/primitive": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@shikijs/primitive/-/primitive-4.0.2.tgz", "integrity": "sha512-M6UMPrSa3fN5ayeJwFVl9qWofl273wtK1VG8ySDZ1mQBfhCpdd8nEx7nPZ/tk7k+TYcpqBZzj/AnwxT9lO+HJw==", "dev": true, "license": "MIT", "dependencies": { "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" }, "engines": { "node": ">=20" } }, "node_modules/@shikijs/primitive/node_modules/@shikijs/types": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-4.0.2.tgz", "integrity": "sha512-qzbeRooUTPnLE+sHD/Z8DStmaDgnbbc/pMrU203950aRqjX/6AFHeDYT+j00y2lPdz0ywJKx7o/7qnqTivtlXg==", "dev": true, "license": "MIT", "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" }, "engines": { "node": ">=20" } }, "node_modules/@shikijs/themes": { "version": "3.23.0", "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.23.0.tgz", "integrity": "sha512-5qySYa1ZgAT18HR/ypENL9cUSGOeI2x+4IvYJu4JgVJdizn6kG4ia5Q1jDEOi7gTbN4RbuYtmHh0W3eccOrjMA==", "dev": true, "license": "MIT", "dependencies": { "@shikijs/types": "3.23.0" } }, "node_modules/@shikijs/types": { "version": "3.23.0", "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.23.0.tgz", "integrity": "sha512-3JZ5HXOZfYjsYSk0yPwBrkupyYSLpAE26Qc0HLghhZNGTZg/SKxXIIgoxOpmmeQP0RRSDJTk1/vPfw9tbw+jSQ==", "dev": true, "license": "MIT", "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "node_modules/@shikijs/vscode-textmate": { "version": "10.0.2", "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", "dev": true, "license": "MIT" }, "node_modules/@types/debug": { "version": "4.1.13", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", "dev": true, "license": "MIT", "dependencies": { "@types/ms": "*" } }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "dev": true, "license": "MIT" }, "node_modules/@types/estree-jsx": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", "dev": true, "license": "MIT", "dependencies": { "@types/estree": "*" } }, "node_modules/@types/hast": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", "dev": true, "license": "MIT", "dependencies": { "@types/unist": "*" } }, "node_modules/@types/js-yaml": { "version": "4.0.9", "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==", "dev": true, "license": "MIT" }, "node_modules/@types/mdast": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", "dev": true, "license": "MIT", "dependencies": { "@types/unist": "*" } }, "node_modules/@types/mdx": { "version": "2.0.13", "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.13.tgz", "integrity": "sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==", "dev": true, "license": "MIT" }, "node_modules/@types/ms": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", "dev": true, "license": "MIT" }, "node_modules/@types/nlcst": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/nlcst/-/nlcst-2.0.3.tgz", "integrity": "sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA==", "dev": true, "license": "MIT", "dependencies": { "@types/unist": "*" } }, "node_modules/@types/node": { "version": "24.12.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.0.tgz", "integrity": "sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ==", "dev": true, "license": "MIT", "dependencies": { "undici-types": "~7.16.0" } }, "node_modules/@types/sax": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/@types/sax/-/sax-1.2.7.tgz", "integrity": "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==", "dev": true, "license": "MIT", "dependencies": { "@types/node": "*" } }, "node_modules/@types/unist": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", "dev": true, "license": "MIT" }, "node_modules/@ungap/structured-clone": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", "dev": true, "license": "ISC" }, "node_modules/acorn": { "version": "8.16.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", "bin": { "acorn": "bin/acorn" }, "engines": { "node": ">=0.4.0" } }, "node_modules/acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "dev": true, "license": "ISC", "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" }, "engines": { "node": ">= 8" } }, "node_modules/anymatch/node_modules/picomatch": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, "license": "MIT", "engines": { "node": ">=8.6" }, "funding": { "url": "https://github.com/sponsors/jonschlinkert" } }, "node_modules/arg": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", "dev": true, "license": "MIT" }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true, "license": "Python-2.0" }, "node_modules/aria-query": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", "dev": true, "license": "Apache-2.0", "engines": { "node": ">= 0.4" } }, "node_modules/array-iterate": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/array-iterate/-/array-iterate-2.0.1.tgz", "integrity": "sha512-I1jXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G/v0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg==", "dev": true, "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, "node_modules/astring": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/astring/-/astring-1.9.0.tgz", "integrity": "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==", "dev": true, "license": "MIT", "bin": { "astring": "bin/astring" } }, "node_modules/astro": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/astro/-/astro-6.1.0.tgz", "integrity": "sha512-J8XGJQo5+W2wJLdUbQHVho4DHWDM6V4Dp8s+z0Fs3O/mcu3WjbTBELOv/MC7ueoqmQ/Jts6Bz7FJwbAopbFd+g==", "dev": true, "license": "MIT", "dependencies": { "@astrojs/compiler": "^3.0.1", "@astrojs/internal-helpers": "0.8.0", "@astrojs/markdown-remark": "7.1.0", "@astrojs/telemetry": "3.3.0", "@capsizecss/unpack": "^4.0.0", "@clack/prompts": "^1.1.0", "@oslojs/encoding": "^1.1.0", "@rollup/pluginutils": "^5.3.0", "aria-query": "^5.3.2", "axobject-query": "^4.1.0", "ci-info": "^4.4.0", "clsx": "^2.1.1", "common-ancestor-path": "^2.0.0", "cookie": "^1.1.1", "devalue": "^5.6.3", "diff": "^8.0.3", "dlv": "^1.1.3", "dset": "^3.1.4", "es-module-lexer": "^2.0.0", "esbuild": "^0.27.3", "flattie": "^1.1.1", "fontace": "~0.4.1", "github-slugger": "^2.0.0", "html-escaper": "3.0.3", "http-cache-semantics": "^4.2.0", "js-yaml": "^4.1.1", "magic-string": "^0.30.21", "magicast": "^0.5.2", "mrmime": "^2.0.1", "neotraverse": "^0.6.18", "obug": "^2.1.1", "p-limit": "^7.3.0", "p-queue": "^9.1.0", "package-manager-detector": "^1.6.0", "piccolore": "^0.1.3", "picomatch": "^4.0.3", "rehype": "^13.0.2", "semver": "^7.7.4", "shiki": "^4.0.2", "smol-toml": "^1.6.0", "svgo": "^4.0.1", "tinyclip": "^0.1.12", "tinyexec": "^1.0.4", "tinyglobby": "^0.2.15", "tsconfck": "^3.1.6", "ultrahtml": "^1.6.0", "unifont": "~0.7.4", "unist-util-visit": "^5.1.0", "unstorage": "^1.17.4", "vfile": "^6.0.3", "vite": "^7.3.1", "vitefu": "^1.1.2", "xxhash-wasm": "^1.1.0", "yargs-parser": "^22.0.0", "zod": "^4.3.6" }, "bin": { "astro": "bin/astro.mjs" }, "engines": { "node": ">=22.12.0", "npm": ">=9.6.5", "pnpm": ">=7.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/astrodotbuild" }, "optionalDependencies": { "sharp": "^0.34.0" } }, "node_modules/astro-expressive-code": { "version": "0.41.7", "resolved": "https://registry.npmjs.org/astro-expressive-code/-/astro-expressive-code-0.41.7.tgz", "integrity": "sha512-hUpogGc6DdAd+I7pPXsctyYPRBJDK7Q7d06s4cyP0Vz3OcbziP3FNzN0jZci1BpCvLn9675DvS7B9ctKKX64JQ==", "dev": true, "license": "MIT", "dependencies": { "rehype-expressive-code": "^0.41.7" }, "peerDependencies": { "astro": "^4.0.0-beta || ^5.0.0-beta || ^3.3.0 || ^6.0.0-beta" } }, "node_modules/astro/node_modules/@shikijs/core": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-4.0.2.tgz", "integrity": "sha512-hxT0YF4ExEqB8G/qFdtJvpmHXBYJ2lWW7qTHDarVkIudPFE6iCIrqdgWxGn5s+ppkGXI0aEGlibI0PAyzP3zlw==", "dev": true, "license": "MIT", "dependencies": { "@shikijs/primitive": "4.0.2", "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" }, "engines": { "node": ">=20" } }, "node_modules/astro/node_modules/@shikijs/engine-javascript": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-4.0.2.tgz", "integrity": "sha512-7PW0Nm49DcoUIQEXlJhNNBHyoGMjalRETTCcjMqEaMoJRLljy1Bi/EGV3/qLBgLKQejdspiiYuHGQW6dX94Nag==", "dev": true, "license": "MIT", "dependencies": { "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.4" }, "engines": { "node": ">=20" } }, "node_modules/astro/node_modules/@shikijs/engine-oniguruma": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-4.0.2.tgz", "integrity": "sha512-UpCB9Y2sUKlS9z8juFSKz7ZtysmeXCgnRF0dlhXBkmQnek7lAToPte8DkxmEYGNTMii72zU/lyXiCB6StuZeJg==", "dev": true, "license": "MIT", "dependencies": { "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2" }, "engines": { "node": ">=20" } }, "node_modules/astro/node_modules/@shikijs/langs": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-4.0.2.tgz", "integrity": "sha512-KaXby5dvoeuZzN0rYQiPMjFoUrz4hgwIE+D6Du9owcHcl6/g16/yT5BQxSW5cGt2MZBz6Hl0YuRqf12omRfUUg==", "dev": true, "license": "MIT", "dependencies": { "@shikijs/types": "4.0.2" }, "engines": { "node": ">=20" } }, "node_modules/astro/node_modules/@shikijs/themes": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-4.0.2.tgz", "integrity": "sha512-mjCafwt8lJJaVSsQvNVrJumbnnj1RI8jbUKrPKgE6E3OvQKxnuRoBaYC51H4IGHePsGN/QtALglWBU7DoKDFnA==", "dev": true, "license": "MIT", "dependencies": { "@shikijs/types": "4.0.2" }, "engines": { "node": ">=20" } }, "node_modules/astro/node_modules/@shikijs/types": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-4.0.2.tgz", "integrity": "sha512-qzbeRooUTPnLE+sHD/Z8DStmaDgnbbc/pMrU203950aRqjX/6AFHeDYT+j00y2lPdz0ywJKx7o/7qnqTivtlXg==", "dev": true, "license": "MIT", "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" }, "engines": { "node": ">=20" } }, "node_modules/astro/node_modules/shiki": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/shiki/-/shiki-4.0.2.tgz", "integrity": "sha512-eAVKTMedR5ckPo4xne/PjYQYrU3qx78gtJZ+sHlXEg5IHhhoQhMfZVzetTYuaJS0L2Ef3AcCRzCHV8T0WI6nIQ==", "dev": true, "license": "MIT", "dependencies": { "@shikijs/core": "4.0.2", "@shikijs/engine-javascript": "4.0.2", "@shikijs/engine-oniguruma": "4.0.2", "@shikijs/langs": "4.0.2", "@shikijs/themes": "4.0.2", "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" }, "engines": { "node": ">=20" } }, "node_modules/astro/node_modules/zod": { "version": "4.3.6", "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" } }, "node_modules/axobject-query": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", "dev": true, "license": "Apache-2.0", "engines": { "node": ">= 0.4" } }, "node_modules/bail": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", "dev": true, "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, "node_modules/bcp-47": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/bcp-47/-/bcp-47-2.1.0.tgz", "integrity": "sha512-9IIS3UPrvIa1Ej+lVDdDwO7zLehjqsaByECw0bu2RRGP73jALm6FYbzI5gWbgHLvNdkvfXB5YrSbocZdOS0c0w==", "dev": true, "license": "MIT", "dependencies": { "is-alphabetical": "^2.0.0", "is-alphanumerical": "^2.0.0", "is-decimal": "^2.0.0" }, "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, "node_modules/bcp-47-match": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/bcp-47-match/-/bcp-47-match-2.0.3.tgz", "integrity": "sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ==", "dev": true, "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, "node_modules/boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", "dev": true, "license": "ISC" }, "node_modules/ccount": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", "dev": true, "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, "node_modules/character-entities": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", "dev": true, "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, "node_modules/character-entities-html4": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", "dev": true, "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, "node_modules/character-entities-legacy": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", "dev": true, "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, "node_modules/character-reference-invalid": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", "dev": true, "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, "node_modules/chokidar": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", "dev": true, "license": "MIT", "dependencies": { "readdirp": "^5.0.0" }, "engines": { "node": ">= 20.19.0" }, "funding": { "url": "https://paulmillr.com/funding/" } }, "node_modules/ci-info": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", "dev": true, "funding": [ { "type": "github", "url": "https://github.com/sponsors/sibiraj-s" } ], "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/clsx": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", "dev": true, "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/collapse-white-space": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-2.1.0.tgz", "integrity": "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==", "dev": true, "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, "node_modules/comma-separated-tokens": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", "dev": true, "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, "node_modules/commander": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", "dev": true, "license": "MIT", "engines": { "node": ">=16" } }, "node_modules/common-ancestor-path": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-2.0.0.tgz", "integrity": "sha512-dnN3ibLeoRf2HNC+OlCiNc5d2zxbLJXOtiZUudNFSXZrNSydxcCsSpRzXwfu7BBWCIfHPw+xTayeBvJCP/D8Ng==", "dev": true, "license": "BlueOak-1.0.0", "engines": { "node": ">= 18" } }, "node_modules/cookie": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", "dev": true, "license": "MIT", "engines": { "node": ">=18" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/express" } }, "node_modules/cookie-es": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-1.2.2.tgz", "integrity": "sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==", "dev": true, "license": "MIT" }, "node_modules/crossws": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/crossws/-/crossws-0.3.5.tgz", "integrity": "sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==", "dev": true, "license": "MIT", "dependencies": { "uncrypto": "^0.1.3" } }, "node_modules/css-select": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", "dev": true, "license": "BSD-2-Clause", "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", "domhandler": "^5.0.2", "domutils": "^3.0.1", "nth-check": "^2.0.1" }, "funding": { "url": "https://github.com/sponsors/fb55" } }, "node_modules/css-selector-parser": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/css-selector-parser/-/css-selector-parser-3.3.0.tgz", "integrity": "sha512-Y2asgMGFqJKF4fq4xHDSlFYIkeVfRsm69lQC1q9kbEsH5XtnINTMrweLkjYMeaUgiXBy/uvKeO/a1JHTNnmB2g==", "dev": true, "funding": [ { "type": "github", "url": "https://github.com/sponsors/mdevils" }, { "type": "patreon", "url": "https://patreon.com/mdevils" } ], "license": "MIT" }, "node_modules/css-tree": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", "dev": true, "license": "MIT", "dependencies": { "mdn-data": "2.27.1", "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" } }, "node_modules/css-what": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">= 6" }, "funding": { "url": "https://github.com/sponsors/fb55" } }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", "dev": true, "license": "MIT", "bin": { "cssesc": "bin/cssesc" }, "engines": { "node": ">=4" } }, "node_modules/csso": { "version": "5.0.5", "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", "dev": true, "license": "MIT", "dependencies": { "css-tree": "~2.2.0" }, "engines": { "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", "npm": ">=7.0.0" } }, "node_modules/csso/node_modules/css-tree": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", "dev": true, "license": "MIT", "dependencies": { "mdn-data": "2.0.28", "source-map-js": "^1.0.1" }, "engines": { "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", "npm": ">=7.0.0" } }, "node_modules/csso/node_modules/mdn-data": { "version": "2.0.28", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", "dev": true, "license": "CC0-1.0" }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" }, "engines": { "node": ">=6.0" }, "peerDependenciesMeta": { "supports-color": { "optional": true } } }, "node_modules/decode-named-character-reference": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", "dev": true, "license": "MIT", "dependencies": { "character-entities": "^2.0.0" }, "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, "node_modules/defu": { "version": "6.1.4", "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", "dev": true, "license": "MIT" }, "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", "dev": true, "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/destr": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", "dev": true, "license": "MIT" }, "node_modules/detect-libc": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "dev": true, "license": "Apache-2.0", "optional": true, "engines": { "node": ">=8" } }, "node_modules/devalue": { "version": "5.6.4", "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.6.4.tgz", "integrity": "sha512-Gp6rDldRsFh/7XuouDbxMH3Mx8GMCcgzIb1pDTvNyn8pZGQ22u+Wa+lGV9dQCltFQ7uVw0MhRyb8XDskNFOReA==", "dev": true, "license": "MIT" }, "node_modules/devlop": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", "dev": true, "license": "MIT", "dependencies": { "dequal": "^2.0.0" }, "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, "node_modules/diff": { "version": "8.0.4", "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.4.tgz", "integrity": "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==", "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.3.1" } }, "node_modules/direction": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/direction/-/direction-2.0.1.tgz", "integrity": "sha512-9S6m9Sukh1cZNknO1CWAr2QAWsbKLafQiyM5gZ7VgXHeuaoUwffKN4q6NC4A/Mf9iiPlOXQEKW/Mv/mh9/3YFA==", "dev": true, "license": "MIT", "bin": { "direction": "cli.js" }, "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, "node_modules/dlv": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", "dev": true, "license": "MIT" }, "node_modules/dom-serializer": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", "dev": true, "license": "MIT", "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" }, "funding": { "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" } }, "node_modules/dom-serializer/node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=0.12" }, "funding": { "url": "https://github.com/fb55/entities?sponsor=1" } }, "node_modules/domelementtype": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", "dev": true, "funding": [ { "type": "github", "url": "https://github.com/sponsors/fb55" } ], "license": "BSD-2-Clause" }, "node_modules/domhandler": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", "dev": true, "license": "BSD-2-Clause", "dependencies": { "domelementtype": "^2.3.0" }, "engines": { "node": ">= 4" }, "funding": { "url": "https://github.com/fb55/domhandler?sponsor=1" } }, "node_modules/domutils": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", "dev": true, "license": "BSD-2-Clause", "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" }, "funding": { "url": "https://github.com/fb55/domutils?sponsor=1" } }, "node_modules/dset": { "version": "3.1.4", "resolved": "https://registry.npmjs.org/dset/-/dset-3.1.4.tgz", "integrity": "sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==", "dev": true, "license": "MIT", "engines": { "node": ">=4" } }, "node_modules/entities": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=0.12" }, "funding": { "url": "https://github.com/fb55/entities?sponsor=1" } }, "node_modules/es-module-lexer": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", "dev": true, "license": "MIT" }, "node_modules/esast-util-from-estree": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/esast-util-from-estree/-/esast-util-from-estree-2.0.0.tgz", "integrity": "sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==", "dev": true, "license": "MIT", "dependencies": { "@types/estree-jsx": "^1.0.0", "devlop": "^1.0.0", "estree-util-visit": "^2.0.0", "unist-util-position-from-estree": "^2.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/esast-util-from-js": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/esast-util-from-js/-/esast-util-from-js-2.0.1.tgz", "integrity": "sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==", "dev": true, "license": "MIT", "dependencies": { "@types/estree-jsx": "^1.0.0", "acorn": "^8.0.0", "esast-util-from-estree": "^2.0.0", "vfile-message": "^4.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/esbuild": { "version": "0.27.4", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.4.tgz", "integrity": "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==", "dev": true, "hasInstallScript": true, "license": "MIT", "bin": { "esbuild": "bin/esbuild" }, "engines": { "node": ">=18" }, "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.4", "@esbuild/android-arm": "0.27.4", "@esbuild/android-arm64": "0.27.4", "@esbuild/android-x64": "0.27.4", "@esbuild/darwin-arm64": "0.27.4", "@esbuild/darwin-x64": "0.27.4", "@esbuild/freebsd-arm64": "0.27.4", "@esbuild/freebsd-x64": "0.27.4", "@esbuild/linux-arm": "0.27.4", "@esbuild/linux-arm64": "0.27.4", "@esbuild/linux-ia32": "0.27.4", "@esbuild/linux-loong64": "0.27.4", "@esbuild/linux-mips64el": "0.27.4", "@esbuild/linux-ppc64": "0.27.4", "@esbuild/linux-riscv64": "0.27.4", "@esbuild/linux-s390x": "0.27.4", "@esbuild/linux-x64": "0.27.4", "@esbuild/netbsd-arm64": "0.27.4", "@esbuild/netbsd-x64": "0.27.4", "@esbuild/openbsd-arm64": "0.27.4", "@esbuild/openbsd-x64": "0.27.4", "@esbuild/openharmony-arm64": "0.27.4", "@esbuild/sunos-x64": "0.27.4", "@esbuild/win32-arm64": "0.27.4", "@esbuild/win32-ia32": "0.27.4", "@esbuild/win32-x64": "0.27.4" } }, "node_modules/escape-string-regexp": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", "dev": true, "license": "MIT", "engines": { "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/estree-util-attach-comments": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/estree-util-attach-comments/-/estree-util-attach-comments-3.0.0.tgz", "integrity": "sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==", "dev": true, "license": "MIT", "dependencies": { "@types/estree": "^1.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/estree-util-build-jsx": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/estree-util-build-jsx/-/estree-util-build-jsx-3.0.1.tgz", "integrity": "sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ==", "dev": true, "license": "MIT", "dependencies": { "@types/estree-jsx": "^1.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "estree-walker": "^3.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/estree-util-is-identifier-name": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", "dev": true, "license": "MIT", "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/estree-util-scope": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/estree-util-scope/-/estree-util-scope-1.0.0.tgz", "integrity": "sha512-2CAASclonf+JFWBNJPndcOpA8EMJwa0Q8LUFJEKqXLW6+qBvbFZuF5gItbQOs/umBUkjviCSDCbBwU2cXbmrhQ==", "dev": true, "license": "MIT", "dependencies": { "@types/estree": "^1.0.0", "devlop": "^1.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/estree-util-to-js": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/estree-util-to-js/-/estree-util-to-js-2.0.0.tgz", "integrity": "sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==", "dev": true, "license": "MIT", "dependencies": { "@types/estree-jsx": "^1.0.0", "astring": "^1.8.0", "source-map": "^0.7.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/estree-util-visit": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/estree-util-visit/-/estree-util-visit-2.0.0.tgz", "integrity": "sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==", "dev": true, "license": "MIT", "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/unist": "^3.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/estree-walker": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", "dev": true, "license": "MIT", "dependencies": { "@types/estree": "^1.0.0" } }, "node_modules/eventemitter3": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", "dev": true, "license": "MIT" }, "node_modules/expressive-code": { "version": "0.41.7", "resolved": "https://registry.npmjs.org/expressive-code/-/expressive-code-0.41.7.tgz", "integrity": "sha512-2wZjC8OQ3TaVEMcBtYY4Va3lo6J+Ai9jf3d4dbhURMJcU4Pbqe6EcHe424MIZI0VHUA1bR6xdpoHYi3yxokWqA==", "dev": true, "license": "MIT", "dependencies": { "@expressive-code/core": "^0.41.7", "@expressive-code/plugin-frames": "^0.41.7", "@expressive-code/plugin-shiki": "^0.41.7", "@expressive-code/plugin-text-markers": "^0.41.7" } }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "dev": true, "license": "MIT" }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, "license": "MIT", "engines": { "node": ">=12.0.0" }, "peerDependencies": { "picomatch": "^3 || ^4" }, "peerDependenciesMeta": { "picomatch": { "optional": true } } }, "node_modules/flattie": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/flattie/-/flattie-1.1.1.tgz", "integrity": "sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ==", "dev": true, "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/fontace": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/fontace/-/fontace-0.4.1.tgz", "integrity": "sha512-lDMvbAzSnHmbYMTEld5qdtvNH2/pWpICOqpean9IgC7vUbUJc3k+k5Dokp85CegamqQpFbXf0rAVkbzpyTA8aw==", "dev": true, "license": "MIT", "dependencies": { "fontkitten": "^1.0.2" } }, "node_modules/fontkitten": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/fontkitten/-/fontkitten-1.0.3.tgz", "integrity": "sha512-Wp1zXWPVUPBmfoa3Cqc9ctaKuzKAV6uLstRqlR56kSjplf5uAce+qeyYym7F+PHbGTk+tCEdkCW6RD7DX/gBZw==", "dev": true, "license": "MIT", "dependencies": { "tiny-inflate": "^1.0.3" }, "engines": { "node": ">=20" } }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, "os": [ "darwin" ], "engines": { "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, "node_modules/github-slugger": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz", "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==", "dev": true, "license": "ISC" }, "node_modules/h3": { "version": "1.15.10", "resolved": "https://registry.npmjs.org/h3/-/h3-1.15.10.tgz", "integrity": "sha512-YzJeWSkDZxAhvmp8dexjRK5hxziRO7I9m0N53WhvYL5NiWfkUkzssVzY9jvGu0HBoLFW6+duYmNSn6MaZBCCtg==", "dev": true, "license": "MIT", "dependencies": { "cookie-es": "^1.2.2", "crossws": "^0.3.5", "defu": "^6.1.4", "destr": "^2.0.5", "iron-webcrypto": "^1.2.1", "node-mock-http": "^1.0.4", "radix3": "^1.1.2", "ufo": "^1.6.3", "uncrypto": "^0.1.3" } }, "node_modules/hast-util-embedded": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/hast-util-embedded/-/hast-util-embedded-3.0.0.tgz", "integrity": "sha512-naH8sld4Pe2ep03qqULEtvYr7EjrLK2QHY8KJR6RJkTUjPGObe1vnx585uzem2hGra+s1q08DZZpfgDVYRbaXA==", "dev": true, "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", "hast-util-is-element": "^3.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/hast-util-format": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/hast-util-format/-/hast-util-format-1.1.0.tgz", "integrity": "sha512-yY1UDz6bC9rDvCWHpx12aIBGRG7krurX0p0Fm6pT547LwDIZZiNr8a+IHDogorAdreULSEzP82Nlv5SZkHZcjA==", "dev": true, "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", "hast-util-embedded": "^3.0.0", "hast-util-minify-whitespace": "^1.0.0", "hast-util-phrasing": "^3.0.0", "hast-util-whitespace": "^3.0.0", "html-whitespace-sensitive-tag-names": "^3.0.0", "unist-util-visit-parents": "^6.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/hast-util-from-html": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.3.tgz", "integrity": "sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==", "dev": true, "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", "devlop": "^1.1.0", "hast-util-from-parse5": "^8.0.0", "parse5": "^7.0.0", "vfile": "^6.0.0", "vfile-message": "^4.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/hast-util-from-parse5": { "version": "8.0.3", "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz", "integrity": "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==", "dev": true, "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "devlop": "^1.0.0", "hastscript": "^9.0.0", "property-information": "^7.0.0", "vfile": "^6.0.0", "vfile-location": "^5.0.0", "web-namespaces": "^2.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/hast-util-has-property": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/hast-util-has-property/-/hast-util-has-property-3.0.0.tgz", "integrity": "sha512-MNilsvEKLFpV604hwfhVStK0usFY/QmM5zX16bo7EjnAEGofr5YyI37kzopBlZJkHD4t887i+q/C8/tr5Q94cA==", "dev": true, "license": "MIT", "dependencies": { "@types/hast": "^3.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/hast-util-is-body-ok-link": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/hast-util-is-body-ok-link/-/hast-util-is-body-ok-link-3.0.1.tgz", "integrity": "sha512-0qpnzOBLztXHbHQenVB8uNuxTnm/QBFUOmdOSsEn7GnBtyY07+ENTWVFBAnXd/zEgd9/SUG3lRY7hSIBWRgGpQ==", "dev": true, "license": "MIT", "dependencies": { "@types/hast": "^3.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/hast-util-is-element": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz", "integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==", "dev": true, "license": "MIT", "dependencies": { "@types/hast": "^3.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/hast-util-minify-whitespace": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hast-util-minify-whitespace/-/hast-util-minify-whitespace-1.0.1.tgz", "integrity": "sha512-L96fPOVpnclQE0xzdWb/D12VT5FabA7SnZOUMtL1DbXmYiHJMXZvFkIZfiMmTCNJHUeO2K9UYNXoVyfz+QHuOw==", "dev": true, "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", "hast-util-embedded": "^3.0.0", "hast-util-is-element": "^3.0.0", "hast-util-whitespace": "^3.0.0", "unist-util-is": "^6.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/hast-util-parse-selector": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", "dev": true, "license": "MIT", "dependencies": { "@types/hast": "^3.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/hast-util-phrasing": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/hast-util-phrasing/-/hast-util-phrasing-3.0.1.tgz", "integrity": "sha512-6h60VfI3uBQUxHqTyMymMZnEbNl1XmEGtOxxKYL7stY2o601COo62AWAYBQR9lZbYXYSBoxag8UpPRXK+9fqSQ==", "dev": true, "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", "hast-util-embedded": "^3.0.0", "hast-util-has-property": "^3.0.0", "hast-util-is-body-ok-link": "^3.0.0", "hast-util-is-element": "^3.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/hast-util-raw": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.1.0.tgz", "integrity": "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==", "dev": true, "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "@ungap/structured-clone": "^1.0.0", "hast-util-from-parse5": "^8.0.0", "hast-util-to-parse5": "^8.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "parse5": "^7.0.0", "unist-util-position": "^5.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0", "web-namespaces": "^2.0.0", "zwitch": "^2.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/hast-util-select": { "version": "6.0.4", "resolved": "https://registry.npmjs.org/hast-util-select/-/hast-util-select-6.0.4.tgz", "integrity": "sha512-RqGS1ZgI0MwxLaKLDxjprynNzINEkRHY2i8ln4DDjgv9ZhcYVIHN9rlpiYsqtFwrgpYU361SyWDQcGNIBVu3lw==", "dev": true, "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "bcp-47-match": "^2.0.0", "comma-separated-tokens": "^2.0.0", "css-selector-parser": "^3.0.0", "devlop": "^1.0.0", "direction": "^2.0.0", "hast-util-has-property": "^3.0.0", "hast-util-to-string": "^3.0.0", "hast-util-whitespace": "^3.0.0", "nth-check": "^2.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "unist-util-visit": "^5.0.0", "zwitch": "^2.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/hast-util-to-estree": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/hast-util-to-estree/-/hast-util-to-estree-3.1.3.tgz", "integrity": "sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w==", "dev": true, "license": "MIT", "dependencies": { "@types/estree": "^1.0.0", "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "devlop": "^1.0.0", "estree-util-attach-comments": "^3.0.0", "estree-util-is-identifier-name": "^3.0.0", "hast-util-whitespace": "^3.0.0", "mdast-util-mdx-expression": "^2.0.0", "mdast-util-mdx-jsx": "^3.0.0", "mdast-util-mdxjs-esm": "^2.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "style-to-js": "^1.0.0", "unist-util-position": "^5.0.0", "zwitch": "^2.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/hast-util-to-html": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", "dev": true, "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-whitespace": "^3.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "stringify-entities": "^4.0.0", "zwitch": "^2.0.4" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/hast-util-to-jsx-runtime": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", "dev": true, "license": "MIT", "dependencies": { "@types/estree": "^1.0.0", "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "comma-separated-tokens": "^2.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "hast-util-whitespace": "^3.0.0", "mdast-util-mdx-expression": "^2.0.0", "mdast-util-mdx-jsx": "^3.0.0", "mdast-util-mdxjs-esm": "^2.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "style-to-js": "^1.0.0", "unist-util-position": "^5.0.0", "vfile-message": "^4.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/hast-util-to-parse5": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.1.tgz", "integrity": "sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA==", "dev": true, "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "devlop": "^1.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "web-namespaces": "^2.0.0", "zwitch": "^2.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/hast-util-to-string": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/hast-util-to-string/-/hast-util-to-string-3.0.1.tgz", "integrity": "sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==", "dev": true, "license": "MIT", "dependencies": { "@types/hast": "^3.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/hast-util-to-text": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz", "integrity": "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==", "dev": true, "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "hast-util-is-element": "^3.0.0", "unist-util-find-after": "^5.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/hast-util-whitespace": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", "dev": true, "license": "MIT", "dependencies": { "@types/hast": "^3.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/hastscript": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.1.tgz", "integrity": "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==", "dev": true, "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-parse-selector": "^4.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/html-escaper": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz", "integrity": "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==", "dev": true, "license": "MIT" }, "node_modules/html-void-elements": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", "dev": true, "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, "node_modules/html-whitespace-sensitive-tag-names": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/html-whitespace-sensitive-tag-names/-/html-whitespace-sensitive-tag-names-3.0.1.tgz", "integrity": "sha512-q+310vW8zmymYHALr1da4HyXUQ0zgiIwIicEfotYPWGN0OJVEN/58IJ3A4GBYcEq3LGAZqKb+ugvP0GNB9CEAA==", "dev": true, "license": "MIT", "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/http-cache-semantics": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", "dev": true, "license": "BSD-2-Clause" }, "node_modules/i18next": { "version": "23.16.8", "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.16.8.tgz", "integrity": "sha512-06r/TitrM88Mg5FdUXAKL96dJMzgqLE5dv3ryBAra4KCwD9mJ4ndOTS95ZuymIGoE+2hzfdaMak2X11/es7ZWg==", "dev": true, "funding": [ { "type": "individual", "url": "https://locize.com" }, { "type": "individual", "url": "https://locize.com/i18next.html" }, { "type": "individual", "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" } ], "license": "MIT", "dependencies": { "@babel/runtime": "^7.23.2" } }, "node_modules/inline-style-parser": { "version": "0.2.7", "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==", "dev": true, "license": "MIT" }, "node_modules/iron-webcrypto": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/iron-webcrypto/-/iron-webcrypto-1.2.1.tgz", "integrity": "sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==", "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/brc-dd" } }, "node_modules/is-alphabetical": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", "dev": true, "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, "node_modules/is-alphanumerical": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", "dev": true, "license": "MIT", "dependencies": { "is-alphabetical": "^2.0.0", "is-decimal": "^2.0.0" }, "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, "node_modules/is-decimal": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", "dev": true, "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, "node_modules/is-docker": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", "dev": true, "license": "MIT", "bin": { "is-docker": "cli.js" }, "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/is-hexadecimal": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", "dev": true, "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, "node_modules/is-inside-container": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", "dev": true, "license": "MIT", "dependencies": { "is-docker": "^3.0.0" }, "bin": { "is-inside-container": "cli.js" }, "engines": { "node": ">=14.16" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/is-plain-obj": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", "dev": true, "license": "MIT", "engines": { "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/is-wsl": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", "dev": true, "license": "MIT", "dependencies": { "is-inside-container": "^1.0.0" }, "engines": { "node": ">=16" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/js-yaml": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, "license": "MIT", "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "node_modules/klona": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz", "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==", "dev": true, "license": "MIT", "engines": { "node": ">= 8" } }, "node_modules/longest-streak": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", "dev": true, "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, "node_modules/lru-cache": { "version": "11.2.7", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", "dev": true, "license": "BlueOak-1.0.0", "engines": { "node": "20 || >=22" } }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "node_modules/magicast": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.2.tgz", "integrity": "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==", "dev": true, "license": "MIT", "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "source-map-js": "^1.2.1" } }, "node_modules/markdown-extensions": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-2.0.0.tgz", "integrity": "sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==", "dev": true, "license": "MIT", "engines": { "node": ">=16" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/markdown-table": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", "dev": true, "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, "node_modules/mdast-util-definitions": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-6.0.0.tgz", "integrity": "sha512-scTllyX6pnYNZH/AIp/0ePz6s4cZtARxImwoPJ7kS42n+MnVsI4XbnG6d4ibehRIldYMWM2LD7ImQblVhUejVQ==", "dev": true, "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "unist-util-visit": "^5.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/mdast-util-directive": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/mdast-util-directive/-/mdast-util-directive-3.1.0.tgz", "integrity": "sha512-I3fNFt+DHmpWCYAT7quoM6lHf9wuqtI+oCOfvILnoicNIqjh5E3dEJWiXuYME2gNe8vl1iMQwyUHa7bgFmak6Q==", "dev": true, "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", "parse-entities": "^4.0.0", "stringify-entities": "^4.0.0", "unist-util-visit-parents": "^6.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/mdast-util-find-and-replace": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", "dev": true, "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", "escape-string-regexp": "^5.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/mdast-util-from-markdown": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", "dev": true, "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/mdast-util-gfm": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", "dev": true, "license": "MIT", "dependencies": { "mdast-util-from-markdown": "^2.0.0", "mdast-util-gfm-autolink-literal": "^2.0.0", "mdast-util-gfm-footnote": "^2.0.0", "mdast-util-gfm-strikethrough": "^2.0.0", "mdast-util-gfm-table": "^2.0.0", "mdast-util-gfm-task-list-item": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/mdast-util-gfm-autolink-literal": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", "dev": true, "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", "ccount": "^2.0.0", "devlop": "^1.0.0", "mdast-util-find-and-replace": "^3.0.0", "micromark-util-character": "^2.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/mdast-util-gfm-footnote": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", "dev": true, "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.1.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/mdast-util-gfm-strikethrough": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", "dev": true, "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/mdast-util-gfm-table": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", "dev": true, "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "markdown-table": "^3.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/mdast-util-gfm-task-list-item": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", "dev": true, "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/mdast-util-mdx": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/mdast-util-mdx/-/mdast-util-mdx-3.0.0.tgz", "integrity": "sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==", "dev": true, "license": "MIT", "dependencies": { "mdast-util-from-markdown": "^2.0.0", "mdast-util-mdx-expression": "^2.0.0", "mdast-util-mdx-jsx": "^3.0.0", "mdast-util-mdxjs-esm": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/mdast-util-mdx-expression": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", "dev": true, "license": "MIT", "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/mdast-util-mdx-jsx": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", "dev": true, "license": "MIT", "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "devlop": "^1.1.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", "parse-entities": "^4.0.0", "stringify-entities": "^4.0.0", "unist-util-stringify-position": "^4.0.0", "vfile-message": "^4.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/mdast-util-mdxjs-esm": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", "dev": true, "license": "MIT", "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/mdast-util-phrasing": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", "dev": true, "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", "unist-util-is": "^6.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/mdast-util-to-hast": { "version": "13.2.1", "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", "dev": true, "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "@ungap/structured-clone": "^1.0.0", "devlop": "^1.0.0", "micromark-util-sanitize-uri": "^2.0.0", "trim-lines": "^3.0.0", "unist-util-position": "^5.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/mdast-util-to-markdown": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", "dev": true, "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "longest-streak": "^3.0.0", "mdast-util-phrasing": "^4.0.0", "mdast-util-to-string": "^4.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "unist-util-visit": "^5.0.0", "zwitch": "^2.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/mdast-util-to-string": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", "dev": true, "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/mdn-data": { "version": "2.27.1", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", "dev": true, "license": "CC0-1.0" }, "node_modules/micromark": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", "dev": true, "funding": [ { "type": "GitHub Sponsors", "url": "https://github.com/sponsors/unifiedjs" }, { "type": "OpenCollective", "url": "https://opencollective.com/unified" } ], "license": "MIT", "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "node_modules/micromark-core-commonmark": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", "dev": true, "funding": [ { "type": "GitHub Sponsors", "url": "https://github.com/sponsors/unifiedjs" }, { "type": "OpenCollective", "url": "https://opencollective.com/unified" } ], "license": "MIT", "dependencies": { "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-destination": "^2.0.0", "micromark-factory-label": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-factory-title": "^2.0.0", "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-html-tag-name": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "node_modules/micromark-extension-directive": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-3.0.2.tgz", "integrity": "sha512-wjcXHgk+PPdmvR58Le9d7zQYWy+vKEU9Se44p2CrCDPiLr2FMyiT4Fyb5UFKFC66wGB3kPlgD7q3TnoqPS7SZA==", "dev": true, "license": "MIT", "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "parse-entities": "^4.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/micromark-extension-gfm": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", "dev": true, "license": "MIT", "dependencies": { "micromark-extension-gfm-autolink-literal": "^2.0.0", "micromark-extension-gfm-footnote": "^2.0.0", "micromark-extension-gfm-strikethrough": "^2.0.0", "micromark-extension-gfm-table": "^2.0.0", "micromark-extension-gfm-tagfilter": "^2.0.0", "micromark-extension-gfm-task-list-item": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-types": "^2.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/micromark-extension-gfm-autolink-literal": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", "dev": true, "license": "MIT", "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/micromark-extension-gfm-footnote": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", "dev": true, "license": "MIT", "dependencies": { "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/micromark-extension-gfm-strikethrough": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", "dev": true, "license": "MIT", "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/micromark-extension-gfm-table": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", "dev": true, "license": "MIT", "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/micromark-extension-gfm-tagfilter": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", "dev": true, "license": "MIT", "dependencies": { "micromark-util-types": "^2.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/micromark-extension-gfm-task-list-item": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", "dev": true, "license": "MIT", "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/micromark-extension-mdx-expression": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/micromark-extension-mdx-expression/-/micromark-extension-mdx-expression-3.0.1.tgz", "integrity": "sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q==", "dev": true, "funding": [ { "type": "GitHub Sponsors", "url": "https://github.com/sponsors/unifiedjs" }, { "type": "OpenCollective", "url": "https://opencollective.com/unified" } ], "license": "MIT", "dependencies": { "@types/estree": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-mdx-expression": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-events-to-acorn": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "node_modules/micromark-extension-mdx-jsx": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-3.0.2.tgz", "integrity": "sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ==", "dev": true, "license": "MIT", "dependencies": { "@types/estree": "^1.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "micromark-factory-mdx-expression": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-events-to-acorn": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "vfile-message": "^4.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/micromark-extension-mdx-md": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/micromark-extension-mdx-md/-/micromark-extension-mdx-md-2.0.0.tgz", "integrity": "sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==", "dev": true, "license": "MIT", "dependencies": { "micromark-util-types": "^2.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/micromark-extension-mdxjs": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs/-/micromark-extension-mdxjs-3.0.0.tgz", "integrity": "sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==", "dev": true, "license": "MIT", "dependencies": { "acorn": "^8.0.0", "acorn-jsx": "^5.0.0", "micromark-extension-mdx-expression": "^3.0.0", "micromark-extension-mdx-jsx": "^3.0.0", "micromark-extension-mdx-md": "^2.0.0", "micromark-extension-mdxjs-esm": "^3.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-types": "^2.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/micromark-extension-mdxjs-esm": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs-esm/-/micromark-extension-mdxjs-esm-3.0.0.tgz", "integrity": "sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==", "dev": true, "license": "MIT", "dependencies": { "@types/estree": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-events-to-acorn": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-position-from-estree": "^2.0.0", "vfile-message": "^4.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/micromark-factory-destination": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", "dev": true, "funding": [ { "type": "GitHub Sponsors", "url": "https://github.com/sponsors/unifiedjs" }, { "type": "OpenCollective", "url": "https://opencollective.com/unified" } ], "license": "MIT", "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "node_modules/micromark-factory-label": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", "dev": true, "funding": [ { "type": "GitHub Sponsors", "url": "https://github.com/sponsors/unifiedjs" }, { "type": "OpenCollective", "url": "https://opencollective.com/unified" } ], "license": "MIT", "dependencies": { "devlop": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "node_modules/micromark-factory-mdx-expression": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-2.0.3.tgz", "integrity": "sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ==", "dev": true, "funding": [ { "type": "GitHub Sponsors", "url": "https://github.com/sponsors/unifiedjs" }, { "type": "OpenCollective", "url": "https://opencollective.com/unified" } ], "license": "MIT", "dependencies": { "@types/estree": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-events-to-acorn": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-position-from-estree": "^2.0.0", "vfile-message": "^4.0.0" } }, "node_modules/micromark-factory-space": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", "dev": true, "funding": [ { "type": "GitHub Sponsors", "url": "https://github.com/sponsors/unifiedjs" }, { "type": "OpenCollective", "url": "https://opencollective.com/unified" } ], "license": "MIT", "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "node_modules/micromark-factory-title": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", "dev": true, "funding": [ { "type": "GitHub Sponsors", "url": "https://github.com/sponsors/unifiedjs" }, { "type": "OpenCollective", "url": "https://opencollective.com/unified" } ], "license": "MIT", "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "node_modules/micromark-factory-whitespace": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", "dev": true, "funding": [ { "type": "GitHub Sponsors", "url": "https://github.com/sponsors/unifiedjs" }, { "type": "OpenCollective", "url": "https://opencollective.com/unified" } ], "license": "MIT", "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "node_modules/micromark-util-character": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "dev": true, "funding": [ { "type": "GitHub Sponsors", "url": "https://github.com/sponsors/unifiedjs" }, { "type": "OpenCollective", "url": "https://opencollective.com/unified" } ], "license": "MIT", "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "node_modules/micromark-util-chunked": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", "dev": true, "funding": [ { "type": "GitHub Sponsors", "url": "https://github.com/sponsors/unifiedjs" }, { "type": "OpenCollective", "url": "https://opencollective.com/unified" } ], "license": "MIT", "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "node_modules/micromark-util-classify-character": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", "dev": true, "funding": [ { "type": "GitHub Sponsors", "url": "https://github.com/sponsors/unifiedjs" }, { "type": "OpenCollective", "url": "https://opencollective.com/unified" } ], "license": "MIT", "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "node_modules/micromark-util-combine-extensions": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", "dev": true, "funding": [ { "type": "GitHub Sponsors", "url": "https://github.com/sponsors/unifiedjs" }, { "type": "OpenCollective", "url": "https://opencollective.com/unified" } ], "license": "MIT", "dependencies": { "micromark-util-chunked": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "node_modules/micromark-util-decode-numeric-character-reference": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", "dev": true, "funding": [ { "type": "GitHub Sponsors", "url": "https://github.com/sponsors/unifiedjs" }, { "type": "OpenCollective", "url": "https://opencollective.com/unified" } ], "license": "MIT", "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "node_modules/micromark-util-decode-string": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", "dev": true, "funding": [ { "type": "GitHub Sponsors", "url": "https://github.com/sponsors/unifiedjs" }, { "type": "OpenCollective", "url": "https://opencollective.com/unified" } ], "license": "MIT", "dependencies": { "decode-named-character-reference": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "node_modules/micromark-util-encode": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", "dev": true, "funding": [ { "type": "GitHub Sponsors", "url": "https://github.com/sponsors/unifiedjs" }, { "type": "OpenCollective", "url": "https://opencollective.com/unified" } ], "license": "MIT" }, "node_modules/micromark-util-events-to-acorn": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/micromark-util-events-to-acorn/-/micromark-util-events-to-acorn-2.0.3.tgz", "integrity": "sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg==", "dev": true, "funding": [ { "type": "GitHub Sponsors", "url": "https://github.com/sponsors/unifiedjs" }, { "type": "OpenCollective", "url": "https://opencollective.com/unified" } ], "license": "MIT", "dependencies": { "@types/estree": "^1.0.0", "@types/unist": "^3.0.0", "devlop": "^1.0.0", "estree-util-visit": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "vfile-message": "^4.0.0" } }, "node_modules/micromark-util-html-tag-name": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", "dev": true, "funding": [ { "type": "GitHub Sponsors", "url": "https://github.com/sponsors/unifiedjs" }, { "type": "OpenCollective", "url": "https://opencollective.com/unified" } ], "license": "MIT" }, "node_modules/micromark-util-normalize-identifier": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", "dev": true, "funding": [ { "type": "GitHub Sponsors", "url": "https://github.com/sponsors/unifiedjs" }, { "type": "OpenCollective", "url": "https://opencollective.com/unified" } ], "license": "MIT", "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "node_modules/micromark-util-resolve-all": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", "dev": true, "funding": [ { "type": "GitHub Sponsors", "url": "https://github.com/sponsors/unifiedjs" }, { "type": "OpenCollective", "url": "https://opencollective.com/unified" } ], "license": "MIT", "dependencies": { "micromark-util-types": "^2.0.0" } }, "node_modules/micromark-util-sanitize-uri": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", "dev": true, "funding": [ { "type": "GitHub Sponsors", "url": "https://github.com/sponsors/unifiedjs" }, { "type": "OpenCollective", "url": "https://opencollective.com/unified" } ], "license": "MIT", "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "node_modules/micromark-util-subtokenize": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", "dev": true, "funding": [ { "type": "GitHub Sponsors", "url": "https://github.com/sponsors/unifiedjs" }, { "type": "OpenCollective", "url": "https://opencollective.com/unified" } ], "license": "MIT", "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "node_modules/micromark-util-symbol": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "dev": true, "funding": [ { "type": "GitHub Sponsors", "url": "https://github.com/sponsors/unifiedjs" }, { "type": "OpenCollective", "url": "https://opencollective.com/unified" } ], "license": "MIT" }, "node_modules/micromark-util-types": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", "dev": true, "funding": [ { "type": "GitHub Sponsors", "url": "https://github.com/sponsors/unifiedjs" }, { "type": "OpenCollective", "url": "https://opencollective.com/unified" } ], "license": "MIT" }, "node_modules/mrmime": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", "dev": true, "license": "MIT", "engines": { "node": ">=10" } }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true, "license": "MIT" }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "dev": true, "funding": [ { "type": "github", "url": "https://github.com/sponsors/ai" } ], "license": "MIT", "bin": { "nanoid": "bin/nanoid.cjs" }, "engines": { "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, "node_modules/neotraverse": { "version": "0.6.18", "resolved": "https://registry.npmjs.org/neotraverse/-/neotraverse-0.6.18.tgz", "integrity": "sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA==", "dev": true, "license": "MIT", "engines": { "node": ">= 10" } }, "node_modules/nlcst-to-string": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/nlcst-to-string/-/nlcst-to-string-4.0.0.tgz", "integrity": "sha512-YKLBCcUYKAg0FNlOBT6aI91qFmSiFKiluk655WzPF+DDMA02qIyy8uiRqI8QXtcFpEvll12LpL5MXqEmAZ+dcA==", "dev": true, "license": "MIT", "dependencies": { "@types/nlcst": "^2.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/node-fetch-native": { "version": "1.6.7", "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz", "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==", "dev": true, "license": "MIT" }, "node_modules/node-mock-http": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/node-mock-http/-/node-mock-http-1.0.4.tgz", "integrity": "sha512-8DY+kFsDkNXy1sJglUfuODx1/opAGJGyrTuFqEoN90oRc2Vk0ZbD4K2qmKXBBEhZQzdKHIVfEJpDU8Ak2NJEvQ==", "dev": true, "license": "MIT" }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/nth-check": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", "dev": true, "license": "BSD-2-Clause", "dependencies": { "boolbase": "^1.0.0" }, "funding": { "url": "https://github.com/fb55/nth-check?sponsor=1" } }, "node_modules/obug": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", "dev": true, "funding": [ "https://github.com/sponsors/sxzz", "https://opencollective.com/debug" ], "license": "MIT" }, "node_modules/ofetch": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/ofetch/-/ofetch-1.5.1.tgz", "integrity": "sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==", "dev": true, "license": "MIT", "dependencies": { "destr": "^2.0.5", "node-fetch-native": "^1.6.7", "ufo": "^1.6.1" } }, "node_modules/ohash": { "version": "2.0.11", "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", "dev": true, "license": "MIT" }, "node_modules/oniguruma-parser": { "version": "0.12.1", "resolved": "https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.12.1.tgz", "integrity": "sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==", "dev": true, "license": "MIT" }, "node_modules/oniguruma-to-es": { "version": "4.3.5", "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-4.3.5.tgz", "integrity": "sha512-Zjygswjpsewa0NLTsiizVuMQZbp0MDyM6lIt66OxsF21npUDlzpHi1Mgb/qhQdkb+dWFTzJmFbEWdvZgRho8eQ==", "dev": true, "license": "MIT", "dependencies": { "oniguruma-parser": "^0.12.1", "regex": "^6.1.0", "regex-recursion": "^6.0.2" } }, "node_modules/p-limit": { "version": "7.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-7.3.0.tgz", "integrity": "sha512-7cIXg/Z0M5WZRblrsOla88S4wAK+zOQQWeBYfV3qJuJXMr+LnbYjaadrFaS0JILfEDPVqHyKnZ1Z/1d6J9VVUw==", "dev": true, "license": "MIT", "dependencies": { "yocto-queue": "^1.2.1" }, "engines": { "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/p-queue": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-9.1.0.tgz", "integrity": "sha512-O/ZPaXuQV29uSLbxWBGGZO1mCQXV2BLIwUr59JUU9SoH76mnYvtms7aafH/isNSNGwuEfP6W/4xD0/TJXxrizw==", "dev": true, "license": "MIT", "dependencies": { "eventemitter3": "^5.0.1", "p-timeout": "^7.0.0" }, "engines": { "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/p-timeout": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-7.0.1.tgz", "integrity": "sha512-AxTM2wDGORHGEkPCt8yqxOTMgpfbEHqF51f/5fJCmwFC3C/zNcGT63SymH2ttOAaiIws2zVg4+izQCjrakcwHg==", "dev": true, "license": "MIT", "engines": { "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/package-manager-detector": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.6.0.tgz", "integrity": "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==", "dev": true, "license": "MIT" }, "node_modules/pagefind": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/pagefind/-/pagefind-1.4.0.tgz", "integrity": "sha512-z2kY1mQlL4J8q5EIsQkLzQjilovKzfNVhX8De6oyE6uHpfFtyBaqUpcl/XzJC/4fjD8vBDyh1zolimIcVrCn9g==", "dev": true, "license": "MIT", "bin": { "pagefind": "lib/runner/bin.cjs" }, "optionalDependencies": { "@pagefind/darwin-arm64": "1.4.0", "@pagefind/darwin-x64": "1.4.0", "@pagefind/freebsd-x64": "1.4.0", "@pagefind/linux-arm64": "1.4.0", "@pagefind/linux-x64": "1.4.0", "@pagefind/windows-x64": "1.4.0" } }, "node_modules/parse-entities": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", "dev": true, "license": "MIT", "dependencies": { "@types/unist": "^2.0.0", "character-entities-legacy": "^3.0.0", "character-reference-invalid": "^2.0.0", "decode-named-character-reference": "^1.0.0", "is-alphanumerical": "^2.0.0", "is-decimal": "^2.0.0", "is-hexadecimal": "^2.0.0" }, "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, "node_modules/parse-entities/node_modules/@types/unist": { "version": "2.0.11", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", "dev": true, "license": "MIT" }, "node_modules/parse-latin": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/parse-latin/-/parse-latin-7.0.0.tgz", "integrity": "sha512-mhHgobPPua5kZ98EF4HWiH167JWBfl4pvAIXXdbaVohtK7a6YBOy56kvhCqduqyo/f3yrHFWmqmiMg/BkBkYYQ==", "dev": true, "license": "MIT", "dependencies": { "@types/nlcst": "^2.0.0", "@types/unist": "^3.0.0", "nlcst-to-string": "^4.0.0", "unist-util-modify-children": "^4.0.0", "unist-util-visit-children": "^3.0.0", "vfile": "^6.0.0" }, "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, "node_modules/parse5": { "version": "7.3.0", "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", "dev": true, "license": "MIT", "dependencies": { "entities": "^6.0.0" }, "funding": { "url": "https://github.com/inikulin/parse5?sponsor=1" } }, "node_modules/piccolore": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/piccolore/-/piccolore-0.1.3.tgz", "integrity": "sha512-o8bTeDWjE086iwKrROaDf31K0qC/BENdm15/uH9usSC/uZjJOKb2YGiVHfLY4GhwsERiPI1jmwI2XrA7ACOxVw==", "dev": true, "license": "ISC" }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true, "license": "ISC" }, "node_modules/picomatch": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/jonschlinkert" } }, "node_modules/postcss": { "version": "8.5.8", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", "dev": true, "funding": [ { "type": "opencollective", "url": "https://opencollective.com/postcss/" }, { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/postcss" }, { "type": "github", "url": "https://github.com/sponsors/ai" } ], "license": "MIT", "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" } }, "node_modules/postcss-nested": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", "dev": true, "funding": [ { "type": "opencollective", "url": "https://opencollective.com/postcss/" }, { "type": "github", "url": "https://github.com/sponsors/ai" } ], "license": "MIT", "dependencies": { "postcss-selector-parser": "^6.1.1" }, "engines": { "node": ">=12.0" }, "peerDependencies": { "postcss": "^8.2.14" } }, "node_modules/postcss-selector-parser": { "version": "6.1.2", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", "dev": true, "license": "MIT", "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" }, "engines": { "node": ">=4" } }, "node_modules/prismjs": { "version": "1.30.0", "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", "dev": true, "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/property-information": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", "dev": true, "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, "node_modules/radix3": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/radix3/-/radix3-1.1.2.tgz", "integrity": "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==", "dev": true, "license": "MIT" }, "node_modules/readdirp": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", "dev": true, "license": "MIT", "engines": { "node": ">= 20.19.0" }, "funding": { "type": "individual", "url": "https://paulmillr.com/funding/" } }, "node_modules/recma-build-jsx": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/recma-build-jsx/-/recma-build-jsx-1.0.0.tgz", "integrity": "sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==", "dev": true, "license": "MIT", "dependencies": { "@types/estree": "^1.0.0", "estree-util-build-jsx": "^3.0.0", "vfile": "^6.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/recma-jsx": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/recma-jsx/-/recma-jsx-1.0.1.tgz", "integrity": "sha512-huSIy7VU2Z5OLv6oFLosQGGDqPqdO1iq6bWNAdhzMxSJP7RAso4fCZ1cKu8j9YHCZf3TPrq4dw3okhrylgcd7w==", "dev": true, "license": "MIT", "dependencies": { "acorn-jsx": "^5.0.0", "estree-util-to-js": "^2.0.0", "recma-parse": "^1.0.0", "recma-stringify": "^1.0.0", "unified": "^11.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" }, "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "node_modules/recma-parse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/recma-parse/-/recma-parse-1.0.0.tgz", "integrity": "sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ==", "dev": true, "license": "MIT", "dependencies": { "@types/estree": "^1.0.0", "esast-util-from-js": "^2.0.0", "unified": "^11.0.0", "vfile": "^6.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/recma-stringify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/recma-stringify/-/recma-stringify-1.0.0.tgz", "integrity": "sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==", "dev": true, "license": "MIT", "dependencies": { "@types/estree": "^1.0.0", "estree-util-to-js": "^2.0.0", "unified": "^11.0.0", "vfile": "^6.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/regex": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/regex/-/regex-6.1.0.tgz", "integrity": "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==", "dev": true, "license": "MIT", "dependencies": { "regex-utilities": "^2.3.0" } }, "node_modules/regex-recursion": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz", "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==", "dev": true, "license": "MIT", "dependencies": { "regex-utilities": "^2.3.0" } }, "node_modules/regex-utilities": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz", "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==", "dev": true, "license": "MIT" }, "node_modules/rehype": { "version": "13.0.2", "resolved": "https://registry.npmjs.org/rehype/-/rehype-13.0.2.tgz", "integrity": "sha512-j31mdaRFrwFRUIlxGeuPXXKWQxet52RBQRvCmzl5eCefn/KGbomK5GMHNMsOJf55fgo3qw5tST5neDuarDYR2A==", "dev": true, "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", "rehype-parse": "^9.0.0", "rehype-stringify": "^10.0.0", "unified": "^11.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/rehype-expressive-code": { "version": "0.41.7", "resolved": "https://registry.npmjs.org/rehype-expressive-code/-/rehype-expressive-code-0.41.7.tgz", "integrity": "sha512-25f8ZMSF1d9CMscX7Cft0TSQIqdwjce2gDOvQ+d/w0FovsMwrSt3ODP4P3Z7wO1jsIJ4eYyaDRnIR/27bd/EMQ==", "dev": true, "license": "MIT", "dependencies": { "expressive-code": "^0.41.7" } }, "node_modules/rehype-format": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/rehype-format/-/rehype-format-5.0.1.tgz", "integrity": "sha512-zvmVru9uB0josBVpr946OR8ui7nJEdzZobwLOOqHb/OOD88W0Vk2SqLwoVOj0fM6IPCCO6TaV9CvQvJMWwukFQ==", "dev": true, "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", "hast-util-format": "^1.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/rehype-parse": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/rehype-parse/-/rehype-parse-9.0.1.tgz", "integrity": "sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag==", "dev": true, "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", "hast-util-from-html": "^2.0.0", "unified": "^11.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/rehype-raw": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz", "integrity": "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==", "dev": true, "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", "hast-util-raw": "^9.0.0", "vfile": "^6.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/rehype-recma": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/rehype-recma/-/rehype-recma-1.0.0.tgz", "integrity": "sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==", "dev": true, "license": "MIT", "dependencies": { "@types/estree": "^1.0.0", "@types/hast": "^3.0.0", "hast-util-to-estree": "^3.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/rehype-stringify": { "version": "10.0.1", "resolved": "https://registry.npmjs.org/rehype-stringify/-/rehype-stringify-10.0.1.tgz", "integrity": "sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA==", "dev": true, "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", "hast-util-to-html": "^9.0.0", "unified": "^11.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/remark-directive": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/remark-directive/-/remark-directive-3.0.1.tgz", "integrity": "sha512-gwglrEQEZcZYgVyG1tQuA+h58EZfq5CSULw7J90AFuCTyib1thgHPoqQ+h9iFvU6R+vnZ5oNFQR5QKgGpk741A==", "dev": true, "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-directive": "^3.0.0", "micromark-extension-directive": "^3.0.0", "unified": "^11.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/remark-gfm": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", "dev": true, "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-gfm": "^3.0.0", "micromark-extension-gfm": "^3.0.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "unified": "^11.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/remark-mdx": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-3.1.1.tgz", "integrity": "sha512-Pjj2IYlUY3+D8x00UJsIOg5BEvfMyeI+2uLPn9VO9Wg4MEtN/VTIq2NEJQfde9PnX15KgtHyl9S0BcTnWrIuWg==", "dev": true, "license": "MIT", "dependencies": { "mdast-util-mdx": "^3.0.0", "micromark-extension-mdxjs": "^3.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/remark-parse": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", "dev": true, "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "micromark-util-types": "^2.0.0", "unified": "^11.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/remark-rehype": { "version": "11.1.2", "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", "dev": true, "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "mdast-util-to-hast": "^13.0.0", "unified": "^11.0.0", "vfile": "^6.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/remark-smartypants": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/remark-smartypants/-/remark-smartypants-3.0.2.tgz", "integrity": "sha512-ILTWeOriIluwEvPjv67v7Blgrcx+LZOkAUVtKI3putuhlZm84FnqDORNXPPm+HY3NdZOMhyDwZ1E+eZB/Df5dA==", "dev": true, "license": "MIT", "dependencies": { "retext": "^9.0.0", "retext-smartypants": "^6.0.0", "unified": "^11.0.4", "unist-util-visit": "^5.0.0" }, "engines": { "node": ">=16.0.0" } }, "node_modules/remark-stringify": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", "dev": true, "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-to-markdown": "^2.0.0", "unified": "^11.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/retext": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/retext/-/retext-9.0.0.tgz", "integrity": "sha512-sbMDcpHCNjvlheSgMfEcVrZko3cDzdbe1x/e7G66dFp0Ff7Mldvi2uv6JkJQzdRcvLYE8CA8Oe8siQx8ZOgTcA==", "dev": true, "license": "MIT", "dependencies": { "@types/nlcst": "^2.0.0", "retext-latin": "^4.0.0", "retext-stringify": "^4.0.0", "unified": "^11.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/retext-latin": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/retext-latin/-/retext-latin-4.0.0.tgz", "integrity": "sha512-hv9woG7Fy0M9IlRQloq/N6atV82NxLGveq+3H2WOi79dtIYWN8OaxogDm77f8YnVXJL2VD3bbqowu5E3EMhBYA==", "dev": true, "license": "MIT", "dependencies": { "@types/nlcst": "^2.0.0", "parse-latin": "^7.0.0", "unified": "^11.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/retext-smartypants": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/retext-smartypants/-/retext-smartypants-6.2.0.tgz", "integrity": "sha512-kk0jOU7+zGv//kfjXEBjdIryL1Acl4i9XNkHxtM7Tm5lFiCog576fjNC9hjoR7LTKQ0DsPWy09JummSsH1uqfQ==", "dev": true, "license": "MIT", "dependencies": { "@types/nlcst": "^2.0.0", "nlcst-to-string": "^4.0.0", "unist-util-visit": "^5.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/retext-stringify": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/retext-stringify/-/retext-stringify-4.0.0.tgz", "integrity": "sha512-rtfN/0o8kL1e+78+uxPTqu1Klt0yPzKuQ2BfWwwfgIUSayyzxpM1PJzkKt4V8803uB9qSy32MvI7Xep9khTpiA==", "dev": true, "license": "MIT", "dependencies": { "@types/nlcst": "^2.0.0", "nlcst-to-string": "^4.0.0", "unified": "^11.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/rollup": { "version": "4.60.0", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.0.tgz", "integrity": "sha512-yqjxruMGBQJ2gG4HtjZtAfXArHomazDHoFwFFmZZl0r7Pdo7qCIXKqKHZc8yeoMgzJJ+pO6pEEHa+V7uzWlrAQ==", "dev": true, "license": "MIT", "dependencies": { "@types/estree": "1.0.8" }, "bin": { "rollup": "dist/bin/rollup" }, "engines": { "node": ">=18.0.0", "npm": ">=8.0.0" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.60.0", "@rollup/rollup-android-arm64": "4.60.0", "@rollup/rollup-darwin-arm64": "4.60.0", "@rollup/rollup-darwin-x64": "4.60.0", "@rollup/rollup-freebsd-arm64": "4.60.0", "@rollup/rollup-freebsd-x64": "4.60.0", "@rollup/rollup-linux-arm-gnueabihf": "4.60.0", "@rollup/rollup-linux-arm-musleabihf": "4.60.0", "@rollup/rollup-linux-arm64-gnu": "4.60.0", "@rollup/rollup-linux-arm64-musl": "4.60.0", "@rollup/rollup-linux-loong64-gnu": "4.60.0", "@rollup/rollup-linux-loong64-musl": "4.60.0", "@rollup/rollup-linux-ppc64-gnu": "4.60.0", "@rollup/rollup-linux-ppc64-musl": "4.60.0", "@rollup/rollup-linux-riscv64-gnu": "4.60.0", "@rollup/rollup-linux-riscv64-musl": "4.60.0", "@rollup/rollup-linux-s390x-gnu": "4.60.0", "@rollup/rollup-linux-x64-gnu": "4.60.0", "@rollup/rollup-linux-x64-musl": "4.60.0", "@rollup/rollup-openbsd-x64": "4.60.0", "@rollup/rollup-openharmony-arm64": "4.60.0", "@rollup/rollup-win32-arm64-msvc": "4.60.0", "@rollup/rollup-win32-ia32-msvc": "4.60.0", "@rollup/rollup-win32-x64-gnu": "4.60.0", "@rollup/rollup-win32-x64-msvc": "4.60.0", "fsevents": "~2.3.2" } }, "node_modules/sax": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz", "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", "dev": true, "license": "BlueOak-1.0.0", "engines": { "node": ">=11.0.0" } }, "node_modules/semver": { "version": "7.7.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" }, "engines": { "node": ">=10" } }, "node_modules/sharp": { "version": "0.34.5", "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", "dev": true, "hasInstallScript": true, "license": "Apache-2.0", "optional": true, "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", "semver": "^7.7.3" }, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.5", "@img/sharp-darwin-x64": "0.34.5", "@img/sharp-libvips-darwin-arm64": "1.2.4", "@img/sharp-libvips-darwin-x64": "1.2.4", "@img/sharp-libvips-linux-arm": "1.2.4", "@img/sharp-libvips-linux-arm64": "1.2.4", "@img/sharp-libvips-linux-ppc64": "1.2.4", "@img/sharp-libvips-linux-riscv64": "1.2.4", "@img/sharp-libvips-linux-s390x": "1.2.4", "@img/sharp-libvips-linux-x64": "1.2.4", "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", "@img/sharp-libvips-linuxmusl-x64": "1.2.4", "@img/sharp-linux-arm": "0.34.5", "@img/sharp-linux-arm64": "0.34.5", "@img/sharp-linux-ppc64": "0.34.5", "@img/sharp-linux-riscv64": "0.34.5", "@img/sharp-linux-s390x": "0.34.5", "@img/sharp-linux-x64": "0.34.5", "@img/sharp-linuxmusl-arm64": "0.34.5", "@img/sharp-linuxmusl-x64": "0.34.5", "@img/sharp-wasm32": "0.34.5", "@img/sharp-win32-arm64": "0.34.5", "@img/sharp-win32-ia32": "0.34.5", "@img/sharp-win32-x64": "0.34.5" } }, "node_modules/shiki": { "version": "3.23.0", "resolved": "https://registry.npmjs.org/shiki/-/shiki-3.23.0.tgz", "integrity": "sha512-55Dj73uq9ZXL5zyeRPzHQsK7Nbyt6Y10k5s7OjuFZGMhpp4r/rsLBH0o/0fstIzX1Lep9VxefWljK/SKCzygIA==", "dev": true, "license": "MIT", "dependencies": { "@shikijs/core": "3.23.0", "@shikijs/engine-javascript": "3.23.0", "@shikijs/engine-oniguruma": "3.23.0", "@shikijs/langs": "3.23.0", "@shikijs/themes": "3.23.0", "@shikijs/types": "3.23.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", "dev": true, "license": "MIT" }, "node_modules/sitemap": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/sitemap/-/sitemap-9.0.1.tgz", "integrity": "sha512-S6hzjGJSG3d6if0YoF5kTyeRJvia6FSTBroE5fQ0bu1QNxyJqhhinfUsXi9fH3MgtXODWvwo2BDyQSnhPQ88uQ==", "dev": true, "license": "MIT", "dependencies": { "@types/node": "^24.9.2", "@types/sax": "^1.2.1", "arg": "^5.0.0", "sax": "^1.4.1" }, "bin": { "sitemap": "dist/esm/cli.js" }, "engines": { "node": ">=20.19.5", "npm": ">=10.8.2" } }, "node_modules/smol-toml": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.6.1.tgz", "integrity": "sha512-dWUG8F5sIIARXih1DTaQAX4SsiTXhInKf1buxdY9DIg4ZYPZK5nGM1VRIYmEbDbsHt7USo99xSLFu5Q1IqTmsg==", "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">= 18" }, "funding": { "url": "https://github.com/sponsors/cyyynthia" } }, "node_modules/source-map": { "version": "0.7.6", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">= 12" } }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } }, "node_modules/space-separated-tokens": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", "dev": true, "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, "node_modules/stream-replace-string": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/stream-replace-string/-/stream-replace-string-2.0.0.tgz", "integrity": "sha512-TlnjJ1C0QrmxRNrON00JvaFFlNh5TTG00APw23j74ET7gkQpTASi6/L2fuiav8pzK715HXtUeClpBTw2NPSn6w==", "dev": true, "license": "MIT" }, "node_modules/stringify-entities": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", "dev": true, "license": "MIT", "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" }, "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, "node_modules/style-to-js": { "version": "1.1.21", "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==", "dev": true, "license": "MIT", "dependencies": { "style-to-object": "1.0.14" } }, "node_modules/style-to-object": { "version": "1.0.14", "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz", "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==", "dev": true, "license": "MIT", "dependencies": { "inline-style-parser": "0.2.7" } }, "node_modules/svgo": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.1.tgz", "integrity": "sha512-XDpWUOPC6FEibaLzjfe0ucaV0YrOjYotGJO1WpF0Zd+n6ZGEQUsSugaoLq9QkEZtAfQIxT42UChcssDVPP3+/w==", "dev": true, "license": "MIT", "dependencies": { "commander": "^11.1.0", "css-select": "^5.1.0", "css-tree": "^3.0.1", "css-what": "^6.1.0", "csso": "^5.0.5", "picocolors": "^1.1.1", "sax": "^1.5.0" }, "bin": { "svgo": "bin/svgo.js" }, "engines": { "node": ">=16" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/svgo" } }, "node_modules/tiny-inflate": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", "dev": true, "license": "MIT" }, "node_modules/tinyclip": { "version": "0.1.12", "resolved": "https://registry.npmjs.org/tinyclip/-/tinyclip-0.1.12.tgz", "integrity": "sha512-Ae3OVUqifDw0wBriIBS7yVaW44Dp6eSHQcyq4Igc7eN2TJH/2YsicswaW+J/OuMvhpDPOKEgpAZCjkb4hpoyeA==", "dev": true, "license": "MIT", "engines": { "node": "^16.14.0 || >= 17.3.0" } }, "node_modules/tinyexec": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.4.tgz", "integrity": "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==", "dev": true, "license": "MIT", "engines": { "node": ">=18" } }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", "dev": true, "license": "MIT", "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" }, "engines": { "node": ">=12.0.0" }, "funding": { "url": "https://github.com/sponsors/SuperchupuDev" } }, "node_modules/trim-lines": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", "dev": true, "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, "node_modules/trough": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", "dev": true, "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, "node_modules/tsconfck": { "version": "3.1.6", "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.6.tgz", "integrity": "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==", "dev": true, "license": "MIT", "bin": { "tsconfck": "bin/tsconfck.js" }, "engines": { "node": "^18 || >=20" }, "peerDependencies": { "typescript": "^5.0.0" }, "peerDependenciesMeta": { "typescript": { "optional": true } } }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "dev": true, "license": "0BSD", "optional": true }, "node_modules/ufo": { "version": "1.6.3", "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz", "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==", "dev": true, "license": "MIT" }, "node_modules/ultrahtml": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/ultrahtml/-/ultrahtml-1.6.0.tgz", "integrity": "sha512-R9fBn90VTJrqqLDwyMph+HGne8eqY1iPfYhPzZrvKpIfwkWZbcYlfpsb8B9dTvBfpy1/hqAD7Wi8EKfP9e8zdw==", "dev": true, "license": "MIT" }, "node_modules/uncrypto": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/uncrypto/-/uncrypto-0.1.3.tgz", "integrity": "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==", "dev": true, "license": "MIT" }, "node_modules/undici-types": { "version": "7.16.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", "dev": true, "license": "MIT" }, "node_modules/unified": { "version": "11.0.5", "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", "dev": true, "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", "bail": "^2.0.0", "devlop": "^1.0.0", "extend": "^3.0.0", "is-plain-obj": "^4.0.0", "trough": "^2.0.0", "vfile": "^6.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/unifont": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/unifont/-/unifont-0.7.4.tgz", "integrity": "sha512-oHeis4/xl42HUIeHuNZRGEvxj5AaIKR+bHPNegRq5LV1gdc3jundpONbjglKpihmJf+dswygdMJn3eftGIMemg==", "dev": true, "license": "MIT", "dependencies": { "css-tree": "^3.1.0", "ofetch": "^1.5.1", "ohash": "^2.0.11" } }, "node_modules/unist-util-find-after": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-5.0.0.tgz", "integrity": "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==", "dev": true, "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/unist-util-is": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", "dev": true, "license": "MIT", "dependencies": { "@types/unist": "^3.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/unist-util-modify-children": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/unist-util-modify-children/-/unist-util-modify-children-4.0.0.tgz", "integrity": "sha512-+tdN5fGNddvsQdIzUF3Xx82CU9sMM+fA0dLgR9vOmT0oPT2jH+P1nd5lSqfCfXAw+93NhcXNY2qqvTUtE4cQkw==", "dev": true, "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", "array-iterate": "^2.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/unist-util-position": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", "dev": true, "license": "MIT", "dependencies": { "@types/unist": "^3.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/unist-util-position-from-estree": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unist-util-position-from-estree/-/unist-util-position-from-estree-2.0.0.tgz", "integrity": "sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==", "dev": true, "license": "MIT", "dependencies": { "@types/unist": "^3.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/unist-util-remove-position": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-5.0.0.tgz", "integrity": "sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==", "dev": true, "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", "unist-util-visit": "^5.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/unist-util-stringify-position": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", "dev": true, "license": "MIT", "dependencies": { "@types/unist": "^3.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/unist-util-visit": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", "dev": true, "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/unist-util-visit-children": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/unist-util-visit-children/-/unist-util-visit-children-3.0.0.tgz", "integrity": "sha512-RgmdTfSBOg04sdPcpTSD1jzoNBjt9a80/ZCzp5cI9n1qPzLZWF9YdvWGN2zmTumP1HWhXKdUWexjy/Wy/lJ7tA==", "dev": true, "license": "MIT", "dependencies": { "@types/unist": "^3.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/unist-util-visit-parents": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", "dev": true, "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/unstorage": { "version": "1.17.4", "resolved": "https://registry.npmjs.org/unstorage/-/unstorage-1.17.4.tgz", "integrity": "sha512-fHK0yNg38tBiJKp/Vgsq4j0JEsCmgqH58HAn707S7zGkArbZsVr/CwINoi+nh3h98BRCwKvx1K3Xg9u3VV83sw==", "dev": true, "license": "MIT", "dependencies": { "anymatch": "^3.1.3", "chokidar": "^5.0.0", "destr": "^2.0.5", "h3": "^1.15.5", "lru-cache": "^11.2.0", "node-fetch-native": "^1.6.7", "ofetch": "^1.5.1", "ufo": "^1.6.3" }, "peerDependencies": { "@azure/app-configuration": "^1.8.0", "@azure/cosmos": "^4.2.0", "@azure/data-tables": "^13.3.0", "@azure/identity": "^4.6.0", "@azure/keyvault-secrets": "^4.9.0", "@azure/storage-blob": "^12.26.0", "@capacitor/preferences": "^6 || ^7 || ^8", "@deno/kv": ">=0.9.0", "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", "@planetscale/database": "^1.19.0", "@upstash/redis": "^1.34.3", "@vercel/blob": ">=0.27.1", "@vercel/functions": "^2.2.12 || ^3.0.0", "@vercel/kv": "^1 || ^2 || ^3", "aws4fetch": "^1.0.20", "db0": ">=0.2.1", "idb-keyval": "^6.2.1", "ioredis": "^5.4.2", "uploadthing": "^7.4.4" }, "peerDependenciesMeta": { "@azure/app-configuration": { "optional": true }, "@azure/cosmos": { "optional": true }, "@azure/data-tables": { "optional": true }, "@azure/identity": { "optional": true }, "@azure/keyvault-secrets": { "optional": true }, "@azure/storage-blob": { "optional": true }, "@capacitor/preferences": { "optional": true }, "@deno/kv": { "optional": true }, "@netlify/blobs": { "optional": true }, "@planetscale/database": { "optional": true }, "@upstash/redis": { "optional": true }, "@vercel/blob": { "optional": true }, "@vercel/functions": { "optional": true }, "@vercel/kv": { "optional": true }, "aws4fetch": { "optional": true }, "db0": { "optional": true }, "idb-keyval": { "optional": true }, "ioredis": { "optional": true }, "uploadthing": { "optional": true } } }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "dev": true, "license": "MIT" }, "node_modules/vfile": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", "dev": true, "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/vfile-location": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz", "integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==", "dev": true, "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", "vfile": "^6.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/vfile-message": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", "dev": true, "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, "node_modules/vite": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "dev": true, "license": "MIT", "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "bin": { "vite": "bin/vite.js" }, "engines": { "node": "^20.19.0 || >=22.12.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "peerDependenciesMeta": { "@types/node": { "optional": true }, "jiti": { "optional": true }, "less": { "optional": true }, "lightningcss": { "optional": true }, "sass": { "optional": true }, "sass-embedded": { "optional": true }, "stylus": { "optional": true }, "sugarss": { "optional": true }, "terser": { "optional": true }, "tsx": { "optional": true }, "yaml": { "optional": true } } }, "node_modules/vitefu": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.2.tgz", "integrity": "sha512-zpKATdUbzbsycPFBN71nS2uzBUQiVnFoOrr2rvqv34S1lcAgMKKkjWleLGeiJlZ8lwCXvtWaRn7R3ZC16SYRuw==", "dev": true, "license": "MIT", "workspaces": [ "tests/deps/*", "tests/projects/*", "tests/projects/workspace/packages/*" ], "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-beta.0" }, "peerDependenciesMeta": { "vite": { "optional": true } } }, "node_modules/web-namespaces": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", "dev": true, "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, "node_modules/which-pm-runs": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.1.0.tgz", "integrity": "sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA==", "dev": true, "license": "MIT", "engines": { "node": ">=4" } }, "node_modules/xxhash-wasm": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/xxhash-wasm/-/xxhash-wasm-1.1.0.tgz", "integrity": "sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA==", "dev": true, "license": "MIT" }, "node_modules/yargs-parser": { "version": "22.0.0", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", "dev": true, "license": "ISC", "engines": { "node": "^20.19.0 || ^22.12.0 || >=23" } }, "node_modules/yocto-queue": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", "dev": true, "license": "MIT", "engines": { "node": ">=12.20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/zod": { "version": "3.25.76", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" } }, "node_modules/zwitch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", "dev": true, "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } } } } taskwarrior-tui-0.27.0/docs/package.json000064400000000000000000000006301046102023000162760ustar 00000000000000{ "name": "taskwarrior-tui-docs", "private": true, "type": "module", "engines": { "node": ">=22.11.0" }, "scripts": { "dev": "astro dev --host", "start": "astro dev --host", "build": "astro build", "preview": "astro preview --host", "check": "astro check" }, "devDependencies": { "@astrojs/starlight": "^0.38.2", "astro": "^6.1.0", "zod": "^3.25.76" } } taskwarrior-tui-0.27.0/docs/public/favicon.svg000064400000000000000000000015701046102023000174400ustar 00000000000000 taskwarrior-tui-0.27.0/docs/src/content/docs/configuration/advanced.md000064400000000000000000000172451046102023000241510ustar 00000000000000--- title: Advanced Configuration description: Configure taskwarrior-tui report behavior, styles, shortcuts, background tasks, and environment overrides. --- `taskwarrior-tui` parses the output of `task show` to get configuration data. This allows `taskwarrior-tui` to use the same defaults as `taskwarrior` and configure additional options as required. ## `taskrc` Configuration File Options Other `taskwarrior-tui` configuration options are possible using Taskwarrior user-defined attributes. All `taskwarrior-tui` specific configuration options begin with `uda.taskwarrior-tui.`. The following is the full list of options available and their default values if they are not defined in your `taskrc` file. ```plaintext uda.taskwarrior-tui.selection.indicator=• uda.taskwarrior-tui.selection.bold=yes uda.taskwarrior-tui.selection.italic=no uda.taskwarrior-tui.selection.dim=no uda.taskwarrior-tui.selection.blink=no uda.taskwarrior-tui.selection.reverse=no uda.taskwarrior-tui.mark.indicator=✔ uda.taskwarrior-tui.unmark.indicator= uda.taskwarrior-tui.mark-selection.indicator=⦿ uda.taskwarrior-tui.unmark-selection.indicator=⦾ uda.taskwarrior-tui.calendar.months-per-row=4 uda.taskwarrior-tui.task-report.info-show=true uda.taskwarrior-tui.task-report.info-location=auto uda.taskwarrior-tui.task-report.looping=true uda.taskwarrior-tui.task-report.use-alternate-style=true uda.taskwarrior-tui.task-report.jump-on-task-add=true uda.taskwarrior-tui.task-report.prompt-on-undo=false uda.taskwarrior-tui.task-report.prompt-on-delete=false uda.taskwarrior-tui.task-report.prompt-on-done=false uda.taskwarrior-tui.style.report.selection= uda.taskwarrior-tui.style.context.active=black on rgb444 uda.taskwarrior-tui.style.report-menu.active=black on rgb444 uda.taskwarrior-tui.style.calendar.title=black on rgb444 uda.taskwarrior-tui.style.report.scrollbar=black uda.taskwarrior-tui.scrollbar.indicator=█ uda.taskwarrior-tui.style.report.scrollbar.area=white uda.taskwarrior-tui.scrollbar.area=║ uda.taskwarrior-tui.task-report.next.filter=$(task show report.next.filter) uda.taskwarrior-tui.task-report.auto-insert-double-quotes-on-add=true uda.taskwarrior-tui.task-report.auto-insert-double-quotes-on-annotate=true uda.taskwarrior-tui.task-report.auto-insert-double-quotes-on-log=true uda.taskwarrior-tui.task-report.reset-filter-on-esc=true uda.taskwarrior-tui.context-menu.select-on-move=false uda.taskwarrior-tui.context-menu.close-on-select=true uda.taskwarrior-tui.report-menu.select-on-move=false uda.taskwarrior-tui.report-menu.close-on-select=true uda.taskwarrior-tui.tabs.change-focus-rotate=false uda.taskwarrior-tui.quick-tag.name=next # UI chrome styles (support all Taskwarrior color formats) uda.taskwarrior-tui.style.title= # default: LightCyan foreground uda.taskwarrior-tui.style.title.border= # default: White foreground uda.taskwarrior-tui.style.help.gauge= # default: Gray foreground uda.taskwarrior-tui.style.command.error= # default: Red foreground ``` See [color configuration](./colors.md) for supported color formats and additional TUI style keys such as `uda.taskwarrior-tui.style.navbar` and `uda.taskwarrior-tui.style.command`. The `uda.taskwarrior-tui.task-report.next.filter` variable defines the default view at program startup. Set this to any preconfigured report from `task reports`, or create your own report in Taskwarrior and specify its name here. ## Legacy Config Aliases The following older keys are still accepted for backward compatibility, but they are legacy, should be migrated to the newer names, and will be removed in a future release: ```plaintext uda.taskwarrior-tui.task-report.show-info -> uda.taskwarrior-tui.task-report.info-show uda.taskwarrior-tui.tasklist.vertical -> uda.taskwarrior-tui.task-report.info-location ``` For the legacy `tasklist.vertical` option, `true` maps to `bottom` and `false` maps to `right`. If both a legacy key and its newer replacement are set, the newer key takes precedence. ## Command-Line Options `-r` specifies a report to be shown and overrides `uda.taskwarrior-tui.task-report.next.filter` for that instance. ## Configure Quick Tag The quick-tag action toggles a single tag on the selected task. By default it uses the tag `next`, and the default keybinding is `t`. Configure the tag name in your `taskrc` with a bare tag value: ```plaintext uda.taskwarrior-tui.quick-tag.name=next ``` Do not include a leading `+`. `taskwarrior-tui` adds and removes the tag for you, so `next` becomes `+next` or `-next` when you trigger the quick-tag action. ## Configure User-Defined Shortcuts You can configure shortcuts from your Taskwarrior `taskrc` file (default: `~/.taskrc`) by mapping them to executable files: ```plaintext uda.taskwarrior-tui.shortcuts.1=~/.config/taskwarrior-tui/shortcut-scripts/add-personal-tag.sh uda.taskwarrior-tui.shortcuts.2=~/.config/taskwarrior-tui/shortcut-scripts/sync.sh ... ``` The executable file can be placed in any location. To make a file executable: 1. Run `chmod +x /path/to/script` to modify the executable flag. 2. Add `#!/usr/bin/env bash`, `#!/usr/bin/env python`, or whatever is appropriate for your script. By default, keys `1`-`9` are available to run shortcuts. When you hit the shortcut, the script will be executed with `selected_tasks_uuid` as an argument: ```bash ~/.config/taskwarrior-tui/shortcut-scripts/add-personal-tag.sh $selected_tasks_uuid ``` For example, you can add the `personal` tag to the currently selected task with the following script in `~/.config/taskwarrior-tui/shortcut-scripts/add-personal-tag.sh`: ```plaintext task rc.bulk=0 rc.confirmation=off rc.dependency.confirmation=off rc.recurrence.confirmation=off "$@" modify +personal ``` By default, shortcuts are linked to the `1-9` number row keys. They can be customized as any other keys through `uda.taskwarrior-tui.keyconfig.shortcut1=`. For example: ```plaintext uda.taskwarrior-tui.keyconfig.shortcut1=n ``` You can set up shortcuts to run `task sync` or any custom script that you like. ## Configure One Background Task You can configure one background task to run periodically: ```plaintext uda.taskwarrior-tui.background_process=task sync uda.taskwarrior-tui.background_process_period=60 ``` This runs `task sync` every 60 seconds. If `background_process` is an empty string, which is the default, then no process is run. Only if `background_process` is defined and runs successfully will it continue to run every `background_process_period` seconds, which defaults to 60. If it fails even once, it will not be run again until `taskwarrior-tui` is restarted. ## Environment Variables ### `TASKWARRIOR_TUI_DATA` Overrides the `taskwarrior-tui` data directory used for logs and history files. By default this uses your platform's local data directory, typically `~/.local/share/taskwarrior-tui/` on Linux. This is useful when debugging because it lets you collect `taskwarrior-tui.log` in a temporary or project-local folder: ```bash TASKWARRIOR_TUI_DATA=/tmp/taskwarrior-tui-debug taskwarrior-tui ``` ### `TASKWARRIOR_TUI_LOG_LEVEL` Controls how much information is written to `taskwarrior-tui.log`. Supported values are `off`, `warn`, `info`, `debug`, and `trace`. Examples: ```bash TASKWARRIOR_TUI_LOG_LEVEL=debug taskwarrior-tui TASKWARRIOR_TUI_LOG_LEVEL=trace taskwarrior-tui ``` ### `TASKWARRIOR_TUI_TASKWARRIOR_CLI` Overrides the path to the Taskwarrior executable. Defaults to `task`, resolved via `PATH`. Use this if Taskwarrior is installed in a non-standard location, or if you want to wrap the `task` binary with a script: ```bash TASKWARRIOR_TUI_TASKWARRIOR_CLI=/usr/local/bin/task taskwarrior-tui ``` You can also set it permanently in your shell profile: ```bash export TASKWARRIOR_TUI_TASKWARRIOR_CLI=/usr/local/bin/task ``` taskwarrior-tui-0.27.0/docs/src/content/docs/configuration/colors.md000064400000000000000000000076161046102023000237060ustar 00000000000000--- title: Color Configuration description: Reuse Taskwarrior color settings and modifiers inside taskwarrior-tui. --- `taskwarrior-tui` reads color values from your Taskwarrior `taskrc` file (default: `~/.taskrc`). ![](https://user-images.githubusercontent.com/1813121/96684390-bf173e80-1338-11eb-971c-ae64233d142e.png) For example, `color.active` is used to style the active task. If you would like to try it, open your `taskrc` file and change `color.active=white on blue`. So `color.active` will take precedence over `color.overdue`. You can see what `color.active` is by running `task show color.active` in your shell. The following color attributes are supported: ```plaintext color.deleted color.completed color.active color.alternate color.overdue color.scheduled color.due.today color.due color.blocked color.blocking color.recurring color.tagged ``` `color.alternate` is applied to every other row in the task report by default. If you want to keep that Taskwarrior color defined but disable it in the TUI, set `uda.taskwarrior-tui.task-report.use-alternate-style=false`. Similarly, `taskwarrior-tui`-specific styles can also be customized in `taskrc` with the following attributes: ```plaintext uda.taskwarrior-tui.style.context.active uda.taskwarrior-tui.style.calendar.title uda.taskwarrior-tui.style.navbar uda.taskwarrior-tui.style.command uda.taskwarrior-tui.style.report.scrollbar uda.taskwarrior-tui.style.report.scrollbar.area ``` ## Color Formats All color formats supported by Taskwarrior are recognized: ### Named colors (16-color) ```plaintext black red green yellow blue magenta cyan white ``` Use `on ` to set the background: ```plaintext red on blue # red foreground, blue background on yellow # default foreground, yellow background ``` ### High-intensity (bright) colors Use the `bright` prefix on backgrounds to get the high-intensity variant (terminal colors 8-15): ```plaintext black on bright green # bright green background white on bright black # bright black (dark gray) background ``` ### 256-color indexed Use `color0` through `color255` to access the full 256-color palette: ```plaintext color196 # bright red color60 on color60 # foreground and background using index 60 ``` ### Grayscale ramp Use `gray0` through `gray23` (or `grey0`-`grey23`) to access the 24-step grayscale ramp (terminal colors 232-255): ```plaintext gray5 # dark gray (Color::Indexed(237)) white on gray10 # white text on mid-gray background ``` ### RGB color cube Use `rgbRGB` where `R`, `G`, and `B` are each a digit from `0` to `5` to address the `6x6x6` color cube (terminal colors 16-231): ```plaintext rgb500 # bright red (Color::Indexed(196)) rgb050 # bright green rgb005 # bright blue rgb444 # medium gray-ish ``` ## Modifiers The following text modifiers can be combined with any color: | Modifier | Effect | | --- | --- | | `bold` | Bold text | | `underline` | Underlined text | | `inverse` | Swapped foreground/background | | `italic` | Italic text | | `strikethrough` | Strikethrough text | Multiple modifiers can be combined: ```plaintext bold underline red on blue italic color111 on color60 ``` ## `bold` vs `bright` - `bold` is a text attribute. It makes text bold and uses the regular color index. - `bright` is a color variant. For backgrounds, it selects the high-intensity color (index + 8). ```plaintext bold red # regular red (#1) + bold attribute on bright red # high-intensity red background (#9) bold on bright red # bold text + high-intensity red background ``` ## Example: TokyoNight Moon Theme ```plaintext color.active=color111 on color60 color.overdue=color203 color.due.today=color215 color.due=color111 color.blocked=color245 color.tagged=color141 color.recurring=color147 color.scheduled=color109 ``` See [advanced configuration](./advanced.md) for TUI-specific style keys. taskwarrior-tui-0.27.0/docs/src/content/docs/configuration/keys.md000064400000000000000000000024531046102023000233520ustar 00000000000000--- title: Key Configuration description: Customize taskwarrior-tui keybindings through your Taskwarrior configuration file. --- Configure `taskwarrior-tui` using `~/.taskrc`: `taskwarrior-tui` reads values from your Taskwarrior `taskrc` file (default: `~/.taskrc`). ```plaintext uda.taskwarrior-tui.keyconfig.quit=q uda.taskwarrior-tui.keyconfig.refresh=r uda.taskwarrior-tui.keyconfig.go-to-bottom=G uda.taskwarrior-tui.keyconfig.go-to-top=g uda.taskwarrior-tui.keyconfig.down=j uda.taskwarrior-tui.keyconfig.up=k uda.taskwarrior-tui.keyconfig.page-down=J uda.taskwarrior-tui.keyconfig.page-up=K uda.taskwarrior-tui.keyconfig.delete=x uda.taskwarrior-tui.keyconfig.done=d uda.taskwarrior-tui.keyconfig.start-stop=s uda.taskwarrior-tui.keyconfig.quick-tag=t uda.taskwarrior-tui.keyconfig.undo=u uda.taskwarrior-tui.keyconfig.edit=e uda.taskwarrior-tui.keyconfig.duplicate=y uda.taskwarrior-tui.keyconfig.modify=m uda.taskwarrior-tui.keyconfig.shell=! uda.taskwarrior-tui.keyconfig.log=l uda.taskwarrior-tui.keyconfig.add=a uda.taskwarrior-tui.keyconfig.annotate=A uda.taskwarrior-tui.keyconfig.filter=/ uda.taskwarrior-tui.keyconfig.zoom=z uda.taskwarrior-tui.keyconfig.context-menu=c uda.taskwarrior-tui.keyconfig.report-menu=R uda.taskwarrior-tui.keyconfig.next-tab=] uda.taskwarrior-tui.keyconfig.previous-tab=[ ``` taskwarrior-tui-0.27.0/docs/src/content/docs/developer/guide.md000064400000000000000000000067231046102023000226160ustar 00000000000000--- title: Developer Guide description: Install the repo toolchain, run tests, work with fixture data, and build the Starlight docs site. --- ## Installing `mise` Install `mise`: ```bash curl https://mise.run | sh ``` Activate it in your shell: ```bash # zsh echo 'eval "$(mise activate zsh)"' >> "${ZDOTDIR-$HOME}/.zshrc" # bash echo 'eval "$(mise activate bash)"' >> ~/.bashrc # fish echo 'mise activate fish | source' >> ~/.config/fish/config.fish ``` Restart your shell, then install this project's tools: ```bash mise install ``` The rest of this guide assumes `mise` is activated in your shell, so the toolchain and project environment from [`.config/mise.toml`](https://github.com/kdheepak/taskwarrior-tui/blob/main/.config/mise.toml) are available automatically. Pinned values like the Taskwarrior source tag and testdata ref live there, while the task entrypoints themselves live in [`.config/mise/tasks/taskwarrior-tui/`](https://github.com/kdheepak/taskwarrior-tui/tree/main/.config/mise/tasks/taskwarrior-tui/). ## Running Tests ```bash git clone https://github.com/kdheepak/taskwarrior-tui cd taskwarrior-tui mise run taskwarrior-tui:cargo-test ``` `mise run taskwarrior-tui:cargo-test` fetches `taskwarrior-testdata` at a pinned commit for deterministic runs. ## Building the CLI ```bash cargo build cargo build --release ``` ## Running a Debug Build ```bash cargo run ``` ## Running the TUI with Local Fixture Data Import `tests/data/export.json` into `tests/data/.task`: ```bash mise run taskwarrior-tui:import-taskdata ``` Run the TUI against that imported data and remove `tests/data/.task` when the TUI exits: ```bash mise run taskwarrior-tui:run-taskdata ``` Use a release build instead: ```bash mise run taskwarrior-tui:run-taskdata --release ``` Remove `tests/data/.task` without starting the TUI: ```bash mise run taskwarrior-tui:clean-taskdata ``` ## Running a Release Build ```bash cargo run --release ``` ## Testing an Individual Function If you want to test the `test_taskwarrior_timing` function in `src/app.rs`: ```bash mise run taskwarrior-tui:setup-testdata cargo test -- app::tests::test_taskwarrior_timing --nocapture ``` ## Getting Logs With `mise` activated, `TASKWARRIOR_TUI_LOG_LEVEL=debug` is already set for this repo. ```bash taskwarrior-tui # OR cargo run ``` ## Contributing to Documentation The Starlight site lives in `docs/`. Content pages live in `docs/src/content/docs/`. Packaging assets that used to live alongside the docs now live in `packaging/`. Build the docs locally with: ```bash mise run taskwarrior-tui:docs-build ``` For iterative docs work, install the site dependencies once and start the Astro dev server: ```bash mise run taskwarrior-tui:docs-preview ``` Regenerate the man page with: ```bash mise run taskwarrior-tui:man ``` When you make a PR to the repository, a preview of the documentation is rendered and a link is posted to the PR. ## Internals of `taskwarrior-tui` `taskwarrior-tui` is a state-driven terminal user interface. Keyboard events are read asynchronously and communicated using channels. Most of the logic is implemented in `src/app.rs`. The difference between the previous state and the current state of the TUI is rendered every `Tick` by `ratatui`. The `app.draw_...` functions are responsible for rendering the UI. Actions for key presses are taken in [`app.handle_input(&mut self, input: Key)`](https://github.com/kdheepak/taskwarrior-tui/blob/f7f89cbff180f81a3b27112d676d6101b0b552d8/src/app.rs#L1893). taskwarrior-tui-0.27.0/docs/src/content/docs/faqs.md000064400000000000000000000031771046102023000204660ustar 00000000000000--- title: Frequently Asked Questions description: Answers to common behavior questions around shell commands, shortcuts, and Taskwarrior prompts. --- ### Does `taskwarrior-tui` show error messages when running shell commands or shortcuts? `taskwarrior-tui` shows an error prompt for shell commands if: 1. The subprocess fails. 2. The subprocess succeeds but prints to stdout. 3. The subprocess command is empty. `taskwarrior-tui` shows an error prompt for shortcuts if: 1. The shortcut fails. If `taskwarrior-tui` encounters a prompt from a subprocess or shortcut, it will not prompt the user for input again. That means if you want to run a `taskwarrior` command as a shell command, you may want to pass `rc.confirmation=off` in the command. See the following screencast as an example: ```bash task rc.confirmation=off context define test project:work ``` If you do not add `rc.confirmation=off` in the shell command, `taskwarrior-tui` will run the command but it will fail because it cannot respond to the interactive prompt. ### How do I debug startup failures, crashes, or performance issues? See [Troubleshooting](./troubleshooting/) for a step-by-step guide to collecting logs, comparing `taskwarrior` command timings, isolating path-specific issues, and filing a useful GitHub issue. taskwarrior-tui-0.27.0/docs/src/content/docs/index.mdx000064400000000000000000000024351046102023000210270ustar 00000000000000--- title: taskwarrior-tui description: A terminal user interface for Taskwarrior with keyboard-first workflows, live filters, and configurable keybindings. template: splash hero: title: taskwarrior-tui tagline: A keyboard-first terminal UI for Taskwarrior with fast filtering, report switching, and configurable workflows. actions: - text: Installation link: ./installation/ icon: right-arrow - text: Quick Start link: ./quick_start/ variant: secondary --- Start with [Installation](./installation/), then move to [Quick Start](./quick_start/) and [Default Keybindings](./keybindings/). ## Documentation - [Installation](./installation/) for setup and package-manager options. - [Quick Start](./quick_start/) for the first-run workflow. - [Default Keybindings](./keybindings/) for the built-in controls. - [Troubleshooting](./troubleshooting/) for logs, timing checks, path isolation, and issue reports. - [Advanced Configuration](./configuration/advanced/) for deeper behavior and style options. - [Developer Guide](./developer/guide/) for local development, tests, and docs builds. - [Frequently Asked Questions](./faqs/) for common edge cases and shell-command behavior. ## Project - [Source and Releases](https://github.com/kdheepak/taskwarrior-tui) on GitHub. taskwarrior-tui-0.27.0/docs/src/content/docs/installation.md000064400000000000000000000051271046102023000222320ustar 00000000000000--- title: Installation description: Install taskwarrior-tui manually, from source, or with common package managers. --- Unless otherwise specified, you will need to install `taskwarrior` first. See for more information. **Manual** (_Recommended_) [![](https://img.shields.io/github/v/tag/kdheepak/taskwarrior-tui)](https://github.com/kdheepak/taskwarrior-tui/releases/latest) [![](https://img.shields.io/github/downloads/kdheepak/taskwarrior-tui/total)](https://github.com/kdheepak/taskwarrior-tui/releases/latest) 1. Download the tar.gz file for your OS from [the latest release](https://github.com/kdheepak/taskwarrior-tui/releases/latest). 2. Unzip the tar.gz file. 3. Run `./taskwarrior-tui`. **Install from source** [![](https://img.shields.io/badge/branch-main-red)](https://github.com/kdheepak/taskwarrior-tui) ```bash git clone https://github.com/kdheepak/taskwarrior-tui.git cd taskwarrior-tui cargo build --release ``` **Using [`brew`](https://brew.sh/)** [![](https://img.shields.io/homebrew/v/taskwarrior-tui)](https://formulae.brew.sh/formula/taskwarrior-tui) [![](https://img.shields.io/homebrew/installs/dy/taskwarrior-tui)](https://formulae.brew.sh/formula/taskwarrior-tui) This installs `task` from Homebrew as well. ```bash brew install taskwarrior-tui ``` **Installation for Arch Linux** [![](https://img.shields.io/archlinux/v/extra/x86_64/taskwarrior-tui)](https://archlinux.org/packages/extra/x86_64/taskwarrior-tui/) [![](https://img.shields.io/aur/version/taskwarrior-tui-git)](https://aur.archlinux.org/packages/taskwarrior-tui-git/) Use [pacman](https://wiki.archlinux.org/index.php/Pacman) to install it from the [extra repository](https://archlinux.org/packages/extra/x86_64/taskwarrior-tui/): ```bash pacman -S taskwarrior-tui ``` Or use your favorite [AUR helper](https://wiki.archlinux.org/index.php/AUR_helpers) to install the [git](https://aur.archlinux.org/packages/taskwarrior-tui-git/) package maintained by [@loki7990](https://github.com/loki7990). For example: ```bash yay -S taskwarrior-tui-git ``` **Using [`snap`](https://snapcraft.io/)** [![](https://snapcraft.io/taskwarrior-tui/badge.svg)](https://snapcraft.io/taskwarrior-tui) ```bash snap install taskwarrior-tui ``` **Using [`zdharma-continuum/zinit`](https://github.com/zdharma-continuum/zinit)** [![](https://img.shields.io/github/v/tag/kdheepak/taskwarrior-tui)](https://github.com/kdheepak/taskwarrior-tui/releases/latest) Add the following to your `~/.zshrc`: ```zsh zinit ice wait:2 lucid extract"" from"gh-r" as"command" mv"taskwarrior-tui* -> tt" zinit load kdheepak/taskwarrior-tui ``` taskwarrior-tui-0.27.0/docs/src/content/docs/keybindings.md000064400000000000000000000111401046102023000220270ustar 00000000000000--- title: Default Keybindings description: Reference the default keyboard controls for reports, prompts, menus, and the calendar view. --- :::note If any bindings are changed in `.taskrc`, the built-in help menu will show them instead of the defaults. See [key configuration](./configuration/keys.md) for details on custom keybindings. ::: Keybindings: Esc: - Exit current action ]: Next view - Go to next view [: Previous view - Go to previous view Keybindings for task report: /: task {string} - Filter task report a: task add {string} - Add new task d: task {selected} done - Mark task as done e: task {selected} edit - Open selected task in editor y: task {selected} duplicate - Duplicate tasks j: {selected+=1} - Move down in task report k: {selected-=1} - Move up in task report J: {selected+=pageheight} - Move page down in task report K: {selected-=pageheight} - Move page up in task report g: {selected=first} - Go to top G: {selected=last} - Go to bottom l: task log {string} - Log new task m: task {selected} modify {string} - Modify selected task q: exit - Quit s: task {selected} start/stop - Toggle start and stop t: task {selected} +{tag}/-{tag} - Toggle {uda.taskwarrior-tui.quick-tag.name} (default: `next`) u: task undo - Undo v: {toggle mark on selected} - Toggle mark on selected V: {toggle marks on all tasks} - Toggle marks on all tasks in current filter report x: task {selected} delete - Delete z: toggle task info - Toggle task info view A: task {selected} annotate {string} - Annotate current task Ctrl-e: scroll down task details - Scroll task details view down one line Ctrl-y: scroll up task details - Scroll task details view up one line !: {string} - Custom shell command 1-9: {string} - Run user defined shortcuts :: {task id} - Jump to task id c: context switcher menu - Open context switcher menu R: report switcher menu - Open report switcher menu ?: help - Help menu Keybindings for filter / command prompt: Ctrl + f | Right: move forward - Move forward one character Ctrl + b | Left: move backward - Move backward one character Ctrl + h | Backspace: backspace - Delete one character back Ctrl + d | Delete: delete - Delete one character forward Ctrl + a | Home: home - Go to the beginning of line Ctrl + e | End: end - Go to the end of line Ctrl + k: delete to end - Delete to the end of line Ctrl + u: delete to beginning - Delete to the beginning of line Ctrl + w: delete previous word - Delete previous word Alt + d: delete next word - Delete next word Alt + b: move to previous word - Move to previous word Alt + f: move to next word - Move to next word Alt + t: transpose words - Transpose words Up: scroll history - Go backward in history matching from beginning of line to cursor Down: scroll history - Go forward in history matching from beginning of line to cursor TAB | Ctrl + n: tab complete - Open tab completion and selection first element OR cycle to next element BACKTAB | Ctrl + p: tab complete - Cycle to previous element Keybindings for context switcher: j: {selected+=1} - Move forward a context k: {selected-=1} - Move back a context Enter: task context {selected} - Select highlighted context Keybindings for report switcher: j: {selected+=1} - Move forward a report k: {selected-=1} - Move back a report Enter: task report {selected} - Select highlighted report Keybindings for calendar: j: {selected+=1} - Move forward a year in calendar k: {selected-=1} - Move back a year in calendar J: {selected+=10} - Move forward a decade in calendar K: {selected-=10} - Move back a decade in calendar taskwarrior-tui-0.27.0/docs/src/content/docs/quick_start.md000064400000000000000000000012011046102023000220470ustar 00000000000000--- title: Quick Start description: Launch taskwarrior-tui and get to a usable keyboard workflow quickly. --- 1. Install `taskwarrior` and `taskwarrior-tui`. 2. Run: ```bash taskwarrior-tui ``` 3. Use Vim-like keys to navigate your task list. Press `?` for more information. _Tip_: alias `tt` to `taskwarrior-tui`. Add the following to your shell profile such as `~/.bashrc` or `~/.zshrc`: ```bash alias tt="taskwarrior-tui" ``` ![](https://user-images.githubusercontent.com/1813121/89620056-4ed64200-d84c-11ea-9153-9e08bc26d3b4.gif) If startup fails or the UI feels slow, continue to [Troubleshooting](./troubleshooting/). taskwarrior-tui-0.27.0/docs/src/content/docs/troubleshooting.md000064400000000000000000000067301046102023000227610ustar 00000000000000--- title: Troubleshooting description: Collect logs, compare Taskwarrior commands, and isolate configuration or filesystem issues when taskwarrior-tui is slow or fails. --- If `taskwarrior-tui` is slow, fails to start, or behaves differently than `task`, use this checklist to narrow the problem down before opening an issue. ## Capture Version and Environment Details Start by recording the versions involved: ```bash taskwarrior-tui --version task --version task diagnostics ``` If you launch `taskwarrior-tui` with custom paths or wrappers, also record: ```bash echo "$TASKRC" echo "$TASKDATA" echo "$TASKWARRIOR_TUI_DATA" echo "$TASKWARRIOR_TUI_TASKWARRIOR_CLI" ``` These environment variables can help identify when there's a bug in `taskwarrior-tui` versus a configuration or environment issue. ## Collect a Log File `taskwarrior-tui` writes a `taskwarrior-tui.log` file to its data directory. - By default it uses your platform's local data directory. - Override the log location with `--data /path/to/folder` or `TASKWARRIOR_TUI_DATA=/path/to/folder`. To enable verbose logs: ```bash TASKWARRIOR_TUI_LOG_LEVEL=debug taskwarrior-tui ``` Use `trace` for the most verbose logs: ```bash TASKWARRIOR_TUI_LOG_LEVEL=trace taskwarrior-tui ``` The log includes timestamps on each line for logging information in `taskwarrior-tui`. ## Compare With Plain Taskwarrior Many issues are easier to isolate by comparing the presentation of the data in `taskwarrior-tui` with the underlying `task` commands directly. Use the same `TASKRC`, `TASKDATA`, report, and shell environment as the `taskwarrior-tui` session. ```bash time task show time task export next time task context time task summary time task ``` Replace `next` with the report you open in `taskwarrior-tui`, and replace `` with a task visible in the TUI view. ## Isolate the UI Refresh Path Try these temporary `taskrc` changes one at a time: ```plaintext uda.taskwarrior-tui.task-report.info-show=0 uda.taskwarrior-tui.task-report.task-detail-prefetch=0 uda.taskwarrior-tui.tick-rate=0 ``` What they help isolate: - `task-report.info-show=0` disables the task details pane. - `task-detail-prefetch=0` keeps the details pane on, but reduces prefetching while you move through tasks. - `tick-rate=0` disables periodic refresh ticks. `task-report.show-info=0` is still accepted as a legacy alias for backward compatibility, but `task-report.info-show=0` is the preferred spelling and the legacy alias will be removed in a future release. Restore your normal settings after the test. ## Disable Local Customizations If you use any of the following, test once without them: - `TASKWARRIOR_TUI_TASKWARRIOR_CLI` - `uda.taskwarrior-tui.background_process` - custom shortcuts or shell commands - a heavily customized `taskrc` This helps separate a core bug from a wrapper script or environment-specific behavior. ## Capture Crash Information If `taskwarrior-tui` exits with a panic, rerun it with: ```bash RUST_BACKTRACE=1 taskwarrior-tui ``` Attach the panic output together with the relevant log excerpt. ## What to Include in a GitHub Issue Please include: - `taskwarrior-tui` version - `task` version - OS, terminal emulator, and shell - whether the issue reproduces with a minimal `taskrc` - whether the issue reproduces with local paths instead of mounted or network paths - relevant log excerpts - timings from the `time task ...` comparison - exact steps to reproduce Redact private paths, project names, or task descriptions if needed. taskwarrior-tui-0.27.0/docs/src/content.config.ts000064400000000000000000000004311046102023000200640ustar 00000000000000import { defineCollection } from 'astro:content'; import { docsLoader } from '@astrojs/starlight/loaders'; import { docsSchema } from '@astrojs/starlight/schema'; export const collections = { docs: defineCollection({ loader: docsLoader(), schema: docsSchema(), }), }; taskwarrior-tui-0.27.0/docs/src/pages/404.astro000064400000000000000000000101001046102023000172500ustar 00000000000000--- const homeHref = new URL('./', Astro.url).pathname; --- 404 | taskwarrior-tui
taskwarrior-tui / docs / 404
Route Not Found

404

The documentation page you requested does not exist at this path. The docs site has moved to Starlight, so older links may no longer match the current structure exactly.

taskwarrior-tui-0.27.0/docs/tsconfig.json000064400000000000000000000000521046102023000165150ustar 00000000000000{ "extends": "astro/tsconfigs/strict" } taskwarrior-tui-0.27.0/packaging/linux/taskwarrior-tui.desktop000064400000000000000000000004541046102023000227110ustar 00000000000000[Desktop Entry] Type=Application Version=1.0 Name=Taskwarrior TUI GenericName=Taskwarrior Terminal User Interface Comment=A Taskwarrior Terminal User Interface Icon=utilities-terminal Exec=taskwarrior-tui Terminal=true Categories=Utility;ConsoleOnly; Keywords=task;todo;taskwarrior;tui;cli;terminal; taskwarrior-tui-0.27.0/packaging/man/taskwarrior-tui.1000064400000000000000000000120741046102023000210150ustar 00000000000000.\" Automatically generated by Pandoc 3.9.0.2 .\" .TH "taskwarrior\-tui" "1" "" "" .SH NAME taskwarrior\-tui \(em A terminal user interface for taskwarrior (https://github.com/kdheepak/taskwarrior\-tui) .SH SYNOPSIS \f[CR]taskwarrior\-tui\f[R] .PP \f[B]\f[CB]taskwarrior\-tui\f[B]\f[R] is a terminal user interface for \f[CR]taskwarrior\f[R]. .SH EXAMPLES .TP \f[CR]taskwarrior\-tui\f[R] Starts a terminal user interface for \f[CR]taskwarrior\f[R]. .TP \f[CR]alias tt=taskwarrior\-tui\f[R] Add the above to your dotfiles to use \f[CR]tt\f[R] to start \f[CR]taskwarrior\-tui\f[R]. .SH KEYBINDINGS Keybindings: .TP \f[CR]Esc\f[R] Exit current action .TP \f[CR]]\f[R] Next view \- Go to next view .TP \f[CR][\f[R] Previous view \- Go to previous view .PP Keybindings for task report: .TP \f[CR]/\f[R] task {string} \- Filter task report .TP \f[CR]a\f[R] task add {string} \- Add new task .TP \f[CR]d\f[R] task {selected} done \- Mark task as done .TP \f[CR]e\f[R] task {selected} edit \- Open selected task in editor .TP \f[CR]j\f[R] {selected+=1} \- Move down in task report .TP \f[CR]k\f[R] {selected\-=1} \- Move up in task report .TP \f[CR]J\f[R] {selected+=pageheight} \- Move page down in task report .TP \f[CR]K\f[R] {selected\-=pageheight} \- Move page up in task report .TP \f[CR]g\f[R] {selected=first} \- Go to top .TP \f[CR]G\f[R] {selected=last} \- Go to bottom .TP \f[CR]l\f[R] task log {string} \- Log new task .TP \f[CR]m\f[R] task {selected} modify {string} \- Modify selected task .TP \f[CR]q\f[R] exit \- Quit .TP \f[CR]s\f[R] task {selected} start/stop \- Toggle start and stop .TP \f[CR]t\f[R] task {selected} +{tag}/\-{tag} \- Toggle {uda.taskwarrior\-tui.quick\-tag.name} (default: \f[CR]next\f[R]) .TP \f[CR]u\f[R] task undo \- Undo .TP \f[CR]v\f[R] {toggle mark on selected} \- Toggle mark on selected .TP \f[CR]V\f[R] {toggle marks on all tasks} \- Toggle marks on all tasks in current filter report .TP \f[CR]x\f[R] task delete {selected} \- Delete .TP \f[CR]z\f[R] toggle task info \- Toggle task info view .TP \f[CR]A\f[R] task {selected} annotate {string} \- Annotate current task .TP Ctrl\-e scroll down task details \- Scroll task details view down one line .TP Ctrl\-y scroll up task details \- Scroll task details view up one line .TP \f[CR]!\f[R] {string} \- Custom shell command .TP \f[CR]1\-9\f[R] {string} \- Run user defined shortcuts .TP \f[CR]:\f[R] {task id} \- Jump to task id .TP \f[CR]c\f[R] context switcher menu \- Open context switcher menu .TP \f[CR]R\f[R] report switcher menu \- Open report switcher menu .TP \f[CR]?\f[R] help \- Help menu .PP Keybindings for filter / command prompt: .TP \f[CR]Ctrl + f | Right\f[R] move forward \- Move forward one character .TP \f[CR]Ctrl + b | Left\f[R] move backward \- Move backward one character .TP \f[CR]Ctrl + h | Backspace\f[R] backspace \- Delete one character back .TP \f[CR]Ctrl + d | Delete\f[R] delete \- Delete one character forward .TP \f[CR]Ctrl + a | Home\f[R] home \- Go to the beginning of line .TP \f[CR]Ctrl + e | End\f[R] end \- Go to the end of line .TP \f[CR]Ctrl + k\f[R] delete to end \- Delete to the end of line .TP \f[CR]Ctrl + u\f[R] delete to beginning \- Delete to the beginning of line .TP \f[CR]Ctrl + w\f[R] delete previous word \- Delete previous word .TP \f[CR]Alt + d\f[R] delete next word \- Delete next word .TP \f[CR]Alt + b\f[R] move to previous word \- Move to previous word .TP \f[CR]Alt + f\f[R] move to next word \- Move to next word .TP \f[CR]Alt + t\f[R] transpose words \- Transpose words .TP \f[CR]Up\f[R] scroll history \- Go backward in history matching from beginning of line to cursor .TP \f[CR]Down\f[R] scroll history \- Go forward in history matching from beginning of line to cursor .TP \f[CR]TAB | Ctrl + n\f[R] tab complete \- Open tab completion and selection first element OR cycle to next element .TP \f[CR]BACKTAB | Ctrl + p\f[R] tab complete \- Cycle to previous element .PP Keybindings for context switcher: .TP \f[CR]j\f[R] {selected+=1} \- Move forward a context .TP \f[CR]k\f[R] {selected\-=1} \- Move back a context .PP Keybindings for report switcher: .TP \f[CR]j\f[R] {selected+=1} \- Move forward a report .TP \f[CR]k\f[R] {selected\-=1} \- Move back a report .PP Keybindings for calendar: .TP \f[CR]j\f[R] {selected+=1} \- Move forward a year in calendar .TP \f[CR]k\f[R] {selected\-=1} \- Move back a year in calendar .TP \f[CR]J\f[R] {selected+=10} \- Move forward a decade in calendar .TP \f[CR]K\f[R] {selected\-=10} \- Move back a decade in calendar .SH ENVIRONMENT .TP \f[CR]TASKWARRIOR_TUI_TASKWARRIOR_CLI\f[R] Path to the \f[CR]task\f[R] executable. Defaults to \f[CR]task\f[R] (resolved via \f[CR]PATH\f[R]). Set this to use a non\-standard installation of Taskwarrior, e.g. \f[CR]TASKWARRIOR_TUI_TASKWARRIOR_CLI=/usr/local/bin/task taskwarrior\-tui\f[R]. .SH EXIT STATUSES .TP 0 If everything goes OK. .SH AUTHOR \f[CR]taskwarrior\-tui\f[R] is maintained by Dheepak `kdheepak' Krishnamurthy and other contributors. .PP \f[B]Source code:\f[R] \f[CR]https://github.com/kdheepak/taskwarrior\-tui/\f[R] .PD 0 .P .PD \f[B]Contributors:\f[R] \f[CR]https://github.com/kdheepak/taskwarrior\-tui/graphs/contributors\f[R] taskwarrior-tui-0.27.0/packaging/man/taskwarrior-tui.1.md000064400000000000000000000126511046102023000214150ustar 00000000000000% taskwarrior-tui(1) NAME ==== taskwarrior-tui — A terminal user interface for taskwarrior (https://github.com/kdheepak/taskwarrior-tui) SYNOPSIS ======== `taskwarrior-tui` **`taskwarrior-tui`** is a terminal user interface for `taskwarrior`. EXAMPLES ======== `taskwarrior-tui` : Starts a terminal user interface for `taskwarrior`. `alias tt=taskwarrior-tui` : Add the above to your dotfiles to use `tt` to start `taskwarrior-tui`. KEYBINDINGS =========== Keybindings: `Esc` : Exit current action `]` : Next view - Go to next view `[` : Previous view - Go to previous view Keybindings for task report: `/` : task {string} - Filter task report `a` : task add {string} - Add new task `d` : task {selected} done - Mark task as done `e` : task {selected} edit - Open selected task in editor `j` : {selected+=1} - Move down in task report `k` : {selected-=1} - Move up in task report `J` : {selected+=pageheight} - Move page down in task report `K` : {selected-=pageheight} - Move page up in task report `g` : {selected=first} - Go to top `G` : {selected=last} - Go to bottom `l` : task log {string} - Log new task `m` : task {selected} modify {string} - Modify selected task `q` : exit - Quit `s` : task {selected} start/stop - Toggle start and stop `t` : task {selected} +{tag}/-{tag} - Toggle {uda.taskwarrior-tui.quick-tag.name} (default: `next`) `u` : task undo - Undo `v` : {toggle mark on selected} - Toggle mark on selected `V` : {toggle marks on all tasks} - Toggle marks on all tasks in current filter report `x` : task delete {selected} - Delete `z` : toggle task info - Toggle task info view `A` : task {selected} annotate {string} - Annotate current task Ctrl-e : scroll down task details - Scroll task details view down one line Ctrl-y : scroll up task details - Scroll task details view up one line `!` : {string} - Custom shell command `1-9` : {string} - Run user defined shortcuts `:` : {task id} - Jump to task id `c` : context switcher menu - Open context switcher menu `R` : report switcher menu - Open report switcher menu `?` : help - Help menu Keybindings for filter / command prompt: `Ctrl + f | Right` : move forward - Move forward one character `Ctrl + b | Left` : move backward - Move backward one character `Ctrl + h | Backspace` : backspace - Delete one character back `Ctrl + d | Delete` : delete - Delete one character forward `Ctrl + a | Home` : home - Go to the beginning of line `Ctrl + e | End` : end - Go to the end of line `Ctrl + k` : delete to end - Delete to the end of line `Ctrl + u` : delete to beginning - Delete to the beginning of line `Ctrl + w` : delete previous word - Delete previous word `Alt + d` : delete next word - Delete next word `Alt + b` : move to previous word - Move to previous word `Alt + f` : move to next word - Move to next word `Alt + t` : transpose words - Transpose words `Up` : scroll history - Go backward in history matching from beginning of line to cursor `Down` : scroll history - Go forward in history matching from beginning of line to cursor `TAB | Ctrl + n` : tab complete - Open tab completion and selection first element OR cycle to next element `BACKTAB | Ctrl + p` : tab complete - Cycle to previous element Keybindings for context switcher: `j` : {selected+=1} - Move forward a context `k` : {selected-=1} - Move back a context Keybindings for report switcher: `j` : {selected+=1} - Move forward a report `k` : {selected-=1} - Move back a report Keybindings for calendar: `j` : {selected+=1} - Move forward a year in calendar `k` : {selected-=1} - Move back a year in calendar `J` : {selected+=10} - Move forward a decade in calendar `K` : {selected-=10} - Move back a decade in calendar ENVIRONMENT =========== `TASKWARRIOR_TUI_TASKWARRIOR_CLI` : Path to the `task` executable. Defaults to `task` (resolved via `PATH`). Set this to use a non-standard installation of Taskwarrior, e.g. `TASKWARRIOR_TUI_TASKWARRIOR_CLI=/usr/local/bin/task taskwarrior-tui`. EXIT STATUSES ============= 0 : If everything goes OK. AUTHOR ====== `taskwarrior-tui` is maintained by Dheepak 'kdheepak' Krishnamurthy and other contributors. **Source code:** `https://github.com/kdheepak/taskwarrior-tui/` \ **Contributors:** `https://github.com/kdheepak/taskwarrior-tui/graphs/contributors` taskwarrior-tui-0.27.0/packaging/vhs/basic-features.tape000064400000000000000000000040521046102023000213560ustar 00000000000000Output packaging/vhs/basic-features.mp4 Require bash Require cargo Require git Require task Set Shell bash Set Theme "Builtin Dark" Set FontSize 18 Set Width 1160 Set Height 820 Set Padding 12 Set Margin 12 Set MarginFill "#0b1220" Set BorderRadius 12 Set Framerate 30 Set PlaybackSpeed 0.75 Set CursorBlink false Set TypingSpeed 80ms Hide Type "./target/debug/taskwarrior-tui" Enter Show Sleep 2500ms Type "j" Sleep 800ms Type "k" Sleep 1000ms Type "a" Sleep 250ms Type "Filter demo: live filter to this task" Ctrl+E Type " p" Sleep 300ms Tab Sleep 500ms Tab Sleep 500ms Tab Sleep 500ms Enter Sleep 300ms Type "de" Sleep 300ms Tab Sleep 500ms Enter Sleep 2200ms Type "/" Sleep 250ms Ctrl+U Sleep 200ms Type "Filter demo" Enter Sleep 2200ms Type "/" Sleep 250ms Ctrl+U Sleep 200ms Enter Sleep 1200ms Type "a" Sleep 250ms Type "Annotate demo: capture a note on this task" Ctrl+E Type " project:de" Sleep 300ms Tab Sleep 500ms Enter Sleep 2200ms Type "z" Sleep 1200ms Type "A" Sleep 250ms Type "Annotation demo" Enter Sleep 2200ms Type "z" Sleep 1000ms Type "a" Sleep 250ms Type "Start demo: toggle this task" Ctrl+E Type " project:de" Sleep 300ms Tab Sleep 500ms Enter Sleep 2200ms Type "s" Sleep 2200ms Type "s" Sleep 1800ms Type "a" Sleep 250ms Type "Toggle demo: add and remove the 'next' tag" Ctrl+E Type " project:de" Sleep 300ms Tab Sleep 500ms Enter Sleep 2200ms Type "t" Sleep 2000ms Type "t" Sleep 1600ms Type "a" Sleep 250ms Type "Mark demo tasks as done together" Ctrl+E Type " project:de" Sleep 300ms Tab Sleep 500ms Enter Sleep 2200ms Type "/" Sleep 250ms Ctrl+U Sleep 200ms Type "project:de" Sleep 300ms Tab Sleep 500ms Enter Sleep 2200ms Type "g" Sleep 600ms Type "v" Sleep 700ms Type "j" Sleep 600ms Type "v" Sleep 700ms Type "j" Sleep 600ms Type "v" Sleep 1200ms Type "d" Sleep 2600ms Type "/" Sleep 250ms Ctrl+U Sleep 200ms Enter Sleep 1200ms Type "R" Sleep 350ms Type "completed" Enter Sleep 2600ms Type "?" Sleep 1400ms Type "jjjjjjjjjjjjjjjjjjjjjjjj" Sleep 700ms Type "jjjjjjjjjjjjjjjjjjjjjjjj" Sleep 1400ms Escape Sleep 1000ms Type "q" Sleep 600ms taskwarrior-tui-0.27.0/rust-toolchain.toml000064400000000000000000000000371046102023000167310ustar 00000000000000[toolchain] channel = "stable" taskwarrior-tui-0.27.0/snap/snapcraft.yaml000064400000000000000000000017271046102023000166760ustar 00000000000000name: taskwarrior-tui base: core24 version: git summary: A terminal user interface for taskwarrior description: | A terminal user interface for taskwarrior to manage your tasks efficiently. grade: stable confinement: strict apps: task: command: usr/bin/task environment: PATH: $SNAP/usr/bin:$SNAP/usr/local/bin/:$PATH:$SNAP/bin/:$PATH MANPATH: $SNAP/usr/share/man taskwarrior-tui: command: bin/taskwarrior-tui desktop: usr/share/applications/taskwarrior-tui.desktop environment: PATH: $SNAP/usr/bin:$SNAP/usr/local/bin/:$PATH:$SNAP/bin/:$PATH MANPATH: $SNAP/usr/share/man parts: task: plugin: nil stage-packages: - taskwarrior taskwarrior-tui: plugin: rust source: . desktop-and-man: plugin: dump source: . organize: packaging/linux/taskwarrior-tui.desktop: usr/share/applications/taskwarrior-tui.desktop packaging/man/taskwarrior-tui.1: usr/share/man/man1/taskwarrior-tui.1 taskwarrior-tui-0.27.0/src/action.rs000064400000000000000000000003561046102023000154770ustar 00000000000000#[derive(Clone, PartialEq, Eq, Debug, Copy)] pub enum Action { Report, Filter, Add, Annotate, Subprocess, Log, Modify, HelpPopup, ContextMenu, ReportMenu, Jump, DeletePrompt, UndoPrompt, DonePrompt, Error, } taskwarrior-tui-0.27.0/src/app.rs000064400000000000000000007120111046102023000150000ustar 00000000000000use std::{ borrow::Borrow, cmp::Ordering, collections::{HashMap, HashSet}, convert::TryInto, fs, io, io::{Read, Write}, path::Path, sync::{Arc, Mutex, mpsc}, time::{Duration, Instant, SystemTime}, }; use anyhow::{Context as AnyhowContext, Result, anyhow}; use chrono::{DateTime, Datelike, FixedOffset, Local, NaiveDate, NaiveDateTime, TimeZone, Timelike}; use crossterm::{ event::{DisableBracketedPaste, DisableMouseCapture, EnableBracketedPaste, EnableMouseCapture}, execute, style::style, terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode}, }; use futures::SinkExt; use log::{Level, LevelFilter, debug, error, info, log_enabled, trace, warn}; use ratatui::{ Frame, Terminal, backend::{Backend, CrosstermBackend}, layout::{Alignment, Constraint, Direction, Layout, Margin, Position, Rect}, style::{Color, Modifier, Style}, symbols::bar::FULL, text::{Line, Span, Text}, widgets::{Block, BorderType, Borders, Clear, Gauge, LineGauge, List, ListItem, Paragraph, Tabs, Wrap}, }; use regex::Regex; use rustyline::{At, Editor, Word, history::SearchDirection as HistoryDirection, line_buffer::LineBuffer}; use std::sync::LazyLock; use task_hookrs::{date::Date, import::import, project::Project, status::TaskStatus, task::Task, uda::UDAValue}; use unicode_segmentation::{Graphemes, UnicodeSegmentation}; use unicode_width::UnicodeWidthStr; use uuid::Uuid; use versions::Versioning; use crate::{ action::Action, calendar::Calendar, completion::{CompletionList, get_start_word_under_cursor}, config::{Config, TaskInfoLocation}, datetime, event::{Event, KeyCode}, help::Help, history::HistoryContext, keyconfig::KeyConfig, pane::{ Pane, context::{ContextDetails, ContextsState}, project::ProjectsState, report::ReportsState, }, scrollbar::Scrollbar, table::{Row, Table, TableMode, TaskwarriorTuiTableState}, task_report::TaskReportTable, ui, utils, }; const MAX_LINE: usize = 4096; static START_TIME: LazyLock = LazyLock::new(Instant::now); static TASKWARRIOR_VERSION_SUPPORTED: LazyLock = LazyLock::new(|| Versioning::new("3.0.0").unwrap()); #[derive(Debug)] pub enum DateState { BeforeToday, EarlierToday, LaterToday, AfterToday, NotDue, } pub fn get_date_state(reference: &Date, due: usize) -> DateState { let now = Local::now(); let reference = datetime::local_from_utc(reference); if reference.date_naive() < now.date_naive() { return DateState::BeforeToday; } if reference.date_naive() == now.date_naive() { return if reference.time() < now.time() { DateState::EarlierToday } else { DateState::LaterToday }; } if reference <= now + chrono::Duration::days(due as i64) { DateState::AfterToday } else { DateState::NotDue } } fn get_formatted_datetime(date: &Date) -> String { datetime::format_taskwarrior_datetime_literal(date) } fn centered_rect(percent_x: u16, percent_y: u16, r: Rect) -> Rect { let popup_layout = Layout::default() .direction(Direction::Vertical) .constraints( [ Constraint::Percentage((100 - percent_y) / 2), Constraint::Percentage(percent_y), Constraint::Percentage((100 - percent_y) / 2), ] .as_ref(), ) .split(r); Layout::default() .direction(Direction::Horizontal) .constraints( [ Constraint::Percentage((100 - percent_x) / 2), Constraint::Percentage(percent_x), Constraint::Percentage((100 - percent_x) / 2), ] .as_ref(), ) .split(popup_layout[1])[1] } #[derive(Debug, Clone, PartialEq, Eq)] pub enum Mode { Tasks(Action), Projects, Timesheet, Calendar, } pub struct TaskwarriorTui { pub should_quit: bool, pub dirty: bool, pub task_table_state: TaskwarriorTuiTableState, pub current_context_filter: String, pub current_context: String, pub command: LineBuffer, pub filter: LineBuffer, pub modify: LineBuffer, pub tasks: Vec, pub all_tasks: Vec, pub task_details: HashMap, pub task_details_modified: HashMap>, pub marked: HashSet, // stores index of current task that is highlighted pub current_selection: usize, pub current_selection_uuid: Option, pub current_selection_id: Option, pub task_report_table: TaskReportTable, pub calendar_year: i32, pub mode: Mode, pub previous_mode: Option, pub config: Config, pub task_report_info_show: bool, pub task_report_height: u16, pub task_details_scroll: u16, pub task_details_defaultwidth: u16, pub help_popup: Help, pub last_export: Option, pub keyconfig: KeyConfig, pub terminal_width: u16, pub terminal_height: u16, pub filter_history: HistoryContext, pub command_history: HistoryContext, pub history_status: Option, pub completion_list: CompletionList, pub show_completion_pane: bool, pub report: String, pub projects: ProjectsState, pub contexts: ContextsState, pub reports: ReportsState, pub task_version: Versioning, pub error: Option, pub event_loop: crate::event::EventLoop, pub requires_redraw: bool, pub changes: utils::Changeset, pub task_info_location_override: Option, pub task_info_location_override_width: Option, pub task_exe: String, pub timesheet_data: String, pub timesheet_scroll: u16, pub timesheet_line_count: u16, } impl TaskwarriorTui { pub async fn new(report: &str, init_event_loop: bool) -> Result { let task_exe = std::env::var("TASKWARRIOR_TUI_TASKWARRIOR_CLI").unwrap_or_else(|_| "task".to_string()); let output = std::process::Command::new(&task_exe) .arg("rc.color=off") .arg("rc._forcecolor=off") .arg("rc.defaultwidth=0") .arg("show") .output() .context("Unable to run `task show`.")?; if !output.status.success() { let output = std::process::Command::new(&task_exe) .arg("diagnostics") .output() .context("Unable to run `task diagnostics`.")?; return Err(anyhow!( "Unable to run `task show`.\n{}\n{}\nPlease check your configuration or open a issue on github.", String::from_utf8_lossy(&output.stdout), String::from_utf8_lossy(&output.stderr) )); } let data = String::from_utf8_lossy(&output.stdout); let c = Config::new(&data, report)?; let kc = KeyConfig::new(&data)?; let output = std::process::Command::new(&task_exe) .arg("--version") .output() .context("Unable to run `task --version`")?; let task_version = Versioning::new(String::from_utf8_lossy(&output.stdout).trim()).context("Unable to get version string")?; let (w, h) = crossterm::terminal::size().unwrap_or((50, 15)); let tick_rate = if c.uda_tick_rate > 0 { Some(std::time::Duration::from_millis(c.uda_tick_rate)) } else { None }; let event_loop = crate::event::EventLoop::new(tick_rate, init_event_loop); let mut app = Self { should_quit: false, dirty: true, task_table_state: TaskwarriorTuiTableState::default(), tasks: vec![], all_tasks: vec![], task_details: HashMap::new(), task_details_modified: HashMap::new(), marked: HashSet::new(), current_selection: 0, current_selection_uuid: None, current_selection_id: None, current_context_filter: "".to_string(), current_context: "".to_string(), command: LineBuffer::with_capacity(MAX_LINE), filter: LineBuffer::with_capacity(MAX_LINE), modify: LineBuffer::with_capacity(MAX_LINE), mode: Mode::Tasks(Action::Report), previous_mode: None, task_report_height: 0, task_details_scroll: 0, task_details_defaultwidth: 0, task_report_info_show: c.uda_task_report_info_show, task_info_location_override: None, task_info_location_override_width: None, config: c, task_report_table: TaskReportTable::new(&data, report, &task_exe)?, calendar_year: Local::now().year(), help_popup: Help::new(&kc), last_export: None, keyconfig: kc, terminal_width: w, terminal_height: h, filter_history: HistoryContext::new("filter.history"), command_history: HistoryContext::new("command.history"), history_status: None, completion_list: CompletionList::with_items(vec![]), show_completion_pane: false, report: report.to_string(), projects: ProjectsState::new(), contexts: ContextsState::new(), reports: ReportsState::new(), task_version, error: None, event_loop, requires_redraw: false, changes: utils::Changeset::default(), task_exe, timesheet_data: String::new(), timesheet_scroll: 0, timesheet_line_count: 0, }; for c in app.config.filter.chars() { app.filter.insert(c, 1, &mut app.changes); } app.task_report_table.date_time_vague_precise = app.config.uda_task_report_date_time_vague_more_precise; app.update(true).await?; app.filter_history.load()?; app.filter_history.add(app.filter.as_str()); app.command_history.load()?; app.task_background(); if app.task_version < *TASKWARRIOR_VERSION_SUPPORTED { app.error = Some(format!( "Found taskwarrior version {} but taskwarrior-tui works with taskwarrior>={}", app.task_version, *TASKWARRIOR_VERSION_SUPPORTED )); app.mode = Mode::Tasks(Action::Error); } Ok(app) } pub fn start_tui(&mut self) -> Result>> { enable_raw_mode()?; let mut stdout = std::io::stdout(); execute!(stdout, EnterAlternateScreen, EnableMouseCapture, EnableBracketedPaste)?; let backend = CrosstermBackend::new(stdout); let mut terminal = Terminal::new(backend)?; terminal.hide_cursor()?; Ok(terminal) } pub async fn resume_tui(&mut self) -> Result<()> { self.resume_event_loop().await?; let backend = CrosstermBackend::new(io::stdout()); let mut terminal = Terminal::new(backend)?; execute!(io::stdout(), EnterAlternateScreen, EnableMouseCapture, EnableBracketedPaste)?; enable_raw_mode()?; self.requires_redraw = true; terminal.hide_cursor()?; Ok(()) } pub async fn abort_event_loop(&mut self) -> Result<()> { self.event_loop.abort.send(())?; while let Some(event) = self.next().await { if let Event::Closed = event { break; } } Ok(()) } pub async fn resume_event_loop(&mut self) -> Result<()> { let tick_rate = if self.config.uda_tick_rate > 0 { Some(std::time::Duration::from_millis(self.config.uda_tick_rate)) } else { None }; self.event_loop = crate::event::EventLoop::new(tick_rate, true); Ok(()) } pub async fn pause_tui(&mut self) -> Result<()> { self.abort_event_loop().await?; let backend = CrosstermBackend::new(io::stdout()); let mut terminal = Terminal::new(backend)?; disable_raw_mode()?; execute!(io::stdout(), LeaveAlternateScreen, DisableMouseCapture, DisableBracketedPaste)?; terminal.show_cursor()?; Ok(()) } pub async fn next(&mut self) -> Option> { self.event_loop.rx.recv().await } pub async fn run(&mut self, terminal: &mut Terminal>) -> Result<()> { loop { if self.requires_redraw { terminal.clear()?; self.requires_redraw = false; } terminal.draw(|f| self.draw(f))?; // Handle input if let Some(event) = self.next().await { match event { Event::Input(input) => { debug!("Received input = {:?}", input); self.handle_input(input).await?; } Event::Paste(paste) => { debug!("Received paste of {} bytes", paste.len()); self.handle_paste(&paste); } Event::Tick => { debug!("Tick event"); self.update(false).await?; } Event::Closed => { debug!("Event loop closed"); } } } if self.should_quit { break; } } Ok(()) } pub fn reset_command(&mut self) { self.command.update("", 0, &mut self.changes) } fn insert_text(linebuffer: &mut LineBuffer, text: &str, changes: &mut utils::Changeset) { for c in text.chars() { linebuffer.insert(c, 1, changes); } } fn handle_paste(&mut self, text: &str) { if text.is_empty() { return; } match self.mode { Mode::Tasks(Action::Add | Action::Annotate | Action::Log) => { self.command_history.reset(); Self::insert_text(&mut self.command, text, &mut self.changes); self.update_input_for_completion(); } Mode::Tasks(Action::Modify) => { self.command_history.reset(); Self::insert_text(&mut self.modify, text, &mut self.changes); self.update_input_for_completion(); } Mode::Tasks(Action::Filter) => { self.filter_history.reset(); Self::insert_text(&mut self.filter, text, &mut self.changes); self.update_input_for_completion(); } Mode::Tasks(Action::Subprocess | Action::Jump) => { Self::insert_text(&mut self.command, text, &mut self.changes); } Mode::Tasks(Action::ContextMenu) => { self.contexts.search.push_str(text); self.contexts.table_state.select(Some(0)); } Mode::Tasks(Action::ReportMenu) => { self.reports.search.push_str(text); self.reports.table_state.select(Some(0)); } _ => {} } } pub fn get_context(&mut self) -> Result<()> { let output = std::process::Command::new(&self.task_exe).arg("_get").arg("rc.context").output()?; self.current_context = String::from_utf8_lossy(&output.stdout).to_string(); self.current_context = self.current_context.strip_suffix('\n').unwrap_or("").to_string(); // support new format for context let output = std::process::Command::new(&self.task_exe) .arg("_get") .arg(format!("rc.context.{}.read", self.current_context)) .output()?; self.current_context_filter = String::from_utf8_lossy(&output.stdout).to_string(); self.current_context_filter = self.current_context_filter.strip_suffix('\n').unwrap_or("").to_string(); // If new format is not used, check if old format is used if self.current_context_filter.is_empty() { let output = std::process::Command::new(&self.task_exe) .arg("_get") .arg(format!("rc.context.{}", self.current_context)) .output()?; self.current_context_filter = String::from_utf8_lossy(&output.stdout).to_string(); self.current_context_filter = self.current_context_filter.strip_suffix('\n').unwrap_or("").to_string(); } Ok(()) } pub fn draw(&mut self, f: &mut Frame) { let rect = f.area(); self.terminal_width = rect.width; self.terminal_height = rect.height; let chunks = Layout::default() .direction(Direction::Vertical) .constraints([Constraint::Length(1), Constraint::Min(0)]) .split(f.area()); let tab_layout = chunks[0]; let main_layout = chunks[1]; self.draw_tabs(f, tab_layout); match self.mode { Mode::Tasks(action) => self.draw_task(f, main_layout, action), Mode::Projects => self.draw_projects(f, main_layout), Mode::Timesheet => self.draw_timesheet(f, main_layout), Mode::Calendar => self.draw_calendar(f, main_layout), } } fn draw_tabs(&self, f: &mut Frame, layout: Rect) { let titles: Vec<&str> = vec!["Tasks", "Projects", "Timesheet", "Calendar"]; let tab_names: Vec<_> = titles.into_iter().map(Line::from).collect(); let selected_tab = match self.mode { Mode::Tasks(_) => 0, Mode::Projects => 1, Mode::Timesheet => 2, Mode::Calendar => 3, }; let navbar_block = Block::default().style(self.config.uda_style_navbar); let context = Line::from(vec![ Span::from(&self.report), Span::from(" "), Span::from("["), Span::from(if self.current_context.is_empty() { "none" } else { &self.current_context }), Span::from("]"), ]); let tabs = Tabs::new(tab_names) .block(navbar_block.clone()) .select(selected_tab) .divider(" ") .highlight_style(Style::default().add_modifier(Modifier::BOLD)); let rects = Layout::default() .direction(Direction::Horizontal) .constraints([Constraint::Min(0), Constraint::Length(context.width() as u16)]) .split(layout); f.render_widget(tabs, rects[0]); f.render_widget(Paragraph::new(Text::from(context)).block(navbar_block), rects[1]); } pub fn draw_debug(&mut self, f: &mut Frame) { let area = centered_rect(50, 50, f.area()); f.render_widget(Clear, area); let t = format!("{}", self.current_selection); let p = Paragraph::new(Text::from(t)).block(Block::default().borders(Borders::ALL).border_type(BorderType::Rounded)); f.render_widget(p, area); } pub fn draw_projects(&mut self, f: &mut Frame, rect: Rect) { let data = self.projects.data.clone(); let p = Paragraph::new(Text::from(&data[..])); f.render_widget(p, rect); } pub fn update_timesheet(&mut self) -> Result<()> { let output = std::process::Command::new(&self.task_exe) .arg("rc.color=off") .arg("rc._forcecolor=off") .arg("rc.verbose=nothing") .arg(format!("rc.defaultwidth={}", self.terminal_width)) .arg("timesheet") .output() .context("Unable to run `task timesheet`")?; self.timesheet_data = String::from_utf8_lossy(&output.stdout).into_owned(); self.timesheet_line_count = self.timesheet_data.lines().count() as u16; // Scroll to end so the most recent week is visible by default. self.timesheet_scroll = self.timesheet_line_count.saturating_sub(self.terminal_height); Ok(()) } fn styled_timesheet_lines(&self) -> Vec> { let style_header = self.config.color.get("color.label").copied().unwrap_or_default(); let style_completed = self.config.color.get("color.completed").copied().unwrap_or_default(); let style_active = self.config.color.get("color.active").copied().unwrap_or_default(); let style_alternate = self.config.color.get("color.alternate").copied().unwrap_or_default(); let style_footnote = self.config.color.get("color.footnote").copied().unwrap_or_default(); // Regex to detect the summary footer line: "N completed, N started." let summary_re = regex::Regex::new(r"^\d+ \w+").unwrap(); let mut lines: Vec = Vec::new(); let mut seen_header = false; let mut week_index: usize = 0; // Track the action style of the last task row so continuation lines match it. let mut last_action_style = Style::default(); for line in self.timesheet_data.lines() { let trimmed = line.trim(); // Detect the start of a new week block: lines like "W9 ...", "W10 ...", etc. let is_week_header = trimmed.starts_with('W') && trimmed.len() > 1 && trimmed.chars().nth(1).is_some_and(|c| c.is_ascii_digit()); let style = if !seen_header && (trimmed.starts_with("Wk") || trimmed.starts_with("---")) { // Column header and separator lines if !trimmed.starts_with("---") { seen_header = true; } style_header } else if is_week_header { week_index += 1; let base = if week_index.is_multiple_of(2) { style_alternate } else { Style::default() }; if trimmed.contains("Completed") { let s = base.patch(style_completed); last_action_style = s; s } else if trimmed.contains("Started") { let s = base.patch(style_active); last_action_style = s; s } else { last_action_style = base; base } } else if trimmed.is_empty() { // Blank separator between weeks — no style Style::default() } else if summary_re.is_match(trimmed) { style_footnote } else if trimmed.contains("Completed") { let base = if week_index.is_multiple_of(2) { style_alternate } else { Style::default() }; let s = base.patch(style_completed); last_action_style = s; s } else if trimmed.contains("Started") { let base = if week_index.is_multiple_of(2) { style_alternate } else { Style::default() }; let s = base.patch(style_active); last_action_style = s; s } else { // Continuation lines and non-action rows — inherit the style of the parent row. last_action_style }; lines.push(Line::styled(line.to_string(), style)); } lines } pub fn draw_timesheet(&mut self, f: &mut Frame, rect: Rect) { let p = Paragraph::new(Text::from(self.styled_timesheet_lines())).scroll((self.timesheet_scroll, 0)); f.render_widget(p, rect); } fn style_for_project(&self, project: &[String]) -> Style { let virtual_tag_names_in_precedence = &self.config.rule_precedence_color; let mut style = Style::default(); for tag_name in virtual_tag_names_in_precedence.iter().rev() { if tag_name.as_str() == "project." { let s = self .config .color .get(&format!("color.project.{}", project[0])) .copied() .unwrap_or_default(); style = style.patch(s); } } style } pub fn draw_calendar(&mut self, f: &mut Frame, layout: Rect) { let mut c = Calendar::default() .today_style(self.config.uda_style_calendar_today) .year(self.calendar_year) .date_style(self.get_dates_with_styles()) .months_per_row(self.config.uda_calendar_months_per_row) .start_on_monday(self.config.weekstart); c.title_background_color = self.config.uda_style_calendar_title.bg.unwrap_or(Color::Reset); f.render_widget(c, layout); } fn task_info_location(&mut self, width: u16) -> TaskInfoLocation { if self.config.uda_task_report_info_location == TaskInfoLocation::Auto && self.task_info_location_override_width.is_some() && self.task_info_location_override_width != Some(width) { self.task_info_location_override = None; self.task_info_location_override_width = None; } self .task_info_location_override .unwrap_or(self.config.uda_task_report_info_location) .resolve(width) } fn toggle_task_info_location(&mut self) { let next = match self.task_info_location(self.terminal_width) { TaskInfoLocation::Bottom => TaskInfoLocation::Right, TaskInfoLocation::Right => TaskInfoLocation::Bottom, TaskInfoLocation::Auto => unreachable!("task info location should always resolve to bottom or right"), }; if self.config.uda_task_report_info_location == TaskInfoLocation::Auto { if self.task_info_location_override_width == Some(self.terminal_width) { self.task_info_location_override = None; self.task_info_location_override_width = None; } else { self.task_info_location_override = Some(next); self.task_info_location_override_width = Some(self.terminal_width); } } else if self.config.uda_task_report_info_location == next { self.task_info_location_override = None; self.task_info_location_override_width = None; } else { self.task_info_location_override = Some(next); self.task_info_location_override_width = None; } } pub fn draw_task(&mut self, f: &mut Frame, layout: Rect, action: Action) { let rects = Layout::default() .direction(Direction::Vertical) .constraints([Constraint::Min(0), Constraint::Length(2)].as_ref()) .split(layout); // render task report and task details if required if self.task_report_info_show { let direction = match self.task_info_location(rects[0].width) { TaskInfoLocation::Bottom => Direction::Vertical, TaskInfoLocation::Right => Direction::Horizontal, TaskInfoLocation::Auto => unreachable!("task info location should always resolve to bottom or right"), }; let split_task_layout = Layout::default() .direction(direction) .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref()) .split(rects[0]); self.task_report_height = split_task_layout[0].height; self.draw_task_report(f, split_task_layout[0]); self.draw_task_details(f, split_task_layout[1]); } else { self.task_report_height = rects[0].height; self.draw_task_report(f, rects[0]); } // calculate selected tasks let selected = self.current_selection; let task_ids = if self.tasks.is_empty() { vec!["0".to_string()] } else { match self.task_table_state.mode() { TableMode::SingleSelection => vec![self.tasks[selected].id().unwrap_or_default().to_string()], TableMode::MultipleSelection => { let mut tids = vec![]; for uuid in &self.marked { if let Some(t) = self.task_by_uuid(*uuid) { tids.push(t.id().unwrap_or_default().to_string()); } } tids } } }; // render task mode self.handle_task_mode_action(f, &rects, &task_ids, action); } fn handle_task_mode_action(&mut self, f: &mut Frame, rects: &[Rect], task_ids: &[String], action: Action) { match action { Action::Error => { self.draw_command( f, rects[1], "Press any key to continue.", (Span::styled("Error", Style::default().add_modifier(Modifier::BOLD)), None), 0, false, self.error.clone(), None, ); let text = self.error.clone().unwrap_or_else(|| "Unknown error.".to_string()); let title = vec![Span::styled("Error", Style::default().add_modifier(Modifier::BOLD))]; let rect = centered_rect(90, 60, f.area()); f.render_widget(Clear, rect); let p = Paragraph::new(Text::from(text)) .block(Block::default().borders(Borders::ALL).border_type(BorderType::Rounded).title(title)) .wrap(Wrap { trim: true }); f.render_widget(p, rect); // draw error pop up let rects = Layout::default() .direction(Direction::Vertical) .constraints([Constraint::Min(0)].as_ref()) .split(f.area()); } Action::Report => { // reset error when entering Action::Report self.previous_mode = None; self.error = None; let position = Self::get_position(&self.command); self.draw_command( f, rects[1], self.filter.as_str(), (Span::raw("Filter Tasks"), self.history_status.as_ref().map(Span::raw)), Self::get_position(&self.filter), false, self.error.clone(), None, ); } Action::Jump => { let position = Self::get_position(&self.command); self.draw_command( f, rects[1], self.command.as_str(), (Span::styled("Jump to Task", Style::default().add_modifier(Modifier::BOLD)), None), position, true, self.error.clone(), None, ); } Action::Filter => { let position = Self::get_position(&self.filter); if self.show_completion_pane { self.draw_completion_pop_up(f, rects[1], position); } let ghost = if !self.show_completion_pane { self.completion_list.ghost_text() } else { None }; self.draw_command( f, rects[1], self.filter.as_str(), ( Span::styled("Filter Tasks", Style::default().add_modifier(Modifier::BOLD)), self .history_status .as_ref() .map(|s| Span::styled(s, Style::default().add_modifier(Modifier::BOLD))), ), position, true, self.error.clone(), ghost.as_deref(), ); } Action::Log => { if self.config.uda_auto_insert_double_quotes_on_log && self.command.is_empty() { self.command.update(r#""""#, 1, &mut self.changes); }; let position = Self::get_position(&self.command); if self.show_completion_pane { self.draw_completion_pop_up(f, rects[1], position); } let ghost = if !self.show_completion_pane { self.completion_list.ghost_text() } else { None }; self.draw_command( f, rects[1], self.command.as_str(), ( Span::styled("Log Task", Style::default().add_modifier(Modifier::BOLD)), self .history_status .as_ref() .map(|s| Span::styled(s, Style::default().add_modifier(Modifier::BOLD))), ), position, true, self.error.clone(), ghost.as_deref(), ); } Action::Subprocess => { let position = Self::get_position(&self.command); self.draw_command( f, rects[1], self.command.as_str(), (Span::styled("Shell Command", Style::default().add_modifier(Modifier::BOLD)), None), position, true, self.error.clone(), None, ); } Action::Modify => { let position = Self::get_position(&self.modify); if self.show_completion_pane { self.draw_completion_pop_up(f, rects[1], position); } let ghost = if !self.show_completion_pane { self.completion_list.ghost_text() } else { None }; let label = if task_ids.len() > 1 { format!("Modify Tasks {}", task_ids.join(",")) } else { format!("Modify Task {}", task_ids.join(",")) }; self.draw_command( f, rects[1], self.modify.as_str(), ( Span::styled(label, Style::default().add_modifier(Modifier::BOLD)), self .history_status .as_ref() .map(|s| Span::styled(s, Style::default().add_modifier(Modifier::BOLD))), ), position, true, self.error.clone(), ghost.as_deref(), ); } Action::Annotate => { if self.config.uda_auto_insert_double_quotes_on_annotate && self.command.is_empty() { self.command.update(r#""""#, 1, &mut self.changes); }; let position = Self::get_position(&self.command); if self.show_completion_pane { self.draw_completion_pop_up(f, rects[1], position); } let ghost = if !self.show_completion_pane { self.completion_list.ghost_text() } else { None }; let label = if task_ids.len() > 1 { format!("Annotate Tasks {}", task_ids.join(",")) } else { format!("Annotate Task {}", task_ids.join(",")) }; self.draw_command( f, rects[1], self.command.as_str(), ( Span::styled(label, Style::default().add_modifier(Modifier::BOLD)), self .history_status .as_ref() .map(|s| Span::styled(s, Style::default().add_modifier(Modifier::BOLD))), ), position, true, self.error.clone(), ghost.as_deref(), ); } Action::Add => { if self.config.uda_auto_insert_double_quotes_on_add && self.command.is_empty() { self.command.update(r#""""#, 1, &mut self.changes); }; let position = Self::get_position(&self.command); if self.show_completion_pane { self.draw_completion_pop_up(f, rects[1], position); } let ghost = if !self.show_completion_pane { self.completion_list.ghost_text() } else { None }; self.draw_command( f, rects[1], self.command.as_str(), ( Span::styled("Add Task", Style::default().add_modifier(Modifier::BOLD)), self .history_status .as_ref() .map(|s| Span::styled(s, Style::default().add_modifier(Modifier::BOLD))), ), position, true, self.error.clone(), ghost.as_deref(), ); } Action::HelpPopup => { self.draw_command( f, rects[1], self.filter.as_str(), ("Filter Tasks".into(), None), Self::get_position(&self.filter), false, self.error.clone(), None, ); self.draw_help_popup(f, 80, 90); } Action::ContextMenu => { self.draw_command( f, rects[1], self.filter.as_str(), ("Filter Tasks".into(), None), Self::get_position(&self.filter), false, self.error.clone(), None, ); self.draw_context_menu(f, 80, 50); } Action::ReportMenu => { self.draw_command( f, rects[1], self.filter.as_str(), ("Filter Tasks".into(), None), Self::get_position(&self.filter), false, self.error.clone(), None, ); self.draw_report_menu(f, 80, 50); } Action::DonePrompt => { let label = if task_ids.len() > 1 { format!("Done Tasks {}?", task_ids.join(",")) } else { format!("Done Task {}?", task_ids.join(",")) }; let x = match self.keyconfig.done { KeyCode::Char(c) => c.to_string(), _ => "Enter".to_string(), }; let q = match self.keyconfig.quit { KeyCode::Char(c) => c.to_string(), _ => "Esc".to_string(), }; self.draw_command( f, rects[1], &format!("Press <{}> to confirm or <{}> to abort.", x, q), (Span::styled(label, Style::default().add_modifier(Modifier::BOLD)), None), 0, false, self.error.clone(), None, ); } Action::DeletePrompt => { let label = if task_ids.len() > 1 { format!("Delete Tasks {}?", task_ids.join(",")) } else { format!("Delete Task {}?", task_ids.join(",")) }; let x = match self.keyconfig.delete { KeyCode::Char(c) => c.to_string(), _ => "Enter".to_string(), }; let q = match self.keyconfig.quit { KeyCode::Char(c) => c.to_string(), _ => "Esc".to_string(), }; self.draw_command( f, rects[1], &format!("Press <{}> to confirm or <{}> to abort.", x, q), (Span::styled(label, Style::default().add_modifier(Modifier::BOLD)), None), 0, false, self.error.clone(), None, ); } Action::UndoPrompt => { let label = "Run `task undo`?"; let k = match self.keyconfig.undo { KeyCode::Char(c) => c.to_string(), _ => "Enter".to_string(), }; let q = match self.keyconfig.quit { KeyCode::Char(c) => c.to_string(), _ => "Esc".to_string(), }; self.draw_command( f, rects[1], &format!("Press <{}> to confirm or <{}> to abort.", k, q), (Span::styled(label, Style::default().add_modifier(Modifier::BOLD)), None), 0, false, self.error.clone(), None, ); } } } pub fn get_dates_with_styles(&self) -> Vec<(chrono::NaiveDate, Style)> { if !self.tasks.is_empty() { let tasks = &self.tasks; tasks .iter() .filter_map(|t| t.due().map(|d| (d.clone(), self.style_for_task(t)))) .map(|(d, t)| (datetime::local_from_utc(&d).date_naive(), t)) .collect() } else { vec![] } } pub fn get_position(lb: &LineBuffer) -> usize { utils::display_width(&lb.as_str()[..lb.pos()]) } fn draw_help_popup(&mut self, f: &mut Frame, percent_x: u16, percent_y: u16) { let area = centered_rect(percent_x, percent_y, f.area()); f.render_widget(Clear, area); let chunks = Layout::default() .constraints([Constraint::Max(area.height - 1), Constraint::Max(1)].as_ref()) .margin(0) .split(area); self.help_popup.scroll = std::cmp::min( self.help_popup.scroll, (self.help_popup.text_height as u16).saturating_sub(chunks[0].height - 3), ); let ratio = ((self.help_popup.scroll + chunks[0].height) as f64 / self.help_popup.text_height as f64).min(1.0); let gauge = LineGauge::default() .block(Block::default()) .filled_style(self.config.uda_style_help_gauge) .ratio(ratio); f.render_widget(gauge, chunks[1]); f.render_widget(&self.help_popup, chunks[0]); } fn draw_context_menu(&mut self, f: &mut Frame, percent_x: u16, percent_y: u16) { let area = centered_rect(percent_x, percent_y, f.area()); f.render_widget(Clear, area.inner(Margin { vertical: 0, horizontal: 0 })); // Split the popup: 1 row for search bar, remainder for the table. let chunks = Layout::default() .direction(Direction::Vertical) .constraints([Constraint::Length(3), Constraint::Min(0)]) .split(area); // --- Search bar --- let search_text = format!(" {}", self.contexts.search); let cursor_x = chunks[0].x + 2 + self.contexts.search.len() as u16; let cursor_y = chunks[0].y + 1; f.render_widget( Paragraph::new(search_text).block( Block::default() .borders(Borders::ALL) .border_type(BorderType::Rounded) .title(Span::styled("Context (type to filter)", Style::default().add_modifier(Modifier::BOLD))), ), chunks[0], ); f.set_cursor_position(Position { x: cursor_x.min(chunks[0].x + chunks[0].width.saturating_sub(2)), y: cursor_y, }); // --- Filtered table --- let indices = self.contexts.filtered_indices(); let selected_filtered = self.contexts.table_state.current_selection().unwrap_or(0); let headers = ["Name", "Definition", "Active"]; let filtered_rows: Vec> = indices .iter() .map(|&ri| { let r = &self.contexts.rows[ri]; vec![r.name.clone(), r.definition.clone(), r.active.clone()] }) .collect(); let maximum_column_width = chunks[1].width; let widths = self.calculate_widths( &filtered_rows, &headers.iter().map(|s| s.to_string()).collect::>(), maximum_column_width, ); let constraints: Vec = widths .iter() .map(|w| Constraint::Length((*w).try_into().unwrap_or(maximum_column_width))) .collect(); let mut highlight_style = Style::default(); let rows: Vec>> = filtered_rows .iter() .enumerate() .map(|(fi, row)| { let ri = indices[fi]; let mut style = Style::default(); if self.contexts.rows[ri].active == "yes" { style = self.config.uda_style_context_active; } if fi == selected_filtered { highlight_style = style; } Row::StyledData(row.iter(), style) }) .collect(); let highlight_style = highlight_style.add_modifier(Modifier::BOLD); let t = Table::new(headers.iter(), rows.into_iter()) .block(Block::default().borders(Borders::ALL).border_type(BorderType::Rounded)) .header_style( self .config .color .get("color.label") .copied() .unwrap_or_default() .add_modifier(Modifier::UNDERLINED), ) .highlight_style(highlight_style) .highlight_symbol(&self.config.uda_selection_indicator) .widths(&constraints); f.render_stateful_widget(t, chunks[1], &mut self.contexts.table_state); } fn draw_report_menu(&mut self, f: &mut Frame, percent_x: u16, percent_y: u16) { let area = centered_rect(percent_x, percent_y, f.area()); f.render_widget(Clear, area.inner(Margin { vertical: 0, horizontal: 0 })); // Split the popup: 1 row for search bar, remainder for the table. let chunks = Layout::default() .direction(Direction::Vertical) .constraints([Constraint::Length(3), Constraint::Min(0)]) .split(area); // --- Search bar --- let search_text = format!(" {}", self.reports.search); let cursor_x = chunks[0].x + 2 + self.reports.search.len() as u16; let cursor_y = chunks[0].y + 1; f.render_widget( Paragraph::new(search_text).block( Block::default() .borders(Borders::ALL) .border_type(BorderType::Rounded) .title(Span::styled("Report (type to filter)", Style::default().add_modifier(Modifier::BOLD))), ), chunks[0], ); f.set_cursor_position(Position { x: cursor_x.min(chunks[0].x + chunks[0].width.saturating_sub(2)), y: cursor_y, }); // --- Filtered table --- let indices = self.reports.filtered_indices(); let selected_filtered = self.reports.table_state.current_selection().unwrap_or(0); let headers = ["Name", "Description", "Active"]; let filtered_rows: Vec> = indices .iter() .map(|&ri| { let r = &self.reports.rows[ri]; vec![r.name.clone(), r.description.clone(), r.active.clone()] }) .collect(); let maximum_column_width = chunks[1].width; let widths = self.calculate_widths( &filtered_rows, &headers.iter().map(|s| s.to_string()).collect::>(), maximum_column_width, ); let constraints: Vec = widths .iter() .map(|w| Constraint::Length((*w).try_into().unwrap_or(maximum_column_width))) .collect(); let mut highlight_style = Style::default(); let rows: Vec>> = filtered_rows .iter() .enumerate() .map(|(fi, row)| { let ri = indices[fi]; let mut style = Style::default(); if self.reports.rows[ri].active == "yes" { style = self.config.uda_style_report_menu_active; } if fi == selected_filtered { highlight_style = style; } Row::StyledData(row.iter(), style) }) .collect(); let highlight_style = highlight_style.add_modifier(Modifier::BOLD); let t = Table::new(headers.iter(), rows.into_iter()) .block(Block::default().borders(Borders::ALL).border_type(BorderType::Rounded)) .header_style( self .config .color .get("color.label") .copied() .unwrap_or_default() .add_modifier(Modifier::UNDERLINED), ) .highlight_style(highlight_style) .highlight_symbol(&self.config.uda_selection_indicator) .widths(&constraints); f.render_stateful_widget(t, chunks[1], &mut self.reports.table_state); } fn draw_completion_pop_up(&mut self, f: &mut Frame, rect: Rect, cursor_position: usize) { if self.completion_list.candidates().is_empty() { self.show_completion_pane = false; return; } // Iterate through all elements in the `items` app and append some debug text to it. let items: Vec = self .completion_list .candidates() .iter() .map(|p| { let lines = vec![Line::from(vec![ Span::styled(p.3.clone(), Style::default().add_modifier(Modifier::BOLD)), Span::from(p.4.clone()), ])]; ListItem::new(lines) }) .collect(); // Create a List from all list items and highlight the currently selected one let items = List::new(items) .block(Block::default().borders(Borders::NONE).title("")) .style(self.config.uda_style_report_completion_pane) .highlight_style(self.config.uda_style_report_completion_pane_highlight) .highlight_symbol(self.config.uda_selection_indicator.as_str()); let area = f.area(); let mut rect = rect; rect.height = std::cmp::min(area.height / 2, self.completion_list.len() as u16 + 2); rect.width = std::cmp::min( area.width / 2, self.completion_list.max_width().unwrap_or(40).try_into().unwrap_or(area.width / 2), ); rect.y = rect.y.saturating_sub(rect.height); if cursor_position as u16 + rect.width >= area.width { rect.x = area.width - rect.width; } else { rect.x = cursor_position as u16; } // We can now render the item list f.render_widget(Clear, rect); f.render_stateful_widget(items, rect, &mut self.completion_list.state); } fn draw_command( &self, f: &mut Frame, rect: Rect, text: &str, title: (Span, Option), position: usize, cursor: bool, error: Option, ghost_text: Option<&str>, ) { let rendered_text = utils::display_control_chars(text); let rendered_ghost_text = ghost_text.map(utils::display_control_chars); // f.render_widget(Clear, rect); if cursor { let position = Position::new(std::cmp::min(rect.x + position as u16, rect.x + rect.width.saturating_sub(2)), rect.y + 1); f.set_cursor_position(position); } let rects = Layout::default() .direction(Direction::Vertical) .constraints([Constraint::Length(1), Constraint::Min(0)].as_ref()) .split(rect); // render command title let mut style = self.config.uda_style_command; if error.is_some() { style = self.config.uda_style_command_error; }; let title_spans = if let Some(subtitle) = title.1 { Line::from(vec![title.0, Span::from(" ["), subtitle, Span::from("]")]) } else { Line::from(vec![title.0]) }; let title = Paragraph::new(Text::from(title_spans)).style(style); f.render_widget(title, rects[0]); // render command with optional ghost text suffix let scroll = (0, ((position + 2) as u16).saturating_sub(rects[1].width)); let p = if let Some(ghost) = rendered_ghost_text { let line = Line::from(vec![Span::raw(rendered_text), Span::styled(ghost, Style::default().fg(Color::DarkGray))]); Paragraph::new(Text::from(line)).scroll(scroll) } else { Paragraph::new(Text::from(rendered_text)).scroll(scroll) }; f.render_widget(p, rects[1]); } fn draw_task_details(&mut self, f: &mut Frame, rect: Rect) { if self.tasks.is_empty() { let p = Paragraph::new(Text::from("Task not found")).block(Block::default().borders(Borders::TOP)); f.render_widget(p, rect); return; } let selected = self.current_selection; let task_id = self.tasks[selected].id().unwrap_or_default(); let task_uuid = *self.tasks[selected].uuid(); let data = match self.task_details.get(&task_uuid) { Some(s) => s.clone(), None => "Loading task details ...".to_string(), }; self.task_details_scroll = std::cmp::min( (data.lines().count() as u16).saturating_sub(rect.height).saturating_add(2), self.task_details_scroll, ); let p = Paragraph::new(Text::from(&data[..])) .block(Block::default().borders(Borders::TOP)) .scroll((self.task_details_scroll, 0)); f.render_widget(p, rect); } fn task_details_scroll_up(&mut self) { self.task_details_scroll = self.task_details_scroll.saturating_sub(1); } fn task_details_scroll_down(&mut self) { self.task_details_scroll = self.task_details_scroll.saturating_add(1); } fn task_by_index(&self, i: usize) -> Option { let tasks = &self.tasks; if i >= tasks.len() { None } else { Some(tasks[i].clone()) } } fn task_by_uuid(&self, uuid: Uuid) -> Option { let tasks = &self.tasks; let m = tasks.iter().find(|t| *t.uuid() == uuid); m.cloned() } fn task_by_id(&self, id: u64) -> Option { let tasks = &self.tasks; let m = tasks.iter().find(|t| t.id() == Some(id)); m.cloned() } fn task_index_by_id(&self, id: u64) -> Option { let tasks = &self.tasks; tasks.iter().position(|t| t.id() == Some(id)) } fn task_index_by_uuid(&self, uuid: Uuid) -> Option { let tasks = &self.tasks; tasks.iter().position(|t| *t.uuid() == uuid) } fn style_for_task(&self, task: &Task) -> Style { let virtual_tag_names_in_precedence = &self.config.rule_precedence_color; let mut style = Style::default(); for tag_name in virtual_tag_names_in_precedence.iter().rev() { if tag_name == "uda.priority." || tag_name == "priority" { if let Some(p) = task.priority() { let s = self.config.color.get(&format!("color.uda.priority.{}", p)).copied().unwrap_or_default(); style = style.patch(s); } } else if tag_name == "uda." { for (uda_name, uda_value) in task.uda().iter() { let uda_value_str = match uda_value { UDAValue::Str(s) => s.to_string(), UDAValue::F64(f) => f.to_string(), UDAValue::U64(u) => u.to_string(), }; let color_uda_name = format!("color.uda.{}.{}", uda_name, uda_value_str); let s = self.config.color.get(&color_uda_name).copied().unwrap_or_default(); style = style.patch(s); } } else if tag_name == "tag." { if let Some(tags) = task.tags() { for t in tags { let color_tag_name = format!("color.tag.{}", t); let s = self.config.color.get(&color_tag_name).copied().unwrap_or_default(); style = style.patch(s); } } } else if tag_name == "project." { if let Some(p) = task.project() { let s = self.config.color.get(&format!("color.project.{}", p)).copied().unwrap_or_default(); style = style.patch(s); } } else if tag_name == "keyword." { let desc = task.description(); for (keyword, kw_style) in &self.config.color_keywords { if desc.contains(keyword.as_str()) { style = style.patch(*kw_style); } } } else if task .tags() .unwrap_or(&vec![]) .contains(&tag_name.to_string().replace('.', "").to_uppercase()) { let color_tag_name = format!("color.{}", tag_name); let s = self.config.color.get(&color_tag_name).copied().unwrap_or_default(); style = style.patch(s); } } style } fn task_report_row_style(&self, row_index: usize, task: &Task) -> Style { let base_style = if row_index % 2 == 1 && self.config.uda_task_report_use_alternate_style { self.config.color.get("color.alternate").copied().unwrap_or_default() } else { Style::default() }; base_style.patch(self.style_for_task(task)) } pub fn calculate_widths(&self, tasks: &[Vec], headers: &[String], maximum_column_width: u16) -> Vec { // naive implementation of calculate widths let mut widths = headers.iter().map(String::len).collect::>(); for row in tasks.iter() { for (i, cell) in row.iter().enumerate() { widths[i] = std::cmp::max(cell.len(), widths[i]); } } for (i, header) in headers.iter().enumerate() { if header == "Description" || header == "Definition" { // always give description or definition the most room to breath widths[i] = maximum_column_width as usize; break; } } for (i, header) in headers.iter().enumerate() { if i == 0 { // always give ID a couple of extra for indicator widths[i] += self.config.uda_selection_indicator.as_str().width(); // if let TableMode::MultipleSelection = self.task_table_state.mode() { // widths[i] += 2 // }; } } // now start trimming while (widths.iter().sum::() as u16) >= maximum_column_width - (headers.len()) as u16 { let index = widths.iter().position(|i| i == widths.iter().max().unwrap_or(&0)).unwrap_or_default(); if widths[index] == 1 { break; } widths[index] -= 1; } widths } fn draw_task_report(&mut self, f: &mut Frame, rect: Rect) { let (tasks, headers) = self.get_task_report(); if tasks.is_empty() { f.render_widget(Block::default(), rect); return; } let maximum_column_width = rect.width; let widths = self.calculate_widths(&tasks, &headers, maximum_column_width); for (i, header) in headers.iter().enumerate() { if header == "Description" || header == "Definition" { self.task_report_table.description_width = widths[i] - 1; break; } } let selected = self.current_selection; let header = headers.iter(); let mut rows = vec![]; let mut highlight_style = Style::default(); let mut pos = 0; for (i, task) in tasks.iter().enumerate() { let style = self.task_report_row_style(i, &self.tasks[i]); if i == selected { pos = i; highlight_style = style.patch(self.config.uda_style_report_selection); if self.config.uda_selection_bold { highlight_style = highlight_style.add_modifier(Modifier::BOLD); } if self.config.uda_selection_italic { highlight_style = highlight_style.add_modifier(Modifier::ITALIC); } if self.config.uda_selection_dim { highlight_style = highlight_style.add_modifier(Modifier::DIM); } if self.config.uda_selection_blink { highlight_style = highlight_style.add_modifier(Modifier::SLOW_BLINK); } if self.config.uda_selection_reverse { highlight_style = highlight_style.add_modifier(Modifier::REVERSED); } } rows.push(Row::StyledData(task.iter(), style)); } let constraints: Vec = widths .iter() .map(|i| Constraint::Length((*i).try_into().unwrap_or(maximum_column_width))) .collect(); let t = Table::new(header, rows.into_iter()) .header_style( self .config .color .get("color.label") .copied() .unwrap_or_default() .add_modifier(Modifier::UNDERLINED), ) .highlight_style(highlight_style) .highlight_symbol(&self.config.uda_selection_indicator) .mark_highlight_symbol(&self.config.uda_mark_highlight_indicator) .unmark_highlight_symbol(&self.config.uda_unmark_highlight_indicator) .mark_symbol(&self.config.uda_mark_indicator) .unmark_symbol(&self.config.uda_unmark_indicator) .widths(&constraints); f.render_stateful_widget(t, rect, &mut self.task_table_state); if tasks.iter().len() as u16 > rect.height.saturating_sub(4) { let mut widget = Scrollbar::new(pos, tasks.iter().len()); widget.pos_style = self.config.uda_style_report_scrollbar; widget.pos_symbol.clone_from(&self.config.uda_scrollbar_indicator); widget.area_style = self.config.uda_style_report_scrollbar_area; widget.area_symbol.clone_from(&self.config.uda_scrollbar_area); f.render_widget(widget, rect); } } fn get_task_report(&mut self) -> (Vec>, Vec) { self.task_report_table.generate_table(&self.tasks); let (tasks, headers) = self.task_report_table.simplify_table(); (tasks, headers) } pub async fn update(&mut self, force: bool) -> Result<()> { trace!("self.update({:?});", force); if force || self.dirty || self.tasks_changed_since(self.last_export).unwrap_or(true) { self.get_context()?; let task_uuids = self.selected_task_uuids(); if self.current_selection_uuid.is_none() && self.current_selection_id.is_none() && let [uuid] = task_uuids.as_slice() { self.current_selection_uuid = Some(*uuid); } self.task_report_table.export_headers(None, &self.report, &self.task_exe)?; self.export_tasks()?; if self.config.uda_task_report_use_all_tasks_for_completion { self.export_all_tasks()?; } self.contexts.update_data(&self.task_exe)?; self.reports.update_data(&self.report, &Self::task_show_output(&self.task_exe)?); self.projects.update_data(&self.task_exe)?; self.update_timesheet()?; self.update_tags(); self.task_details.clear(); self.task_details_modified.clear(); self.dirty = false; self.save_history()?; // Some operations like export or summary change the taskwarrior database. // The export time therefore gets set at the end, to avoid an infinite update loop. self.last_export = Some(std::time::SystemTime::now()); } self.cursor_fix(); self.update_task_table_state(); if self.task_report_info_show { self.update_task_details().await?; } self.selection_fix(); Ok(()) } pub fn selection_fix(&mut self) { if let (Some(t), Some(id)) = (self.task_current(), self.current_selection_id) && t.id() != Some(id) && let Some(i) = self.task_index_by_id(id) { self.current_selection = i; self.current_selection_id = None; } if let (Some(t), Some(uuid)) = (self.task_current(), self.current_selection_uuid) && t.uuid() != &uuid && let Some(i) = self.task_index_by_uuid(uuid) { self.current_selection = i; self.current_selection_uuid = None; } } pub fn save_history(&mut self) -> Result<()> { self.filter_history.write()?; self.command_history.write()?; Ok(()) } pub fn cursor_fix(&mut self) { while !self.tasks.is_empty() && self.current_selection >= self.tasks.len() { self.task_report_previous(); } } pub async fn update_task_details(&mut self) -> Result<()> { if self.tasks.is_empty() { return Ok(()); } let defaultwidth = self.terminal_width.saturating_sub(2); if self.task_details_defaultwidth != defaultwidth { self.task_details.clear(); self.task_details_modified.clear(); self.task_details_defaultwidth = defaultwidth; } // remove task_details of tasks not in task report let mut to_delete = vec![]; for k in self.task_details.keys() { if !self.tasks.iter().map(Task::uuid).any(|x| x == k) { to_delete.push(*k); } } for k in to_delete { self.task_details.remove(&k); self.task_details_modified.remove(&k); } let selected = self.current_selection; if selected >= self.tasks.len() { return Ok(()); } let mut l = vec![selected]; for s in 1..=self.config.uda_task_detail_prefetch { l.insert(0, std::cmp::min(selected.saturating_sub(s), self.tasks.len() - 1)); l.push(std::cmp::min(selected + s, self.tasks.len() - 1)); } l.dedup(); let (tx, mut rx) = tokio::sync::mpsc::channel(100); let tasks = self.tasks.clone(); let task_exe = self.task_exe.clone(); for s in &l { if tasks.is_empty() { return Ok(()); } if s >= &tasks.len() { break; } let task_uuid = *tasks[*s].uuid(); let task_modified = tasks[*s].modified().cloned(); let cached_modified = self.task_details_modified.get(&task_uuid).cloned(); if !self.task_details.contains_key(&task_uuid) || cached_modified != Some(task_modified.clone()) { debug!("Running task details for {}", task_uuid); let _tx = tx.clone(); let task_exe = task_exe.clone(); tokio::spawn(async move { let output = tokio::process::Command::new(&task_exe) .arg("rc.color=off") .arg("rc._forcecolor=off") .arg(format!("rc.defaultwidth={}", defaultwidth)) .arg(format!("{}", task_uuid)) .output() .await; if let Ok(output) = output { let data = String::from_utf8_lossy(&output.stdout).to_string(); _tx.send(Some((task_uuid, task_modified, data))).await.unwrap(); } }); } } drop(tx); while let Some(Some((task_uuid, task_modified, data))) = rx.recv().await { self.task_details.insert(task_uuid, data); self.task_details_modified.insert(task_uuid, task_modified); } Ok(()) } pub fn update_task_table_state(&mut self) { trace!("self.update_task_table_state()"); self.task_table_state.select(Some(self.current_selection)); for uuid in self.marked.clone() { if self.task_by_uuid(uuid).is_none() { self.marked.remove(&uuid); } } if self.marked.is_empty() { self.task_table_state.single_selection(); } self.task_table_state.clear(); for uuid in &self.marked { self.task_table_state.mark(self.task_index_by_uuid(*uuid)); } } pub fn context_next(&mut self) { let n = self.contexts.filtered_indices().len(); if n == 0 { return; } let i = match self.contexts.table_state.current_selection() { Some(i) => { if i >= n - 1 { 0 } else { i + 1 } } None => 0, }; self.contexts.table_state.select(Some(i)); } pub fn context_previous(&mut self) { let n = self.contexts.filtered_indices().len(); if n == 0 { return; } let i = match self.contexts.table_state.current_selection() { Some(i) => { if i == 0 { n - 1 } else { i - 1 } } None => 0, }; self.contexts.table_state.select(Some(i)); } pub fn context_select(&mut self) -> Result { let fi = self.contexts.table_state.current_selection().unwrap_or_default(); let indices = self.contexts.filtered_indices(); let Some(ri) = indices.get(fi).copied() else { return Ok(false); }; let context = self.contexts.rows.get(ri).map(|r| r.name.clone()).unwrap_or_default(); if context.is_empty() { return Ok(false); } let mut command = std::process::Command::new(&self.task_exe); command.arg("context").arg(context); command.output()?; Ok(true) } fn context_selected_row_index(&self) -> Option { let fi = self.contexts.table_state.current_selection()?; self.contexts.filtered_indices().get(fi).copied() } fn restore_context_selection(&mut self, previous_row_index: Option) { let indices = self.contexts.filtered_indices(); let selection = previous_row_index .and_then(|ri| indices.iter().position(|&filtered_ri| filtered_ri == ri)) .or_else(|| (!indices.is_empty()).then_some(0)); self.contexts.table_state.select(selection); } async fn maybe_autoselect_context_menu(&mut self) -> Result<()> { if !self.config.uda_context_menu_select_on_move { return Ok(()); } if self.error.is_some() { self.previous_mode = Some(self.mode.clone()); self.mode = Mode::Tasks(Action::Error); return Ok(()); } match self.context_select() { Ok(true) => self.update(true).await?, Ok(false) => {} Err(e) => { self.error = Some(e.to_string()); } } Ok(()) } pub fn report_next(&mut self) { let n = self.reports.filtered_indices().len(); if n == 0 { return; } let i = match self.reports.table_state.current_selection() { Some(i) => { if i >= n - 1 { 0 } else { i + 1 } } None => 0, }; self.reports.table_state.select(Some(i)); } pub fn report_previous(&mut self) { let n = self.reports.filtered_indices().len(); if n == 0 { return; } let i = match self.reports.table_state.current_selection() { Some(i) => { if i == 0 { n - 1 } else { i - 1 } } None => 0, }; self.reports.table_state.select(Some(i)); } pub fn report_select(&mut self, data: &str) -> Result { let fi = self.reports.table_state.current_selection().unwrap_or_default(); let indices = self.reports.filtered_indices(); let Some(ri) = indices.get(fi).copied() else { return Ok(false); }; let report = self.reports.rows.get(ri).map(|r| r.name.clone()).unwrap_or_default(); if report.is_empty() { return Ok(false); } self.report = report; self.config.filter = Config::get_filter(data, &self.report)?; if !self.config.filter.trim().is_empty() { self.config.filter = format!("{} ", self.config.filter.trim()); } self.filter = LineBuffer::with_capacity(MAX_LINE); for c in self.config.filter.chars() { self.filter.insert(c, 1, &mut self.changes); } self.task_report_table.export_headers(Some(data), &self.report, &self.task_exe)?; Ok(true) } fn report_selected_row_index(&self) -> Option { let fi = self.reports.table_state.current_selection()?; self.reports.filtered_indices().get(fi).copied() } fn restore_report_selection(&mut self, previous_row_index: Option) { let indices = self.reports.filtered_indices(); let selection = previous_row_index .and_then(|ri| indices.iter().position(|&filtered_ri| filtered_ri == ri)) .or_else(|| (!indices.is_empty()).then_some(0)); self.reports.table_state.select(selection); } async fn maybe_autoselect_report_menu(&mut self) -> Result<()> { if !self.config.uda_report_menu_select_on_move { return Ok(()); } if self.error.is_some() { self.previous_mode = Some(self.mode.clone()); self.mode = Mode::Tasks(Action::Error); return Ok(()); } let data = Self::task_show_output(&self.task_exe)?; match self.report_select(&data) { Ok(true) => self.update(true).await?, Ok(false) => {} Err(e) => { self.error = Some(e.to_string()); } } Ok(()) } fn task_show_output(task_exe: &str) -> Result { let output = std::process::Command::new(task_exe) .arg("rc.color=off") .arg("rc._forcecolor=off") .arg("rc.defaultwidth=0") .arg("show") .output() .context("Unable to run `task show`.")?; Ok(String::from_utf8_lossy(&output.stdout).into_owned()) } pub fn task_report_top(&mut self) { if self.tasks.is_empty() { return; } self.current_selection = 0; self.current_selection_id = None; self.current_selection_uuid = None; } pub fn task_report_bottom(&mut self) { if self.tasks.is_empty() { return; } self.current_selection = self.tasks.len() - 1; self.current_selection_id = None; self.current_selection_uuid = None; } pub fn task_report_next(&mut self) { if self.tasks.is_empty() { return; } let i = { if self.current_selection >= self.tasks.len() - 1 { if self.config.uda_task_report_looping { 0 } else { self.current_selection } } else { self.current_selection + 1 } }; self.current_selection = i; self.current_selection_id = None; self.current_selection_uuid = None; } pub fn task_report_previous(&mut self) { if self.tasks.is_empty() { return; } let i = { if self.current_selection == 0 { if self.config.uda_task_report_looping { self.tasks.len() - 1 } else { 0 } } else { self.current_selection - 1 } }; self.current_selection = i; self.current_selection_id = None; self.current_selection_uuid = None; } pub fn task_report_next_page(&mut self) { if self.tasks.is_empty() { return; } let i = { if self.current_selection == self.tasks.len() - 1 { if self.config.uda_task_report_looping { 0 } else { self.tasks.len() - 1 } } else { std::cmp::min( self .current_selection .checked_add(self.task_report_height as usize) .unwrap_or(self.tasks.len() - 1), self.tasks.len() - 1, ) } }; self.current_selection = i; self.current_selection_id = None; self.current_selection_uuid = None; } pub fn task_report_previous_page(&mut self) { if self.tasks.is_empty() { return; } let i = { if self.current_selection == 0 { if self.config.uda_task_report_looping { self.tasks.len() - 1 } else { 0 } } else { self.current_selection.saturating_sub(self.task_report_height as usize) } }; self.current_selection = i; self.current_selection_id = None; self.current_selection_uuid = None; } pub fn task_report_jump(&mut self) -> Result<()> { if self.tasks.is_empty() { return Ok(()); } let i = self.command.as_str().parse::()?; if let Some(task) = self.task_by_id(i as u64) { let j = self.task_index_by_uuid(*task.uuid()).unwrap_or_default(); self.current_selection = j; self.current_selection_id = None; self.current_selection_uuid = None; Ok(()) } else { Err(anyhow!("Cannot locate task id {} in report", i)) } } fn get_task_database_mtime(&self) -> Result { let data_dir = shellexpand::tilde(&self.config.data_location); let database_path = Path::new(data_dir.as_ref()).join("taskchampion.sqlite3"); let metadata = fs::metadata(database_path).context("Fetching the metadate of the task database failed")?; let mtime = metadata .modified() .context("Could not get mtime of task database, but fetching metadata succeeded")?; Ok(mtime) } pub fn tasks_changed_since(&mut self, prev: Option) -> Result { if let Some(prev) = prev { let mtime = self.get_task_database_mtime()?; if mtime > prev { Ok(true) } else { // Unfortunately, we can not use std::time::Instant which is guaranteed to be monotonic, // because we need to compare it to a file mtime as SystemTime, so as a safety for unexpected // time shifts, cap maximum wait to 1 min let now = SystemTime::now(); let max_delta = Duration::from_secs(60); Ok(now.duration_since(prev)? > max_delta) } } else { Ok(true) } } pub fn export_all_tasks(&mut self) -> Result<()> { let mut task = std::process::Command::new(&self.task_exe); task .arg("rc.json.array=on") .arg("rc.confirmation=off") .arg("rc.json.depends.array=on") .arg("rc.color=off") .arg("rc._forcecolor=off"); // .arg("rc.verbose:override=false"); task.arg("export"); task.arg("all"); info!("Running `{:?}`", task); let output = task.output()?; let data = String::from_utf8_lossy(&output.stdout); let error = String::from_utf8_lossy(&output.stderr); if output.status.success() { let imported = import(data.as_bytes()); match imported { Ok(imported) => { self.all_tasks = imported; info!("Imported {} tasks", self.tasks.len()); self.error = None; if self.mode == Mode::Tasks(Action::Error) { self.mode = self.previous_mode.clone().unwrap_or(Mode::Tasks(Action::Report)); self.previous_mode = None; } } Err(err) => { self.error = Some(format!("Unable to parse output of `{:?}`:\n`{:?}`\n\n{}", task, data, err)); self.mode = Mode::Tasks(Action::Error); debug!("Unable to parse output: {:?}", data); debug!("Error: {:?}", err); } } } else { self.error = Some(format!("Cannot run `{:?}` - ({}) error:\n{}", &task, output.status, error)); } Ok(()) } pub fn export_tasks(&mut self) -> Result<()> { let mut task = std::process::Command::new(&self.task_exe); task .arg("rc.json.array=on") .arg("rc.confirmation=off") .arg("rc.json.depends.array=on") .arg("rc.color=off") .arg("rc._forcecolor=off"); // .arg("rc.verbose:override=false"); if let Some(args) = shlex::split(format!(r#"rc.report.{}.filter='{}'"#, self.report, self.filter.trim()).trim()) { for arg in args { task.arg(arg); } } if !self.current_context_filter.trim().is_empty() && self.task_version >= *TASKWARRIOR_VERSION_SUPPORTED { if let Some(args) = shlex::split(&self.current_context_filter) { for arg in args { task.arg(arg); } } } else if !self.current_context_filter.trim().is_empty() { task.arg(format!("'\\({}\\)'", self.current_context_filter)); } task.arg("export"); if self.task_version >= *TASKWARRIOR_VERSION_SUPPORTED { task.arg(&self.report); } info!("Running `{:#?}`", task); let output = task.output()?; let data = String::from_utf8_lossy(&output.stdout); let error = String::from_utf8_lossy(&output.stderr); if output.status.success() { let imported = import(data.as_bytes()); match imported { Ok(imported) => { self.tasks = imported; info!("Imported {} tasks", self.tasks.len()); self.error = None; if self.mode == Mode::Tasks(Action::Error) { self.mode = self.previous_mode.clone().unwrap_or(Mode::Tasks(Action::Report)); self.previous_mode = None; } } Err(err) => { self.error = Some(format!("Unable to parse output of `{:?}`:\n`{:?}`", task, data)); self.mode = Mode::Tasks(Action::Error); debug!("Unable to parse output:\n\n{}", data); debug!("Error: {:?}", err); } } } else { self.error = Some(format!("Cannot run `{:?}` - ({}) error:\n{}", &task, output.status, error)); } Ok(()) } pub fn selected_task_uuids(&self) -> Vec { let selected = match self.task_table_state.mode() { TableMode::SingleSelection => vec![self.current_selection], TableMode::MultipleSelection => self.task_table_state.marked().copied().collect::>(), }; let mut task_uuids = vec![]; for s in selected { if self.tasks.is_empty() { break; } let task_id = self.tasks[s].id().unwrap_or_default(); let task_uuid = *self.tasks[s].uuid(); task_uuids.push(task_uuid); } task_uuids } pub fn task_subprocess(&mut self) -> Result<(), String> { let task_uuids = if self.tasks.is_empty() { vec![] } else { self.selected_task_uuids() }; let shell = self.command.as_str(); let r = match shlex::split(shell) { Some(cmd) => { if cmd.is_empty() { Err(format!("Shell command empty: {}", shell)) } else { // first argument must be a binary let mut command = std::process::Command::new(&cmd[0]); // remaining arguments are args for (i, s) in cmd.iter().enumerate() { if i == 0 { continue; } command.arg(s); } let output = command.output(); match output { Ok(o) => { let output = String::from_utf8_lossy(&o.stdout); if !output.is_empty() { Err(format!( "Shell command `{}` ran successfully but printed the following output:\n\n{}\n\nSuppress output of shell commands to prevent the error prompt from showing up.", shell, output )) } else { Ok(()) } } Err(_) => Err(format!("Shell command `{}` exited with non-zero output", shell)), } } } None => Err(format!("Cannot run subprocess. Unable to shlex split `{}`", shell)), }; if let [uuid] = task_uuids.as_slice() { self.current_selection_uuid = Some(*uuid); } r } pub fn task_log(&mut self) -> Result<(), String> { let mut command = std::process::Command::new(&self.task_exe); command.arg("log"); let shell = self.command.as_str(); match shlex::split(shell) { Some(cmd) => { for s in cmd { command.arg(&s); } let output = command.output(); match output { Ok(_) => Ok(()), Err(_) => Err(format!("Cannot run `task log {}`. Check documentation for more information", shell)), } } None => Err(format!("Unable to run `{:?}`: shlex::split(`{}`) failed.", command, shell)), } } pub fn task_background(&mut self) { let shell = self.config.uda_background_process.clone(); if shell.is_empty() { return; } let shell = shellexpand::tilde(&shell).into_owned(); let period = self.config.uda_background_process_period; std::thread::spawn(move || { loop { std::thread::sleep(Duration::from_secs(period as u64)); match shlex::split(&shell) { Some(cmd) => { let mut command = std::process::Command::new(&cmd[0]); for s in cmd.iter().skip(1) { command.arg(s); } if let Ok(output) = command.output() { if !output.status.success() { break; } } else { break; } } None => break, }; } }); } pub async fn task_shortcut(&mut self, s: usize) -> Result<(), String> { self.pause_tui().await.unwrap(); let task_uuids = if self.tasks.is_empty() { vec![] } else { self.selected_task_uuids() }; let shell = &self.config.uda_shortcuts[s]; if shell.is_empty() { self.resume_tui().await.unwrap(); return Err("Trying to run empty shortcut.".to_string()); } let shell = format!( "{} {}", shell, task_uuids.iter().map(ToString::to_string).collect::>().join(" ") ); let shell = shellexpand::tilde(&shell).into_owned(); let r = match shlex::split(&shell) { Some(cmd) => { let mut command = std::process::Command::new(&cmd[0]); for i in cmd.iter().skip(1) { command.arg(i); } match command.spawn() { Ok(child) => { let output = child.wait_with_output(); match output { Ok(o) => { if o.status.success() { Ok(()) } else { Err(format!( "Unable to run shortcut {}. Status Code: {} - stdout: {} stderr: {}", s, o.status.code().unwrap_or_default(), String::from_utf8_lossy(&o.stdout), String::from_utf8_lossy(&o.stderr), )) } } Err(s) => Err(format!("`{}` failed to wait with output: {}", shell, s)), } } Err(err) => Err(format!("`{}` failed: Unable to spawn shortcut number {} - Error: {}", shell, s, err)), } } None => Err(format!("Unable to run shortcut number {}: shlex::split(`{}`) failed.", s, shell)), }; if let [uuid] = task_uuids.as_slice() { self.current_selection_uuid = Some(*uuid); } self.resume_tui().await.unwrap(); r } pub fn task_modify(&mut self) -> Result<(), String> { if self.tasks.is_empty() { return Ok(()); } let task_uuids = self.selected_task_uuids(); let mut command = std::process::Command::new(&self.task_exe); command.arg("rc.bulk=0"); command.arg("rc.confirmation=off"); command.arg("rc.dependency.confirmation=off"); command.arg("rc.recurrence.confirmation=off"); for task_uuid in &task_uuids { command.arg(task_uuid.to_string()); } command.arg("modify"); let shell = self.modify.as_str(); let r = match shlex::split(shell) { Some(cmd) => { for s in cmd { command.arg(&s); } let output = command.output(); match output { Ok(o) => { if o.status.success() { Ok(()) } else { Err(format!("Modify failed. {}", String::from_utf8_lossy(&o.stdout))) } } Err(_) => Err(format!( "Cannot run `task {:?} modify {}`. Check documentation for more information", task_uuids, shell, )), } } None => Err(format!("Cannot shlex split `{}`", shell)), }; if let [uuid] = task_uuids.as_slice() { self.current_selection_uuid = Some(*uuid); } r } pub fn task_annotate(&mut self) -> Result<(), String> { if self.tasks.is_empty() { return Ok(()); } let task_uuids = self.selected_task_uuids(); let mut command = std::process::Command::new(&self.task_exe); command.arg("rc.bulk=0"); command.arg("rc.confirmation=off"); command.arg("rc.dependency.confirmation=off"); command.arg("rc.recurrence.confirmation=off"); for task_uuid in &task_uuids { command.arg(task_uuid.to_string()); } command.arg("annotate"); let shell = self.command.as_str(); let r = match shlex::split(shell) { Some(cmd) => { for s in cmd { command.arg(&s); } let output = command.output(); match output { Ok(o) => { if o.status.success() { Ok(()) } else { Err(format!("Annotate failed. {}", String::from_utf8_lossy(&o.stdout))) } } Err(_) => Err(format!( "Cannot run `task {} annotate {}`. Check documentation for more information", task_uuids.iter().map(ToString::to_string).collect::>().join(" "), shell )), } } None => Err(format!("Cannot shlex split `{}`", shell)), }; if let [uuid] = task_uuids.as_slice() { self.current_selection_uuid = Some(*uuid); } r } pub fn task_add(&mut self) -> Result<(), String> { let mut command = std::process::Command::new(&self.task_exe); command.arg("add"); let shell = self.command.as_str(); match shlex::split(shell) { Some(cmd) => { for s in cmd { command.arg(&s); } let output = command.output(); match output { Ok(output) => { if output.status.code() == Some(0) { let data = String::from_utf8_lossy(&output.stdout); let re = Regex::new(r"^Created task (?P\d+).\n$").unwrap(); if self.config.uda_task_report_jump_to_task_on_add && let Some(caps) = re.captures(&data) { self.current_selection_id = Some(caps["task_id"].parse::().unwrap_or_default()); } Ok(()) } else { Err(format!("Error: {}", String::from_utf8_lossy(&output.stderr))) } } Err(e) => Err(format!("Cannot run `{:?}`: {}", command, e)), } } None => Err(format!("Unable to run `{:?}`: shlex::split(`{}`) failed.", command, shell)), } } pub fn task_virtual_tags(task_uuid: Uuid, task_exe: &str) -> Result { let output = std::process::Command::new(task_exe).arg(format!("{}", task_uuid)).output(); match output { Ok(output) => { let data = String::from_utf8_lossy(&output.stdout); for line in data.split('\n') { for prefix in &["Virtual tags", "Virtual"] { if line.starts_with(prefix) { let line = line.to_string(); let line = line.replace(prefix, ""); return Ok(line); } } } Err(format!( "Cannot find any tags for `task {}`. Check documentation for more information", task_uuid )) } Err(_) => Err(format!("Cannot run `task {}`. Check documentation for more information", task_uuid)), } } pub fn task_start_stop(&mut self) -> Result<(), String> { if self.tasks.is_empty() { return Ok(()); } let task_uuids = self.selected_task_uuids(); for task_uuid in &task_uuids { let mut command = "start"; for tag in TaskwarriorTui::task_virtual_tags(*task_uuid, &self.task_exe) .unwrap_or_default() .split(' ') { if tag == "ACTIVE" { command = "stop"; } } let output = std::process::Command::new(&self.task_exe) .arg(task_uuid.to_string()) .arg(command) .output(); if output.is_err() { return Err(format!("Error running `task {}` for task `{}`.", command, task_uuid)); } } if let [uuid] = task_uuids.as_slice() { self.current_selection_uuid = Some(*uuid); } Ok(()) } pub fn task_quick_tag(&mut self) -> Result<(), String> { let tag_name = &self.config.uda_quick_tag_name; let ptag_name = format!("+{}", tag_name); let ntag_name = format!("-{}", tag_name); if self.tasks.is_empty() { return Ok(()); } let task_uuids = self.selected_task_uuids(); for task_uuid in &task_uuids { if let Some(task) = self.task_by_uuid(*task_uuid) { let mut tag_to_set = &ptag_name; for tag in task.tags().unwrap() { if tag == tag_name { tag_to_set = &ntag_name; } } let output = std::process::Command::new(&self.task_exe) .arg(task_uuid.to_string()) .arg("modify") .arg(tag_to_set) .output(); if output.is_err() { return Err(format!("Error running `task modify {}` for task `{}`.", tag_to_set, task_uuid,)); } } } if let [uuid] = task_uuids.as_slice() { self.current_selection_uuid = Some(*uuid); } Ok(()) } pub fn task_delete(&mut self) -> Result<(), String> { if self.tasks.is_empty() { return Ok(()); } let task_uuids = self.selected_task_uuids(); let mut cmd = std::process::Command::new(&self.task_exe); cmd .arg("rc.bulk=0") .arg("rc.confirmation=off") .arg("rc.dependency.confirmation=off") .arg("rc.recurrence.confirmation=off"); for task_uuid in &task_uuids { cmd.arg(task_uuid.to_string()); } cmd.arg("delete"); let output = cmd.output(); let r = match output { Ok(_) => Ok(()), Err(_) => Err(format!( "Cannot run `task delete` for tasks `{}`. Check documentation for more information", task_uuids.iter().map(ToString::to_string).collect::>().join(" ") )), }; self.current_selection_uuid = None; self.current_selection_id = None; r } pub fn task_done(&mut self) -> Result<(), String> { if self.tasks.is_empty() { return Ok(()); } let task_uuids = self.selected_task_uuids(); let mut cmd = std::process::Command::new(&self.task_exe); cmd .arg("rc.bulk=0") .arg("rc.confirmation=off") .arg("rc.dependency.confirmation=off") .arg("rc.recurrence.confirmation=off"); for task_uuid in &task_uuids { cmd.arg(task_uuid.to_string()); } cmd.arg("done"); let output = cmd.output(); let r = match output { Ok(_) => Ok(()), Err(_) => Err(format!( "Cannot run `task done` for task `{}`. Check documentation for more information", task_uuids.iter().map(ToString::to_string).collect::>().join(" ") )), }; self.current_selection_uuid = None; self.current_selection_id = None; r } pub fn task_priority(&mut self, priority: &str) -> Result<(), String> { if self.tasks.is_empty() { return Ok(()); } let mut priority_arg = String::from("priority:"); priority_arg.push_str(priority); let task_uuids = self.selected_task_uuids(); let mut cmd = std::process::Command::new(&self.task_exe); cmd .arg("rc.bulk=0") .arg("rc.confirmation=off") .arg("rc.dependency.confirmation=off") .arg("rc.recurrence.confirmation=off") .arg("modify") .arg(&priority_arg); for task_uuid in &task_uuids { cmd.arg(task_uuid.to_string()); } let output = cmd.output(); let r = match output { Ok(_) => Ok(()), Err(_) => Err(format!( "Cannot run `task modify priority` for task `{}`. Check documentation for more information", task_uuids.iter().map(ToString::to_string).collect::>().join(" ") )), }; self.current_selection_uuid = None; self.current_selection_id = None; r } pub fn task_undo(&mut self) -> Result<(), String> { let output = std::process::Command::new(&self.task_exe).arg("rc.confirmation=off").arg("undo").output(); match output { Ok(output) => { let data = String::from_utf8_lossy(&output.stdout); let re = Regex::new(r"(?P[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12})").unwrap(); if let Some(caps) = re.captures(&data) && let Ok(uuid) = Uuid::parse_str(&caps["task_uuid"]) { self.current_selection_uuid = Some(uuid); } Ok(()) } Err(_) => Err("Cannot run `task undo`. Check documentation for more information".to_string()), } } pub fn task_duplicate(&mut self) -> Result<(), String> { if self.tasks.is_empty() { return Ok(()); } let task_uuids = self.selected_task_uuids(); let mut command = std::process::Command::new(&self.task_exe); command.arg("rc.bulk=0"); command.arg("rc.confirmation=off"); command.arg("rc.dependency.confirmation=off"); command.arg("rc.recurrence.confirmation=off"); for task_uuid in &task_uuids { command.arg(task_uuid.to_string()); } command.arg("duplicate"); let output = command.output(); let r = match output { Ok(o) => { if o.status.success() { Ok(()) } else { Err(format!("Modify failed. {}", String::from_utf8_lossy(&o.stdout))) } } Err(_) => Err(format!( "Cannot run `task {:?} duplicate`. Check documentation for more information", task_uuids, )), }; if let [uuid] = task_uuids.as_slice() { self.current_selection_uuid = Some(*uuid); } r } pub async fn task_edit(&mut self) -> Result<(), String> { if self.tasks.is_empty() { return Ok(()); } self.pause_tui().await.unwrap(); let selected = self.current_selection; let task_id = self.tasks[selected].id().unwrap_or_default(); let task_uuid = *self.tasks[selected].uuid(); let r = std::process::Command::new(&self.task_exe) .arg(format!("{}", task_uuid)) .arg("edit") .spawn(); let r = match r { Ok(child) => { let output = child.wait_with_output(); match output { Ok(output) => { if output.status.success() { Ok(()) } else { Err(format!( "`task edit` for task `{}` failed. {}{}", task_uuid, String::from_utf8_lossy(&output.stdout), String::from_utf8_lossy(&output.stderr), )) } } Err(err) => Err(format!("Cannot run `task edit` for task `{}`. {}", task_uuid, err)), } } _ => Err(format!( "Cannot start `task edit` for task `{}`. Check documentation for more information", task_uuid )), }; self.current_selection_uuid = Some(task_uuid); self.resume_tui().await.unwrap(); r } pub fn task_current(&self) -> Option { if self.tasks.is_empty() { return None; } let selected = self.current_selection; Some(self.tasks[selected].clone()) } pub fn update_tags(&mut self) { let tasks = &mut self.tasks; // dependency scan for l_i in 0..tasks.len() { let default_deps = vec![]; let deps = tasks[l_i].depends().unwrap_or(&default_deps).clone(); add_tag(&mut tasks[l_i], "UNBLOCKED".to_string()); for dep in deps { for r_i in 0..tasks.len() { if tasks[r_i].uuid() == &dep { let l_status = tasks[l_i].status(); let r_status = tasks[r_i].status(); if l_status != &TaskStatus::Completed && l_status != &TaskStatus::Deleted && r_status != &TaskStatus::Completed && r_status != &TaskStatus::Deleted { remove_tag(&mut tasks[l_i], "UNBLOCKED"); add_tag(&mut tasks[l_i], "BLOCKED".to_string()); add_tag(&mut tasks[r_i], "BLOCKING".to_string()); } break; } } } } // other virtual tags // TODO: support all virtual tags that taskwarrior supports for task in tasks.iter_mut() { match task.status() { TaskStatus::Waiting => add_tag(task, "WAITING".to_string()), TaskStatus::Completed => add_tag(task, "COMPLETED".to_string()), TaskStatus::Pending => add_tag(task, "PENDING".to_string()), TaskStatus::Deleted => add_tag(task, "DELETED".to_string()), TaskStatus::Recurring => (), } if task.start().is_some() { add_tag(task, "ACTIVE".to_string()); } if task.scheduled().is_some() { add_tag(task, "SCHEDULED".to_string()); } if task.parent().is_some() { add_tag(task, "INSTANCE".to_string()); } if task.until().is_some() { add_tag(task, "UNTIL".to_string()); } if task.annotations().is_some() { add_tag(task, "ANNOTATED".to_string()); } let virtual_tags = self.task_report_table.virtual_tags.clone(); if task.tags().is_some() && task.tags().unwrap().iter().any(|s| !virtual_tags.contains(s)) { add_tag(task, "TAGGED".to_string()); } if !task.uda().is_empty() { add_tag(task, "UDA".to_string()); } if task.mask().is_some() { add_tag(task, "TEMPLATE".to_string()); } if task.project().is_some() { add_tag(task, "PROJECT".to_string()); } if task.priority().is_some() { add_tag(task, "PRIORITY".to_string()); } if task.recur().is_some() { add_tag(task, "RECURRING".to_string()); let r = task.recur().unwrap(); } if let Some(d) = task.due() { let status = task.status(); // due today if status != &TaskStatus::Completed && status != &TaskStatus::Deleted { let now = Local::now(); let reference = datetime::local_from_utc(d); let d = d.clone(); if (reference - chrono::Duration::nanoseconds(1)).month() == now.month() { add_tag(task, "MONTH".to_string()); } if (reference - chrono::Duration::nanoseconds(1)).month() % 4 == now.month() % 4 { add_tag(task, "QUARTER".to_string()); } if reference.year() == now.year() { add_tag(task, "YEAR".to_string()); } match get_date_state(&d, self.config.due) { DateState::EarlierToday | DateState::LaterToday => { add_tag(task, "DUE".to_string()); add_tag(task, "TODAY".to_string()); add_tag(task, "DUETODAY".to_string()); } DateState::AfterToday => { add_tag(task, "DUE".to_string()); if reference.date_naive() == (now + chrono::Duration::days(1)).date_naive() { add_tag(task, "TOMORROW".to_string()); } } _ => (), } } } if let Some(d) = task.due() { let status = task.status(); // overdue if status != &TaskStatus::Completed && status != &TaskStatus::Deleted && status != &TaskStatus::Recurring { let now = Local::now().naive_utc(); let d = NaiveDateTime::new(d.date(), d.time()); if d < now { add_tag(task, "OVERDUE".to_string()); } } } } } pub fn toggle_mark(&mut self) { if !self.tasks.is_empty() { let selected = self.current_selection; let task_id = self.tasks[selected].id().unwrap_or_default(); let task_uuid = *self.tasks[selected].uuid(); if !self.marked.insert(task_uuid) { self.marked.remove(&task_uuid); } } } pub fn toggle_mark_all(&mut self) { for task in &self.tasks { if !self.marked.insert(*task.uuid()) { self.marked.remove(task.uuid()); } } } pub fn escape(s: &str) -> String { let mut es = String::with_capacity(s.len() + 2); es.push('"'); for ch in s.chars() { match ch { '"' => { es.push('\\'); es.push(ch); } _ => es.push(ch), } } es.push('"'); es } pub async fn handle_input(&mut self, input: KeyCode) -> Result<()> { match self.mode { Mode::Tasks(_) => { self.handle_input_by_task_mode(input).await?; } Mode::Projects => { ProjectsState::handle_input(self, input)?; self.update(false).await?; } Mode::Timesheet => { if input == self.keyconfig.quit || input == KeyCode::Ctrl('c') { self.should_quit = true; } else if input == self.keyconfig.next_tab { self.mode = Mode::Calendar; } else if input == self.keyconfig.previous_tab { self.mode = Mode::Projects; } else if input == KeyCode::Up || input == self.keyconfig.up { self.timesheet_scroll = self.timesheet_scroll.saturating_sub(1); } else if input == KeyCode::Down || input == self.keyconfig.down { self.timesheet_scroll = self.timesheet_scroll.saturating_add(1); } else if input == KeyCode::PageUp || input == self.keyconfig.page_up { self.timesheet_scroll = self.timesheet_scroll.saturating_sub(self.terminal_height); } else if input == KeyCode::PageDown || input == self.keyconfig.page_down { self.timesheet_scroll = self.timesheet_scroll.saturating_add(self.terminal_height); } // Clamp scroll: viewport is terminal height minus the tab bar row. let viewport = self.terminal_height.saturating_sub(1); let max_scroll = self.timesheet_line_count.saturating_sub(viewport); self.timesheet_scroll = self.timesheet_scroll.min(max_scroll); } Mode::Calendar => { if input == self.keyconfig.quit || input == KeyCode::Ctrl('c') { self.should_quit = true; } else if input == self.keyconfig.next_tab { if self.config.uda_change_focus_rotate { self.mode = Mode::Tasks(Action::Report); } } else if input == self.keyconfig.previous_tab { self.mode = Mode::Timesheet; } else if input == KeyCode::Up || input == self.keyconfig.up { if self.calendar_year > 0 { self.calendar_year -= 1; } } else if input == KeyCode::Down || input == self.keyconfig.down { self.calendar_year += 1; } else if input == KeyCode::PageUp || input == self.keyconfig.page_up { self.task_report_previous_page(); } else if input == KeyCode::PageDown || input == self.keyconfig.page_down { self.calendar_year += 10; } else if input == KeyCode::Ctrl('e') { self.task_details_scroll_down(); } else if input == KeyCode::Ctrl('y') { self.task_details_scroll_up(); } else if input == self.keyconfig.done { if self.config.uda_task_report_prompt_on_done { self.mode = Mode::Tasks(Action::DonePrompt); if self.task_current().is_none() { self.mode = Mode::Tasks(Action::Report); } } else { match self.task_done() { Ok(_) => self.update(true).await?, Err(e) => { self.error = Some(e); self.mode = Mode::Tasks(Action::Error); } } if self.calendar_year > 0 { self.calendar_year -= 10; } } } } } self.update_task_table_state(); Ok(()) } async fn handle_input_by_task_mode(&mut self, input: KeyCode) -> Result<()> { if let Mode::Tasks(task_mode) = &self.mode { match task_mode { Action::Report => { if input == KeyCode::Esc { self.marked.clear(); } else if input == self.keyconfig.quit || input == KeyCode::Ctrl('c') { self.should_quit = true; } else if input == self.keyconfig.select { self.task_table_state.multiple_selection(); self.toggle_mark(); } else if input == self.keyconfig.select_all { self.task_table_state.multiple_selection(); self.toggle_mark_all(); } else if input == self.keyconfig.refresh { self.update(true).await?; } else if input == self.keyconfig.go_to_bottom || input == KeyCode::End { self.task_report_bottom(); } else if input == self.keyconfig.go_to_top || input == KeyCode::Home { self.task_report_top(); } else if input == KeyCode::Down || input == self.keyconfig.down { self.task_report_next(); } else if input == KeyCode::Up || input == self.keyconfig.up { self.task_report_previous(); } else if input == KeyCode::PageDown || input == self.keyconfig.page_down { self.task_report_next_page(); } else if input == KeyCode::PageUp || input == self.keyconfig.page_up { self.task_report_previous_page(); } else if input == KeyCode::Ctrl('e') { self.task_details_scroll_down(); } else if input == KeyCode::Ctrl('y') { self.task_details_scroll_up(); } else if input == self.keyconfig.done { if self.config.uda_task_report_prompt_on_done { self.mode = Mode::Tasks(Action::DonePrompt); if self.task_current().is_none() { self.mode = Mode::Tasks(Action::Report); } } else { match self.task_done() { Ok(_) => self.update(true).await?, Err(e) => { self.error = Some(e); self.mode = Mode::Tasks(Action::Error); } } } } else if input == self.keyconfig.delete { if self.config.uda_task_report_prompt_on_delete { self.mode = Mode::Tasks(Action::DeletePrompt); if self.task_current().is_none() { self.mode = Mode::Tasks(Action::Report); } } else { match self.task_delete() { Ok(_) => self.update(true).await?, Err(e) => { self.error = Some(e); self.mode = Mode::Tasks(Action::Error); } } } } else if input == self.keyconfig.start_stop { match self.task_start_stop() { Ok(_) => self.update(true).await?, Err(e) => { self.error = Some(e); self.mode = Mode::Tasks(Action::Error); } } } else if input == self.keyconfig.quick_tag { match self.task_quick_tag() { Ok(_) => self.update(true).await?, Err(e) => { self.error = Some(e); self.mode = Mode::Tasks(Action::Error); } } } else if input == self.keyconfig.edit { match self.task_edit().await { Ok(_) => self.update(true).await?, Err(e) => { self.error = Some(e); self.mode = Mode::Tasks(Action::Error); } } } else if input == self.keyconfig.duplicate { match self.task_duplicate() { Ok(_) => self.update(true).await?, Err(e) => { self.error = Some(e); self.mode = Mode::Tasks(Action::Error); } } } else if input == self.keyconfig.undo { if self.config.uda_task_report_prompt_on_undo { self.mode = Mode::Tasks(Action::UndoPrompt); if self.task_current().is_none() { self.mode = Mode::Tasks(Action::Report); } } else { match self.task_undo() { Ok(_) => self.update(true).await?, Err(e) => { self.error = Some(e); self.mode = Mode::Tasks(Action::Error); } } } } else if input == self.keyconfig.modify { self.mode = Mode::Tasks(Action::Modify); self.command_history.reset(); self.history_status = Some(format!( "{} / {}", self .command_history .history_index() .unwrap_or_else(|| self.command_history.history_len().saturating_sub(1)) .saturating_add(1), self.command_history.history_len() )); self.update_completion_list(); match self.task_table_state.mode() { TableMode::SingleSelection => match self.task_current() { Some(t) => { let mut s = format!("{} ", Self::escape(t.description())); if self.config.uda_prefill_task_metadata { if t.tags().is_some() { let virtual_tags = self.task_report_table.virtual_tags.clone(); for tag in t.tags().unwrap() { if !virtual_tags.contains(tag) { s = format!("{}+{} ", s, tag); } } } if t.project().is_some() { s = format!("{}project:{} ", s, t.project().unwrap()); } if t.priority().is_some() { s = format!("{}priority:{} ", s, t.priority().unwrap()); } if t.due().is_some() { let date = t.due().unwrap(); s = format!("{}due:{} ", s, get_formatted_datetime(date)); } } self.modify.update(&s, s.as_str().len(), &mut self.changes); } None => self.modify.update("", 0, &mut self.changes), }, TableMode::MultipleSelection => self.modify.update("", 0, &mut self.changes), } } else if input == self.keyconfig.shell { self.mode = Mode::Tasks(Action::Subprocess); } else if input == self.keyconfig.log { self.mode = Mode::Tasks(Action::Log); self.command_history.reset(); self.history_status = Some(format!( "{} / {}", self .command_history .history_index() .unwrap_or_else(|| self.command_history.history_len().saturating_sub(1)) .saturating_add(1), self.command_history.history_len() )); self.update_completion_list(); } else if input == self.keyconfig.add { self.mode = Mode::Tasks(Action::Add); self.command_history.reset(); self.history_status = Some(format!( "{} / {}", self .command_history .history_index() .unwrap_or_else(|| self.command_history.history_len().saturating_sub(1)) .saturating_add(1), self.command_history.history_len() )); self.update_completion_list(); } else if input == self.keyconfig.annotate { self.mode = Mode::Tasks(Action::Annotate); self.command_history.reset(); self.history_status = Some(format!( "{} / {}", self .command_history .history_index() .unwrap_or_else(|| self.command_history.history_len().saturating_sub(1)) .saturating_add(1), self.command_history.history_len() )); self.update_completion_list(); } else if input == self.keyconfig.help { self.mode = Mode::Tasks(Action::HelpPopup); } else if input == self.keyconfig.filter { self.mode = Mode::Tasks(Action::Filter); self.filter_history.reset(); self.history_status = Some(format!( "{} / {}", self .filter_history .history_index() .unwrap_or_else(|| self.filter_history.history_len().saturating_sub(1)) .saturating_add(1), self.filter_history.history_len() )); self.update_completion_list(); } else if input == KeyCode::Char(':') { self.mode = Mode::Tasks(Action::Jump); } else if input == self.keyconfig.shortcut1 { match self.task_shortcut(1).await { Ok(_) => self.update(true).await?, Err(e) => { self.update(true).await?; self.error = Some(e); self.mode = Mode::Tasks(Action::Error); } } } else if input == self.keyconfig.priority_h { match self.task_priority("H") { Ok(_) => self.update(true).await?, Err(e) => { self.error = Some(e); self.mode = Mode::Tasks(Action::Error); } } } else if input == self.keyconfig.priority_m { match self.task_priority("M") { Ok(_) => self.update(true).await?, Err(e) => { self.error = Some(e); self.mode = Mode::Tasks(Action::Error); } } } else if input == self.keyconfig.priority_l { match self.task_priority("L") { Ok(_) => self.update(true).await?, Err(e) => { self.error = Some(e); self.mode = Mode::Tasks(Action::Error); } } } else if input == self.keyconfig.priority_n { match self.task_priority("") { Ok(_) => self.update(true).await?, Err(e) => { self.error = Some(e); self.mode = Mode::Tasks(Action::Error); } } } else if input == self.keyconfig.shortcut2 { match self.task_shortcut(2).await { Ok(_) => self.update(true).await?, Err(e) => { self.update(true).await?; self.error = Some(e); self.mode = Mode::Tasks(Action::Error); } } } else if input == self.keyconfig.shortcut3 { match self.task_shortcut(3).await { Ok(_) => self.update(true).await?, Err(e) => { self.update(true).await?; self.error = Some(e); self.mode = Mode::Tasks(Action::Error); } } } else if input == self.keyconfig.shortcut4 { match self.task_shortcut(4).await { Ok(_) => self.update(true).await?, Err(e) => { self.update(true).await?; self.error = Some(e); self.mode = Mode::Tasks(Action::Error); } } } else if input == self.keyconfig.shortcut5 { match self.task_shortcut(5).await { Ok(_) => self.update(true).await?, Err(e) => { self.update(true).await?; self.error = Some(e); self.mode = Mode::Tasks(Action::Error); } } } else if input == self.keyconfig.shortcut6 { match self.task_shortcut(6).await { Ok(_) => self.update(true).await?, Err(e) => { self.update(true).await?; self.error = Some(e); self.mode = Mode::Tasks(Action::Error); } } } else if input == self.keyconfig.shortcut7 { match self.task_shortcut(7).await { Ok(_) => self.update(true).await?, Err(e) => { self.update(true).await?; self.error = Some(e); self.mode = Mode::Tasks(Action::Error); } } } else if input == self.keyconfig.shortcut8 { match self.task_shortcut(8).await { Ok(_) => self.update(true).await?, Err(e) => { self.update(true).await?; self.error = Some(e); self.mode = Mode::Tasks(Action::Error); } } } else if input == self.keyconfig.shortcut9 { match self.task_shortcut(9).await { Ok(_) => self.update(true).await?, Err(e) => { self.update(true).await?; self.error = Some(e); self.mode = Mode::Tasks(Action::Error); } } } else if input == self.keyconfig.zoom { self.task_report_info_show = !self.task_report_info_show; } else if input == self.keyconfig.transpose { self.toggle_task_info_location(); } else if input == self.keyconfig.context_menu { self.contexts.search.clear(); // Pre-select the active context. let active_pos = self .contexts .filtered_indices() .iter() .position(|&ri| self.contexts.rows[ri].active == "yes") .unwrap_or(0); self.contexts.table_state.select(Some(active_pos)); self.mode = Mode::Tasks(Action::ContextMenu); } else if input == self.keyconfig.report_menu { self.reports.search.clear(); // Pre-select the active report. let active_pos = self .reports .filtered_indices() .iter() .position(|&ri| self.reports.rows[ri].active == "yes") .unwrap_or(0); self.reports.table_state.select(Some(active_pos)); self.mode = Mode::Tasks(Action::ReportMenu); } else if input == self.keyconfig.previous_tab { if self.config.uda_change_focus_rotate { self.mode = Mode::Calendar; } } else if input == self.keyconfig.next_tab { self.mode = Mode::Projects; } } Action::ContextMenu => { match input { // Esc: clear search first; if already empty, close the menu. KeyCode::Esc => { if !self.contexts.search.is_empty() { let previous_row_index = self.context_selected_row_index(); self.contexts.search.clear(); self.restore_context_selection(previous_row_index); self.maybe_autoselect_context_menu().await?; } else { self.mode = Mode::Tasks(Action::Report); } } // Backspace removes the last search character. KeyCode::Backspace | KeyCode::Ctrl('h') => { let previous_row_index = self.context_selected_row_index(); self.contexts.search.pop(); self.restore_context_selection(previous_row_index); self.maybe_autoselect_context_menu().await?; } KeyCode::Char('\n') => { if self.error.is_some() { self.previous_mode = Some(self.mode.clone()); self.mode = Mode::Tasks(Action::Error); } else { match self.context_select() { Ok(true) => { self.update(true).await?; if self.config.uda_context_menu_close_on_select { self.mode = Mode::Tasks(Action::Report); } } Ok(false) => {} Err(e) => { self.error = Some(e.to_string()); self.mode = Mode::Tasks(Action::Error); } } } } // Printable characters feed the search bar (except quit when search is empty). KeyCode::Char(c) => { if self.contexts.search.is_empty() && input == self.keyconfig.quit { self.mode = Mode::Tasks(Action::Report); } else { let previous_row_index = self.context_selected_row_index(); self.contexts.search.push(c); self.restore_context_selection(previous_row_index); self.maybe_autoselect_context_menu().await?; } } _ if input == KeyCode::Down || input == self.keyconfig.down => { self.context_next(); self.maybe_autoselect_context_menu().await?; } _ if input == KeyCode::Up || input == self.keyconfig.up => { self.context_previous(); self.maybe_autoselect_context_menu().await?; } _ => {} } } Action::ReportMenu => { match input { // Esc: clear search first; if already empty, close the menu. KeyCode::Esc => { if !self.reports.search.is_empty() { let previous_row_index = self.report_selected_row_index(); self.reports.search.clear(); self.restore_report_selection(previous_row_index); self.maybe_autoselect_report_menu().await?; } else { self.mode = Mode::Tasks(Action::Report); } } // Backspace removes the last search character. KeyCode::Backspace | KeyCode::Ctrl('h') => { let previous_row_index = self.report_selected_row_index(); self.reports.search.pop(); self.restore_report_selection(previous_row_index); self.maybe_autoselect_report_menu().await?; } KeyCode::Char('\n') => { if self.error.is_some() { self.previous_mode = Some(self.mode.clone()); self.mode = Mode::Tasks(Action::Error); } else { let data = Self::task_show_output(&self.task_exe)?; match self.report_select(&data) { Ok(true) => { self.update(true).await?; if self.config.uda_report_menu_close_on_select { self.mode = Mode::Tasks(Action::Report); } } Ok(false) => {} Err(e) => { self.error = Some(e.to_string()); self.mode = Mode::Tasks(Action::Error); } } } } // Printable characters feed the search bar (except quit when search is empty). KeyCode::Char(c) => { if self.reports.search.is_empty() && input == self.keyconfig.quit { self.mode = Mode::Tasks(Action::Report); } else { let previous_row_index = self.report_selected_row_index(); self.reports.search.push(c); self.restore_report_selection(previous_row_index); self.maybe_autoselect_report_menu().await?; } } _ if input == KeyCode::Down || input == self.keyconfig.down => { self.report_next(); self.maybe_autoselect_report_menu().await?; } _ if input == KeyCode::Up || input == self.keyconfig.up => { self.report_previous(); self.maybe_autoselect_report_menu().await?; } _ => {} } } Action::HelpPopup => { if input == self.keyconfig.quit || input == KeyCode::Esc { self.mode = Mode::Tasks(Action::Report); } else if input == self.keyconfig.down { self.help_popup.scroll = self.help_popup.scroll.checked_add(1).unwrap_or(0); let th = (self.help_popup.text_height as u16).saturating_sub(1); if self.help_popup.scroll > th { self.help_popup.scroll = th; } } else if input == self.keyconfig.up { self.help_popup.scroll = self.help_popup.scroll.saturating_sub(1); } } Action::Modify => match input { KeyCode::Esc => { if self.show_completion_pane { self.show_completion_pane = false; self.completion_list.unselect(); } else { self.modify.update("", 0, &mut self.changes); self.mode = Mode::Tasks(Action::Report); } } KeyCode::Char('\n') => { if self.show_completion_pane { self.show_completion_pane = false; if let Some((_, (r, _, o, _, _))) = self.completion_list.selected() { Self::apply_completion_to_buffer(&mut self.modify, &r, &o, &mut self.changes); } self.completion_list.unselect(); } else if self.error.is_some() { self.previous_mode = Some(self.mode.clone()); self.mode = Mode::Tasks(Action::Error); } else { match self.task_modify() { Ok(_) => { self.mode = Mode::Tasks(Action::Report); self.command_history.add(self.modify.as_str()); self.modify.update("", 0, &mut self.changes); self.update(true).await?; } Err(e) => { self.error = Some(e); self.mode = Mode::Tasks(Action::Error); } } } } KeyCode::Tab | KeyCode::Ctrl('n') => { if !self.completion_list.is_empty() { self.update_input_for_completion(); let candidates = self.completion_list.candidates(); if candidates.len() == 1 { let (r, _, o, _, _) = candidates.into_iter().next().unwrap(); Self::apply_completion_to_buffer(&mut self.modify, &r, &o, &mut self.changes); self.show_completion_pane = false; self.completion_list.unselect(); self.update_input_for_completion(); } else { if !self.show_completion_pane { self.show_completion_pane = true; } self.completion_list.next(); } } } KeyCode::BackTab | KeyCode::Ctrl('p') => { if self.show_completion_pane && !self.completion_list.is_empty() { self.completion_list.previous(); } } KeyCode::Up => { if self.show_completion_pane && !self.completion_list.is_empty() { self.completion_list.previous(); } else if let Some(s) = self .command_history .history_search(&self.modify.as_str()[..self.modify.pos()], HistoryDirection::Reverse) { let p = self.modify.pos(); self.modify.update("", 0, &mut self.changes); self.modify.update(&s, std::cmp::min(s.len(), p), &mut self.changes); self.history_status = Some(format!( "{} / {}", self .command_history .history_index() .unwrap_or_else(|| self.command_history.history_len().saturating_sub(1)) .saturating_add(1), self.command_history.history_len() )); } } KeyCode::Down => { if self.show_completion_pane && !self.completion_list.is_empty() { self.completion_list.next(); } else if let Some(s) = self .command_history .history_search(&self.modify.as_str()[..self.modify.pos()], HistoryDirection::Forward) { let p = self.modify.pos(); self.modify.update("", 0, &mut self.changes); self.modify.update(&s, std::cmp::min(s.len(), p), &mut self.changes); self.history_status = Some(format!( "{} / {}", self .command_history .history_index() .unwrap_or_else(|| self.command_history.history_len().saturating_sub(1)) .saturating_add(1), self.command_history.history_len() )); } } _ => { self.command_history.reset(); handle_movement(&mut self.modify, input, &mut self.changes); self.update_input_for_completion(); } }, Action::Subprocess => match input { KeyCode::Char('\n') => { if self.error.is_some() { self.previous_mode = Some(self.mode.clone()); self.mode = Mode::Tasks(Action::Error); } else { match self.task_subprocess() { Ok(_) => { self.mode = Mode::Tasks(Action::Report); self.reset_command(); self.update(true).await?; } Err(e) => { self.error = Some(e); self.mode = Mode::Tasks(Action::Error); } } } } KeyCode::Esc => { self.reset_command(); self.mode = Mode::Tasks(Action::Report); } _ => handle_movement(&mut self.command, input, &mut self.changes), }, Action::Log => match input { KeyCode::Esc => { if self.show_completion_pane { self.show_completion_pane = false; self.completion_list.unselect(); } else { self.reset_command(); self.history_status = None; self.mode = Mode::Tasks(Action::Report); } } KeyCode::Char('\n') => { if self.show_completion_pane { self.show_completion_pane = false; if let Some((_, (r, _, o, _, _))) = self.completion_list.selected() { Self::apply_completion_to_buffer(&mut self.command, &r, &o, &mut self.changes); } self.completion_list.unselect(); } else if self.error.is_some() { self.previous_mode = Some(self.mode.clone()); self.mode = Mode::Tasks(Action::Error); } else { match self.task_log() { Ok(_) => { self.mode = Mode::Tasks(Action::Report); self.command_history.add(self.command.as_str()); self.reset_command(); self.history_status = None; self.update(true).await?; } Err(e) => { self.error = Some(e); self.mode = Mode::Tasks(Action::Error); } } } } KeyCode::Tab | KeyCode::Ctrl('n') => { if !self.completion_list.is_empty() { self.update_input_for_completion(); let candidates = self.completion_list.candidates(); if candidates.len() == 1 { let (r, _, o, _, _) = candidates.into_iter().next().unwrap(); Self::apply_completion_to_buffer(&mut self.command, &r, &o, &mut self.changes); self.show_completion_pane = false; self.completion_list.unselect(); self.update_input_for_completion(); } else { if !self.show_completion_pane { self.show_completion_pane = true; } self.completion_list.next(); } } } KeyCode::BackTab | KeyCode::Ctrl('p') => { if self.show_completion_pane && !self.completion_list.is_empty() { self.completion_list.previous(); } } KeyCode::Up => { if self.show_completion_pane && !self.completion_list.is_empty() { self.completion_list.previous(); } else if let Some(s) = self .command_history .history_search(&self.command.as_str()[..self.command.pos()], HistoryDirection::Reverse) { let p = self.command.pos(); self.command.update("", 0, &mut self.changes); self.command.update(&s, std::cmp::min(s.len(), p), &mut self.changes); self.history_status = Some(format!( "{} / {}", self .command_history .history_index() .unwrap_or_else(|| self.command_history.history_len().saturating_sub(1)) .saturating_add(1), self.command_history.history_len() )); } } KeyCode::Down => { if self.show_completion_pane && !self.completion_list.is_empty() { self.completion_list.next(); } else if let Some(s) = self .command_history .history_search(&self.command.as_str()[..self.command.pos()], HistoryDirection::Forward) { let p = self.command.pos(); self.command.update("", 0, &mut self.changes); self.command.update(&s, std::cmp::min(s.len(), p), &mut self.changes); self.history_status = Some(format!( "{} / {}", self .command_history .history_index() .unwrap_or_else(|| self.command_history.history_len().saturating_sub(1)) .saturating_add(1), self.command_history.history_len() )); } } _ => { self.command_history.reset(); handle_movement(&mut self.command, input, &mut self.changes); self.update_input_for_completion(); } }, Action::Annotate => match input { KeyCode::Esc => { if self.show_completion_pane { self.show_completion_pane = false; self.completion_list.unselect(); } else { self.reset_command(); self.mode = Mode::Tasks(Action::Report); self.history_status = None; } } KeyCode::Char('\n') => { if self.show_completion_pane { self.show_completion_pane = false; if let Some((_, (r, _, o, _, _))) = self.completion_list.selected() { Self::apply_completion_to_buffer(&mut self.command, &r, &o, &mut self.changes); } self.completion_list.unselect(); } else if self.error.is_some() { self.previous_mode = Some(self.mode.clone()); self.mode = Mode::Tasks(Action::Error); } else { match self.task_annotate() { Ok(_) => { self.mode = Mode::Tasks(Action::Report); self.command_history.add(self.command.as_str()); self.reset_command(); self.history_status = None; self.update(true).await?; } Err(e) => { self.error = Some(e); self.mode = Mode::Tasks(Action::Error); } } } } KeyCode::Tab | KeyCode::Ctrl('n') => { if !self.completion_list.is_empty() { self.update_input_for_completion(); let candidates = self.completion_list.candidates(); if candidates.len() == 1 { let (r, _, o, _, _) = candidates.into_iter().next().unwrap(); Self::apply_completion_to_buffer(&mut self.command, &r, &o, &mut self.changes); self.show_completion_pane = false; self.completion_list.unselect(); self.update_input_for_completion(); } else { if !self.show_completion_pane { self.show_completion_pane = true; } self.completion_list.next(); } } } KeyCode::BackTab | KeyCode::Ctrl('p') => { if self.show_completion_pane && !self.completion_list.is_empty() { self.completion_list.previous(); } } KeyCode::Up => { if self.show_completion_pane && !self.completion_list.is_empty() { self.completion_list.previous(); } else if let Some(s) = self .command_history .history_search(&self.command.as_str()[..self.command.pos()], HistoryDirection::Reverse) { let p = self.command.pos(); self.command.update("", 0, &mut self.changes); self.command.update(&s, std::cmp::min(s.len(), p), &mut self.changes); self.history_status = Some(format!( "{} / {}", self .command_history .history_index() .unwrap_or_else(|| self.command_history.history_len().saturating_sub(1)) .saturating_add(1), self.command_history.history_len() )); } } KeyCode::Down => { if self.show_completion_pane && !self.completion_list.is_empty() { self.completion_list.next(); } else if let Some(s) = self .command_history .history_search(&self.command.as_str()[..self.command.pos()], HistoryDirection::Forward) { let p = self.command.pos(); self.command.update("", 0, &mut self.changes); self.command.update(&s, std::cmp::min(s.len(), p), &mut self.changes); self.history_status = Some(format!( "{} / {}", self .command_history .history_index() .unwrap_or_else(|| self.command_history.history_len().saturating_sub(1)) .saturating_add(1), self.command_history.history_len() )); } } _ => { self.command_history.reset(); handle_movement(&mut self.command, input, &mut self.changes); self.update_input_for_completion(); } }, Action::Jump => match input { KeyCode::Char('\n') => { if self.error.is_some() { self.previous_mode = Some(self.mode.clone()); self.mode = Mode::Tasks(Action::Error); } else { match self.task_report_jump() { Ok(_) => { self.mode = Mode::Tasks(Action::Report); self.reset_command(); self.update(true).await?; } Err(e) => { self.reset_command(); self.error = Some(e.to_string()); self.mode = Mode::Tasks(Action::Error); } } } } KeyCode::Esc => { self.reset_command(); self.mode = Mode::Tasks(Action::Report); } _ => handle_movement(&mut self.command, input, &mut self.changes), }, Action::Add => match input { KeyCode::Esc => { if self.show_completion_pane { self.show_completion_pane = false; self.completion_list.unselect(); } else { self.reset_command(); self.history_status = None; self.mode = Mode::Tasks(Action::Report); } } KeyCode::Char('\n') => { if self.show_completion_pane { self.show_completion_pane = false; if let Some((_, (r, _, o, _, _))) = self.completion_list.selected() { Self::apply_completion_to_buffer(&mut self.command, &r, &o, &mut self.changes); } self.completion_list.unselect(); } else if self.error.is_some() { self.previous_mode = Some(self.mode.clone()); self.mode = Mode::Tasks(Action::Error); } else { match self.task_add() { Ok(_) => { self.mode = Mode::Tasks(Action::Report); self.command_history.add(self.command.as_str()); self.reset_command(); self.history_status = None; self.update(true).await?; } Err(e) => { self.error = Some(e); self.mode = Mode::Tasks(Action::Error); } } } } KeyCode::Tab | KeyCode::Ctrl('n') => { if !self.completion_list.is_empty() { self.update_input_for_completion(); let candidates = self.completion_list.candidates(); if candidates.len() == 1 { let (r, _, o, _, _) = candidates.into_iter().next().unwrap(); Self::apply_completion_to_buffer(&mut self.command, &r, &o, &mut self.changes); self.show_completion_pane = false; self.completion_list.unselect(); self.update_input_for_completion(); } else { if !self.show_completion_pane { self.show_completion_pane = true; } self.completion_list.next(); } } } KeyCode::BackTab | KeyCode::Ctrl('p') => { if self.show_completion_pane && !self.completion_list.is_empty() { self.completion_list.previous(); } } KeyCode::Up => { if self.show_completion_pane && !self.completion_list.is_empty() { self.completion_list.previous(); } else if let Some(s) = self .command_history .history_search(&self.command.as_str()[..self.command.pos()], HistoryDirection::Reverse) { let p = self.command.pos(); self.command.update("", 0, &mut self.changes); self.command.update(&s, std::cmp::min(s.len(), p), &mut self.changes); self.history_status = Some(format!( "{} / {}", self .command_history .history_index() .unwrap_or_else(|| self.command_history.history_len().saturating_sub(1)) .saturating_add(1), self.command_history.history_len() )); } } KeyCode::Down => { if self.show_completion_pane && !self.completion_list.is_empty() { self.completion_list.next(); } else if let Some(s) = self .command_history .history_search(&self.command.as_str()[..self.command.pos()], HistoryDirection::Forward) { let p = self.command.pos(); self.command.update("", 0, &mut self.changes); self.command.update(&s, std::cmp::min(s.len(), p), &mut self.changes); self.history_status = Some(format!( "{} / {}", self .command_history .history_index() .unwrap_or_else(|| self.command_history.history_len().saturating_sub(1)) .saturating_add(1), self.command_history.history_len() )); } } _ => { self.command_history.reset(); handle_movement(&mut self.command, input, &mut self.changes); self.update_input_for_completion(); } }, Action::Filter => match input { KeyCode::Esc => { if self.show_completion_pane { self.show_completion_pane = false; self.completion_list.unselect(); } else { self.mode = Mode::Tasks(Action::Report); self.filter_history.add(self.filter.as_str()); if self.config.uda_reset_filter_on_esc { self.filter.update("", 0, &mut self.changes); for c in self.config.filter.chars() { self.filter.insert(c, 1, &mut self.changes); } self.update_input_for_completion(); self.dirty = true; } self.history_status = None; self.update(true).await?; } } KeyCode::Char('\n') => { if self.show_completion_pane { self.show_completion_pane = false; if let Some((_, (r, _, o, _, _))) = self.completion_list.selected() { Self::apply_completion_to_buffer(&mut self.filter, &r, &o, &mut self.changes); } self.completion_list.unselect(); self.dirty = true; } else if self.error.is_some() { self.previous_mode = Some(self.mode.clone()); self.mode = Mode::Tasks(Action::Error); } else { self.mode = Mode::Tasks(Action::Report); self.filter_history.add(self.filter.as_str()); self.history_status = None; self.update(true).await?; } } KeyCode::Up => { if self.show_completion_pane && !self.completion_list.is_empty() { self.completion_list.previous(); } else if let Some(s) = self .filter_history .history_search(&self.filter.as_str()[..self.filter.pos()], HistoryDirection::Reverse) { let p = self.filter.pos(); self.filter.update("", 0, &mut self.changes); self.filter.update(&s, std::cmp::min(p, s.len()), &mut self.changes); self.history_status = Some(format!( "{} / {}", self .filter_history .history_index() .unwrap_or_else(|| self.filter_history.history_len().saturating_sub(1)) .saturating_add(1), self.filter_history.history_len() )); self.dirty = true; } } KeyCode::Down => { if self.show_completion_pane && !self.completion_list.is_empty() { self.completion_list.next(); } else if let Some(s) = self .filter_history .history_search(&self.filter.as_str()[..self.filter.pos()], HistoryDirection::Forward) { let p = self.filter.pos(); self.filter.update("", 0, &mut self.changes); self.filter.update(&s, std::cmp::min(p, s.len()), &mut self.changes); self.history_status = Some(format!( "{} / {}", self .filter_history .history_index() .unwrap_or_else(|| self.filter_history.history_len().saturating_sub(1)) .saturating_add(1), self.filter_history.history_len() )); self.dirty = true; } } KeyCode::Tab | KeyCode::Ctrl('n') => { if !self.completion_list.is_empty() { self.update_input_for_completion(); let candidates = self.completion_list.candidates(); if candidates.len() == 1 { let (r, _, o, _, _) = candidates.into_iter().next().unwrap(); Self::apply_completion_to_buffer(&mut self.filter, &r, &o, &mut self.changes); self.show_completion_pane = false; self.completion_list.unselect(); self.update_input_for_completion(); self.dirty = true; } else { if !self.show_completion_pane { self.show_completion_pane = true; } self.completion_list.next(); } } } KeyCode::BackTab | KeyCode::Ctrl('p') => { if self.show_completion_pane && !self.completion_list.is_empty() { self.completion_list.previous(); } } KeyCode::Ctrl('r') => { self.filter.update("", 0, &mut self.changes); for c in self.config.filter.chars() { self.filter.insert(c, 1, &mut self.changes); } self.history_status = None; self.update_input_for_completion(); self.dirty = true; } _ => { handle_movement(&mut self.filter, input, &mut self.changes); self.update_input_for_completion(); self.dirty = true; } }, Action::DonePrompt => { if input == self.keyconfig.done || input == KeyCode::Char('\n') { if self.error.is_some() { self.previous_mode = Some(self.mode.clone()); self.mode = Mode::Tasks(Action::Error); } else { match self.task_done() { Ok(_) => { self.mode = Mode::Tasks(Action::Report); self.update(true).await?; } Err(e) => { self.error = Some(e); self.mode = Mode::Tasks(Action::Error); } } } } else if input == self.keyconfig.quit || input == KeyCode::Esc { self.mode = Mode::Tasks(Action::Report); } else { handle_movement(&mut self.command, input, &mut self.changes); } } Action::DeletePrompt => { if input == self.keyconfig.delete || input == KeyCode::Char('\n') { if self.error.is_some() { self.previous_mode = Some(self.mode.clone()); self.mode = Mode::Tasks(Action::Error); } else { match self.task_delete() { Ok(_) => { self.mode = Mode::Tasks(Action::Report); self.update(true).await?; } Err(e) => { self.error = Some(e); self.mode = Mode::Tasks(Action::Error); } } } } else if input == self.keyconfig.quit || input == KeyCode::Esc { self.mode = Mode::Tasks(Action::Report); } else { handle_movement(&mut self.command, input, &mut self.changes); } } Action::UndoPrompt => { if input == self.keyconfig.undo || input == KeyCode::Char('\n') { if self.error.is_some() { self.previous_mode = Some(self.mode.clone()); self.mode = Mode::Tasks(Action::Error); } else { match self.task_undo() { Ok(_) => { self.mode = Mode::Tasks(Action::Report); self.update(true).await?; } Err(e) => { self.error = Some(e); self.mode = Mode::Tasks(Action::Error); } } } } else if input == self.keyconfig.quit || input == KeyCode::Esc { self.mode = Mode::Tasks(Action::Report); } else { handle_movement(&mut self.command, input, &mut self.changes); } } Action::Error => { // since filter live updates, don't reset error status // for other actions, resetting error to None is required otherwise user cannot // ever successfully execute mode. if self.previous_mode != Some(Mode::Tasks(Action::Filter)) { self.error = None; } self.mode = self.previous_mode.clone().unwrap_or(Mode::Tasks(Action::Report)); self.previous_mode = None; } } } self.update_task_table_state(); Ok(()) } pub fn update_completion_list(&mut self) { self.completion_list.clear(); let tasks = if self.config.uda_task_report_use_all_tasks_for_completion { &self.all_tasks } else { &self.tasks }; if let Mode::Tasks(Action::Modify | Action::Filter | Action::Annotate | Action::Add | Action::Log) = self.mode { for s in [ "project:".to_string(), "priority:".to_string(), "due:".to_string(), "scheduled:".to_string(), "wait:".to_string(), "depends:".to_string(), "tag:".to_string(), "recur:".to_string(), ] { self.completion_list.insert(("attribute".to_string(), s)); } } if let Mode::Tasks(Action::Modify | Action::Filter | Action::Annotate | Action::Add | Action::Log) = self.mode { for s in [ ".before:", ".under:", ".below:", ".after:", ".over:", ".above:", ".by:", ".none:", ".any:", ".is:", ".equals:", ".isnt:", ".not:", ".has:", ".contains:", ".hasnt:", ".startswith:", ".left:", ".endswith:", ".right:", ".word:", ".noword:", ] { self.completion_list.insert(("modifier".to_string(), s.to_string())); } } if let Mode::Tasks(Action::Modify | Action::Filter | Action::Annotate | Action::Add | Action::Log) = self.mode { for priority in &self.config.uda_priority_values { let p = priority.to_string(); self.completion_list.insert(("priority".to_string(), p)); } let virtual_tags = self.task_report_table.virtual_tags.clone(); for task in tasks { if let Some(tags) = task.tags() { for tag in tags { if !virtual_tags.contains(tag) { self.completion_list.insert(("tag".to_string(), tag.to_string())); } } } } for task in tasks { if let Some(tags) = task.tags() { for tag in tags { if !virtual_tags.contains(tag) { self.completion_list.insert(("+".to_string(), format!("+{}", &tag))); } } } } for task in tasks { if let Some(project) = task.project() { let p = if project.contains(' ') { format!(r#""{}""#, &project) } else { project.to_string() }; self.completion_list.insert(("project".to_string(), p)); } } for task in tasks { if let Some(date) = task.due() { self.completion_list.insert(("due".to_string(), get_formatted_datetime(date))); } } for task in tasks { if let Some(date) = task.wait() { self.completion_list.insert(("wait".to_string(), get_formatted_datetime(date))); } } for task in tasks { if let Some(date) = task.scheduled() { self.completion_list.insert(("scheduled".to_string(), get_formatted_datetime(date))); } } for task in tasks { if let Some(date) = task.end() { self.completion_list.insert(("end".to_string(), get_formatted_datetime(date))); } } } if self.mode == Mode::Tasks(Action::Filter) { self.completion_list.insert(("status".to_string(), "pending".into())); self.completion_list.insert(("status".to_string(), "completed".into())); self.completion_list.insert(("status".to_string(), "deleted".into())); self.completion_list.insert(("status".to_string(), "recurring".into())); } } pub fn update_input_for_completion(&mut self) { match self.mode { Mode::Tasks(Action::Add | Action::Annotate | Action::Log) => { let i = get_start_word_under_cursor(self.command.as_str(), self.command.pos()); let input = self.command.as_str()[i..self.command.pos()].to_string(); self.completion_list.input(input, "".to_string()); } Mode::Tasks(Action::Modify) => { let i = get_start_word_under_cursor(self.modify.as_str(), self.modify.pos()); let input = self.modify.as_str()[i..self.modify.pos()].to_string(); self.completion_list.input(input, "".to_string()); } Mode::Tasks(Action::Filter) => { let i = get_start_word_under_cursor(self.filter.as_str(), self.filter.pos()); let input = self.filter.as_str()[i..self.filter.pos()].to_string(); self.completion_list.input(input, "".to_string()); } _ => {} } } /// Apply a completion candidate to the given line buffer. /// `replacement` is the full candidate string, `original` is the already-typed prefix. fn apply_completion_to_buffer(buffer: &mut LineBuffer, replacement: &str, original: &str, changes: &mut utils::Changeset) { let (before, after) = buffer.as_str().split_at(buffer.pos()); let fs = format!("{}{}{}", before.trim_end_matches(original), replacement, after); buffer.update(&fs, buffer.pos() + replacement.len() - original.len(), changes); } } pub fn handle_movement(linebuffer: &mut LineBuffer, input: KeyCode, changes: &mut utils::Changeset) { match input { KeyCode::Ctrl('f') | KeyCode::Right => { linebuffer.move_forward(1); } KeyCode::Ctrl('b') | KeyCode::Left => { linebuffer.move_backward(1); } KeyCode::Ctrl('h') | KeyCode::Backspace => { linebuffer.backspace(1, changes); } KeyCode::Ctrl('d') | KeyCode::Delete => { linebuffer.delete(1, changes); } KeyCode::Ctrl('a') | KeyCode::Home => { linebuffer.move_home(); } KeyCode::Ctrl('e') | KeyCode::End => { linebuffer.move_end(); } KeyCode::Ctrl('k') => { linebuffer.kill_line(changes); } KeyCode::Ctrl('u') => { linebuffer.discard_line(changes); } KeyCode::Ctrl('w') | KeyCode::AltBackspace | KeyCode::CtrlBackspace => { linebuffer.delete_prev_word(Word::Emacs, 1, changes); } KeyCode::Alt('d') | KeyCode::AltDelete | KeyCode::CtrlDelete => { linebuffer.delete_word(At::AfterEnd, Word::Emacs, 1, changes); } KeyCode::Alt('f') => { linebuffer.move_to_next_word(At::AfterEnd, Word::Emacs, 1); } KeyCode::Alt('b') => { linebuffer.move_to_prev_word(Word::Emacs, 1); } KeyCode::Alt('t') => { linebuffer.transpose_words(1, changes); } KeyCode::Char(c) => { linebuffer.insert(c, 1, changes); } _ => {} } } pub fn add_tag(task: &mut Task, tag: String) { match task.tags_mut() { Some(t) => t.push(tag), None => task.set_tags(Some(vec![tag])), } } pub fn remove_tag(task: &mut Task, tag: &str) { if let Some(t) = task.tags_mut() && let Some(index) = t.iter().position(|x| *x == tag) { t.remove(index); } } #[cfg(test)] // Disabled, as "'" should be a String for more readable shlex shell escaping. #[allow(clippy::single_char_pattern)] mod tests { use std::{ ffi::OsStr, fmt::Write, fs::File, io, path::{Path, PathBuf}, }; use ratatui::{backend::TestBackend, buffer::Buffer}; use super::*; fn get_taskdata_path() -> PathBuf { let taskdata_env_var = std::env::var("TASKDATA").expect("TASKDATA environment variable not set."); let taskdata_path = Path::new(&taskdata_env_var).to_owned(); taskdata_path } fn task_exe() -> String { std::env::var("TASKWARRIOR_TUI_TASKWARRIOR_CLI").unwrap_or_else(|_| "task".to_string()) } fn assert_task_has_tags(task: &Task, expected_tags: &[&str]) { let task_tags = task .tags() .unwrap_or_else(|| panic!("task {} should have tags", task.id().unwrap_or_default())); for expected_tag in expected_tags { assert!( task_tags.contains(&expected_tag.to_string()), "expected tag `{expected_tag}` in {:?}", task_tags ); } } /// Returns a string representation of the given buffer for debugging purpose. fn buffer_view(buffer: &Buffer) -> String { let mut view = String::with_capacity(buffer.content.len() + buffer.area.height as usize * 3); for cells in buffer.content.chunks(buffer.area.width as usize) { let mut overwritten = vec![]; let mut skip: usize = 0; view.push('"'); for (x, c) in cells.iter().enumerate() { if skip == 0 { view.push_str(c.symbol()); } else { overwritten.push((x, c.symbol())) } skip = std::cmp::max(skip, c.symbol().width()).saturating_sub(1); } view.push('"'); if !overwritten.is_empty() { write!(&mut view, " Hidden by multi-width symbols: {:?}", overwritten).unwrap(); } view.push('\n'); } view } #[test] fn test_centered_rect() { assert_eq!(centered_rect(50, 50, Rect::new(0, 0, 100, 100)), Rect::new(25, 25, 50, 50)); } #[tokio::test] async fn test_task_info_location_auto_and_override() { let mut app = TaskwarriorTui::new("next", false).await.unwrap(); app.config.uda_task_report_info_location = TaskInfoLocation::Auto; assert_eq!(app.task_info_location(TaskInfoLocation::AUTO_WIDTH_THRESHOLD), TaskInfoLocation::Bottom); assert_eq!( app.task_info_location(TaskInfoLocation::AUTO_WIDTH_THRESHOLD + 1), TaskInfoLocation::Right ); app.terminal_width = TaskInfoLocation::AUTO_WIDTH_THRESHOLD; app.toggle_task_info_location(); assert_eq!(app.task_info_location_override, Some(TaskInfoLocation::Right)); assert_eq!(app.task_info_location_override_width, Some(TaskInfoLocation::AUTO_WIDTH_THRESHOLD)); assert_eq!(app.task_info_location(TaskInfoLocation::AUTO_WIDTH_THRESHOLD), TaskInfoLocation::Right); assert_eq!( app.task_info_location(TaskInfoLocation::AUTO_WIDTH_THRESHOLD + 1), TaskInfoLocation::Right ); assert_eq!(app.task_info_location_override, None); assert_eq!(app.task_info_location_override_width, None); assert_eq!( app.task_info_location(TaskInfoLocation::AUTO_WIDTH_THRESHOLD + 1), TaskInfoLocation::Right ); } #[tokio::test] async fn test_task_info_location_fixed_toggle_returns_to_configured_value() { let mut app = TaskwarriorTui::new("next", false).await.unwrap(); app.config.uda_task_report_info_location = TaskInfoLocation::Bottom; app.terminal_width = TaskInfoLocation::AUTO_WIDTH_THRESHOLD + 1; app.toggle_task_info_location(); assert_eq!(app.task_info_location_override, Some(TaskInfoLocation::Right)); assert_eq!(app.task_info_location_override_width, None); assert_eq!( app.task_info_location(TaskInfoLocation::AUTO_WIDTH_THRESHOLD + 1), TaskInfoLocation::Right ); app.toggle_task_info_location(); assert_eq!(app.task_info_location_override, None); assert_eq!(app.task_info_location_override_width, None); assert_eq!( app.task_info_location(TaskInfoLocation::AUTO_WIDTH_THRESHOLD + 1), TaskInfoLocation::Bottom ); } fn setup() { use std::process::Stdio; let mut f = File::open(get_taskdata_path().parent().unwrap().join("export.json")).unwrap(); let mut s = String::new(); f.read_to_string(&mut s).unwrap(); let tasks = task_hookrs::import::import(s.as_bytes()).unwrap(); // tasks.iter_mut().find(| t | t.id().unwrap() == 1).unwrap().priority_mut().replace(&mut "H".to_string()); // tasks.iter_mut().find(| t | t.id().unwrap() == 2).unwrap().priority_mut().replace(&mut "H".to_string()); // tasks.iter_mut().find(| t | t.id().unwrap() == 4).unwrap().tags_mut().replace(&mut vec!["test".to_string(), "another tag".to_string()]); assert!(task_hookrs::tw::save(&tasks).is_ok()); } fn teardown() { let cd = get_taskdata_path(); if cd.exists() { std::fs::remove_dir_all(cd).unwrap(); } } fn reset_test_taskdata() { teardown(); std::process::Command::new(task_exe()).arg("context").arg("none").output().unwrap(); } fn reset_test_context() { std::process::Command::new(task_exe()).arg("context").arg("none").output().unwrap(); } fn configure_context_menu_selection(app: &mut TaskwarriorTui, select_on_move: bool, close_on_select: bool) { app.config.uda_context_menu_select_on_move = select_on_move; app.config.uda_context_menu_close_on_select = close_on_select; } fn enable_context_menu_move_selection(app: &mut TaskwarriorTui) { configure_context_menu_selection(app, true, true); } fn configure_report_menu_selection(app: &mut TaskwarriorTui, select_on_move: bool, close_on_select: bool) { app.config.uda_report_menu_select_on_move = select_on_move; app.config.uda_report_menu_close_on_select = close_on_select; } fn enable_report_menu_move_selection(app: &mut TaskwarriorTui) { configure_report_menu_selection(app, true, true); } fn selected_context_filter(app: &TaskwarriorTui) -> Option { let fi = app.contexts.table_state.current_selection()?; let ri = *app.contexts.filtered_indices().get(fi)?; Some(app.contexts.rows.get(ri)?.definition.clone()) } fn selected_report_name(app: &TaskwarriorTui) -> Option { let fi = app.reports.table_state.current_selection()?; let ri = *app.reports.filtered_indices().get(fi)?; Some(app.reports.rows.get(ri)?.name.clone()) } async fn test_taskwarrior_tui_history() { let mut app = TaskwarriorTui::new("next", false).await.unwrap(); // setup(); app.mode = Mode::Tasks(Action::Add); app.update_completion_list(); let input = "Wash car"; for c in input.chars() { app.handle_input(KeyCode::Char(c)).await.unwrap(); } app.handle_input(KeyCode::Right).await.unwrap(); let input = " +test"; for c in input.chars() { app.handle_input(KeyCode::Char(c)).await.unwrap(); } app.handle_input(KeyCode::Char('\n')).await.unwrap(); app.mode = Mode::Tasks(Action::Add); app.update_completion_list(); let backend = TestBackend::new(50, 15); let mut terminal = Terminal::new(backend).unwrap(); terminal .draw(|f| { app.draw(f); app.draw(f); }) .unwrap(); let input = "Buy groceries"; for c in input.chars() { app.handle_input(KeyCode::Char(c)).await.unwrap(); } app.handle_input(KeyCode::Right).await.unwrap(); let input = " +test"; for c in input.chars() { app.handle_input(KeyCode::Char(c)).await.unwrap(); } app.update(true).await.unwrap(); app.handle_input(KeyCode::Down).await.unwrap(); assert_eq!("\"Buy groceries\" +test", app.command.as_str()); app.handle_input(KeyCode::Char('\n')).await.unwrap(); app.mode = Mode::Tasks(Action::Add); app.update_completion_list(); let backend = TestBackend::new(50, 15); let mut terminal = Terminal::new(backend).unwrap(); terminal .draw(|f| { app.draw(f); app.draw(f); }) .unwrap(); let input = "Buy groceries"; for c in input.chars() { app.handle_input(KeyCode::Char(c)).await.unwrap(); } app.handle_input(KeyCode::Right).await.unwrap(); app.handle_input(KeyCode::Backspace).await.unwrap(); app.update(true).await.unwrap(); app.handle_input(KeyCode::Down).await.unwrap(); assert_eq!("\"Buy groceries", app.command.as_str()); app.update(true).await.unwrap(); app.handle_input(KeyCode::Up).await.unwrap(); assert_eq!("\"Buy groceries\" +test", app.command.as_str()); // teardown(); } async fn test_multiline_paste_in_add_prompt_does_not_submit() { let mut app = TaskwarriorTui::new("next", false).await.unwrap(); app.mode = Mode::Tasks(Action::Add); app.update_completion_list(); app.handle_paste("hello \n world"); assert_eq!(Mode::Tasks(Action::Add), app.mode); assert_eq!("hello \n world", app.command.as_str()); } async fn test_draw_command_shows_control_characters() { let app = TaskwarriorTui::new("next", false).await.unwrap(); let backend = TestBackend::new(20, 3); let mut terminal = Terminal::new(backend).unwrap(); terminal .draw(|f| { app.draw_command( f, f.area(), "hello \n world", (Span::styled("Add Task", Style::default().add_modifier(Modifier::BOLD)), None), utils::display_width("hello \n world"), false, None, None, ); }) .unwrap(); assert!(buffer_view(terminal.backend().buffer()).contains("\"hello ^J world \"")); } #[tokio::test] async fn test_taskwarrior_tui() { reset_test_taskdata(); let app = TaskwarriorTui::new("next", false).await.unwrap(); assert!( app.task_by_index(0).is_none(), "Expected task data to be empty but found {} tasks. Delete contents of {:?} and {:?} and run the tests again.", app.tasks.len(), get_taskdata_path(), get_taskdata_path().parent().unwrap().join(".config") ); let app = TaskwarriorTui::new("next", false).await.unwrap(); assert!( app .task_by_uuid(Uuid::parse_str("3f43831b-88dc-45e2-bf0d-4aea6db634cc").unwrap()) .is_none() ); test_draw_empty_task_report().await; test_draw_calendar().await; test_draw_help_popup().await; setup(); let app = TaskwarriorTui::new("next", false).await.unwrap(); assert!(app.task_by_index(0).is_some()); let app = TaskwarriorTui::new("next", false).await.unwrap(); assert!( app .task_by_uuid(Uuid::parse_str("3f43831b-88dc-45e2-bf0d-4aea6db634cc").unwrap()) .is_some() ); test_draw_task_report_with_extended_modify_command().await; test_draw_timesheet_styles_week_headers_with_actions().await; test_update_timesheet_uses_current_week_events().await; // test_draw_task_report(); test_task_tags().await; test_task_style().await; test_task_style_keyword_color().await; test_task_style_uda_color_with_spaces_in_value().await; test_task_report_alternate_style().await; test_context_menu_enter_closes_menu().await; test_context_menu_enter_can_stay_open().await; test_context_menu_enter_with_no_matches_does_not_select().await; test_context_menu_selection_config_combinations_select_filtered_match_on_enter().await; test_context_menu_selection_config_combinations_keep_menu_open_with_no_matches().await; test_context_menu_selection_config_combinations_preserve_selection_while_filtering().await; test_context_menu_selection_config_combinations_move_to_top_when_selection_is_filtered_out().await; test_context_menu_enter_selects_filtered_match_when_select_on_move_is_enabled().await; test_context_menu_type_to_filter_selects_top_match_when_select_on_move_is_enabled().await; test_context_menu_backspace_selects_top_match_and_stays_open_when_select_on_move_is_enabled().await; test_context_menu_escape_clears_search_selects_top_match_and_stays_open_when_select_on_move_is_enabled().await; test_context_menu_enter_with_no_matches_stays_open_when_select_on_move_is_enabled().await; test_report_menu_enter_closes_menu().await; test_report_menu_enter_can_stay_open().await; test_report_menu_enter_with_no_matches_does_not_select().await; test_report_menu_selection_config_combinations_select_filtered_match_on_enter().await; test_report_menu_selection_config_combinations_keep_menu_open_with_no_matches().await; test_report_menu_selection_config_combinations_preserve_selection_while_filtering().await; test_report_menu_selection_config_combinations_move_to_top_when_selection_is_filtered_out().await; test_report_menu_enter_selects_filtered_match_when_select_on_move_is_enabled().await; test_report_menu_type_to_filter_selects_top_match_when_select_on_move_is_enabled().await; test_report_menu_backspace_selects_top_match_and_stays_open_when_select_on_move_is_enabled().await; test_report_menu_escape_clears_search_selects_top_match_and_stays_open_when_select_on_move_is_enabled().await; test_report_menu_enter_with_no_matches_stays_open_when_select_on_move_is_enabled().await; test_task_context().await; test_task_tomorrow().await; test_task_earlier_today().await; test_task_later_today().await; test_task_details_are_cached_for_selected_task().await; test_taskwarrior_tui_history().await; test_multiline_paste_in_add_prompt_does_not_submit().await; test_draw_command_shows_control_characters().await; teardown(); } async fn test_task_tags() { // testing tags let app = TaskwarriorTui::new("next", false).await.unwrap(); let task = app.task_by_id(1).unwrap(); let tags = vec!["PENDING".to_string(), "PRIORITY".to_string()]; for tag in tags { assert!(task.tags().unwrap().contains(&tag)); } let mut app = TaskwarriorTui::new("next", false).await.unwrap(); let task = app.task_by_id(11).unwrap(); let tags = ["finance", "UNBLOCKED", "PENDING", "TAGGED", "UDA"] .iter() .map(ToString::to_string) .collect::>(); for tag in tags { assert!(task.tags().unwrap().contains(&tag)); } if let Some(task) = app.task_by_id(11) { let i = app.task_index_by_uuid(*task.uuid()).unwrap_or_default(); app.current_selection = i; app.current_selection_id = None; app.current_selection_uuid = None; } app.task_quick_tag().unwrap(); app.update(true).await.unwrap(); let task = app.task_by_id(11).unwrap(); let tags = ["next", "finance", "UNBLOCKED", "PENDING", "TAGGED", "UDA"] .iter() .map(ToString::to_string) .collect::>(); for tag in tags { assert!(task.tags().unwrap().contains(&tag)); } app.task_quick_tag().unwrap(); app.update(true).await.unwrap(); let task = app.task_by_id(11).unwrap(); let tags = ["finance", "UNBLOCKED", "PENDING", "TAGGED", "UDA"] .iter() .map(ToString::to_string) .collect::>(); for tag in tags { assert!(task.tags().unwrap().contains(&tag)); } } async fn test_task_style() { let app = TaskwarriorTui::new("next", false).await.unwrap(); let task = app.task_by_id(1).unwrap(); for r in vec![ "active", "blocked", "blocking", "completed", "deleted", "due", "due.today", "keyword.", "overdue", "project.", "recurring", "scheduled", "tag.", "tagged", "uda.", ] { assert!(app.config.rule_precedence_color.contains(&r.to_string())); } let style = app.style_for_task(&task); assert_eq!(style, Style::default()); let task = app.task_by_id(11).unwrap(); let style = app.style_for_task(&task); } async fn test_task_style_keyword_color() { let mut app = TaskwarriorTui::new("next", false).await.unwrap(); let keyword_style = Style::default() .fg(Color::Indexed(4)) .bg(Color::Indexed(3)) .add_modifier(Modifier::ITALIC); app.config.color_keywords = vec![("urgent".to_string(), keyword_style)]; let mut task = app.task_by_id(1).unwrap(); *task.description_mut() = "urgent task".to_string(); assert_eq!(app.style_for_task(&task), keyword_style); *task.description_mut() = "ordinary task".to_string(); assert_eq!(app.style_for_task(&task), Style::default()); } async fn test_task_style_uda_color_with_spaces_in_value() { let app = TaskwarriorTui::new("next", false).await.unwrap(); let mut task = app.task_by_id(1).unwrap(); task.uda_mut().insert("jirastatus".to_string(), UDAValue::Str("In Review".to_string())); assert_eq!(app.style_for_task(&task), Config::get_tcolor("black on bright cyan")); task.uda_mut().insert("jirastatus".to_string(), UDAValue::Str("To Do".to_string())); assert_eq!(app.style_for_task(&task), Config::get_tcolor("bright white")); } async fn test_task_report_alternate_style() { let mut app = TaskwarriorTui::new("next", false).await.unwrap(); assert!(app.update(true).await.is_ok()); let alternate_style = Style::default() .fg(Color::Indexed(5)) .bg(Color::Indexed(2)) .add_modifier(Modifier::ITALIC); app.config.color.insert("color.alternate".to_string(), alternate_style); let task = app.task_by_id(1).unwrap(); let default_style = app.task_report_row_style(0, &task); let odd_row_style = app.task_report_row_style(1, &task); assert_eq!(default_style, Style::default()); assert_eq!(odd_row_style, alternate_style); app.config.uda_task_report_use_alternate_style = false; let disabled_style = app.task_report_row_style(1, &task); assert_eq!(disabled_style, Style::default()); } async fn test_task_details_are_cached_for_selected_task() { let mut app = TaskwarriorTui::new("next", false).await.unwrap(); assert!(app.update(true).await.is_ok()); assert!(app.update_task_details().await.is_ok()); let task_uuid = *app.tasks[app.current_selection].uuid(); let task_modified = app.tasks[app.current_selection].modified().cloned(); assert!(app.task_details.contains_key(&task_uuid)); app.task_details.insert(task_uuid, "sentinel".to_string()); app.task_details_modified.insert(task_uuid, task_modified.clone()); assert!(app.update_task_details().await.is_ok()); assert_eq!(app.task_details.get(&task_uuid).map(String::as_str), Some("sentinel")); app.task_details_modified.insert( task_uuid, match task_modified { Some(_) => None, None => Some(Local::now().naive_utc().into()), }, ); assert!(app.update_task_details().await.is_ok()); assert_ne!(app.task_details.get(&task_uuid).map(String::as_str), Some("sentinel")); app.task_details.insert(task_uuid, "sentinel".to_string()); app .task_details_modified .insert(task_uuid, app.tasks[app.current_selection].modified().cloned()); app.terminal_width = app.terminal_width.saturating_add(10); assert!(app.update_task_details().await.is_ok()); assert_ne!(app.task_details.get(&task_uuid).map(String::as_str), Some("sentinel")); } async fn test_context_menu_enter_closes_menu() { reset_test_context(); let mut app = TaskwarriorTui::new("next", false).await.unwrap(); assert!(app.update(true).await.is_ok()); app.handle_input(app.keyconfig.context_menu).await.unwrap(); assert_eq!(app.mode, Mode::Tasks(Action::ContextMenu)); let finance_index = app .contexts .filtered_indices() .iter() .position(|&ri| app.contexts.rows[ri].name == "finance") .unwrap(); app.contexts.table_state.select(Some(finance_index)); app.handle_input(KeyCode::Char('\n')).await.unwrap(); assert_eq!(app.mode, Mode::Tasks(Action::Report)); assert_eq!(app.current_context_filter, "+finance -private"); } async fn test_context_menu_enter_can_stay_open() { reset_test_context(); let mut app = TaskwarriorTui::new("next", false).await.unwrap(); assert!(app.update(true).await.is_ok()); app.config.uda_context_menu_close_on_select = false; app.handle_input(app.keyconfig.context_menu).await.unwrap(); assert_eq!(app.mode, Mode::Tasks(Action::ContextMenu)); let finance_index = app .contexts .filtered_indices() .iter() .position(|&ri| app.contexts.rows[ri].name == "finance") .unwrap(); app.contexts.table_state.select(Some(finance_index)); app.handle_input(KeyCode::Char('\n')).await.unwrap(); assert_eq!(app.mode, Mode::Tasks(Action::ContextMenu)); assert_eq!(app.current_context_filter, "+finance -private"); } async fn test_context_menu_enter_with_no_matches_does_not_select() { reset_test_context(); let mut app = TaskwarriorTui::new("next", false).await.unwrap(); assert!(app.update(true).await.is_ok()); app.handle_input(app.keyconfig.context_menu).await.unwrap(); assert_eq!(app.mode, Mode::Tasks(Action::ContextMenu)); for c in "zzz-no-match".chars() { app.handle_input(KeyCode::Char(c)).await.unwrap(); } assert!(app.contexts.filtered_indices().is_empty()); app.handle_input(KeyCode::Char('\n')).await.unwrap(); assert_eq!(app.mode, Mode::Tasks(Action::ContextMenu)); assert_eq!(app.current_context_filter, ""); } async fn test_context_menu_selection_config_combinations_select_filtered_match_on_enter() { for (select_on_move, close_on_select) in [(false, false), (false, true), (true, false), (true, true)] { reset_test_context(); let mut app = TaskwarriorTui::new("next", false).await.unwrap(); assert!(app.update(true).await.is_ok()); configure_context_menu_selection(&mut app, select_on_move, close_on_select); app.handle_input(app.keyconfig.context_menu).await.unwrap(); assert_eq!(app.mode, Mode::Tasks(Action::ContextMenu)); let initial_context_filter = app.current_context_filter.clone(); for c in "work".chars() { app.handle_input(KeyCode::Char(c)).await.unwrap(); } let expected_context_filter = selected_context_filter(&app).unwrap(); let expected_during_search = if select_on_move { expected_context_filter.clone() } else { initial_context_filter.clone() }; assert_eq!( app.current_context_filter, expected_during_search, "context combo select_on_move={select_on_move} close_on_select={close_on_select}" ); app.handle_input(KeyCode::Char('\n')).await.unwrap(); let expected_mode = if close_on_select { Mode::Tasks(Action::Report) } else { Mode::Tasks(Action::ContextMenu) }; assert_eq!( app.mode, expected_mode, "context combo select_on_move={select_on_move} close_on_select={close_on_select}" ); assert_eq!( app.current_context_filter, expected_context_filter, "context combo select_on_move={select_on_move} close_on_select={close_on_select}" ); } } async fn test_context_menu_selection_config_combinations_keep_menu_open_with_no_matches() { for (select_on_move, close_on_select) in [(false, false), (false, true), (true, false), (true, true)] { reset_test_context(); let mut app = TaskwarriorTui::new("next", false).await.unwrap(); assert!(app.update(true).await.is_ok()); configure_context_menu_selection(&mut app, select_on_move, close_on_select); app.handle_input(app.keyconfig.context_menu).await.unwrap(); assert_eq!(app.mode, Mode::Tasks(Action::ContextMenu)); let initial_context_filter = app.current_context_filter.clone(); for c in "zzz-no-match".chars() { app.handle_input(KeyCode::Char(c)).await.unwrap(); } assert!( app.contexts.filtered_indices().is_empty(), "context combo select_on_move={select_on_move} close_on_select={close_on_select}" ); assert_eq!( app.current_context_filter, initial_context_filter, "context combo select_on_move={select_on_move} close_on_select={close_on_select}" ); app.handle_input(KeyCode::Char('\n')).await.unwrap(); assert_eq!( app.mode, Mode::Tasks(Action::ContextMenu), "context combo select_on_move={select_on_move} close_on_select={close_on_select}" ); assert_eq!( app.current_context_filter, initial_context_filter, "context combo select_on_move={select_on_move} close_on_select={close_on_select}" ); } } async fn test_context_menu_selection_config_combinations_preserve_selection_while_filtering() { for (select_on_move, close_on_select) in [(false, false), (false, true), (true, false), (true, true)] { reset_test_context(); let mut app = TaskwarriorTui::new("next", false).await.unwrap(); assert!(app.update(true).await.is_ok()); configure_context_menu_selection(&mut app, select_on_move, close_on_select); app.handle_input(app.keyconfig.context_menu).await.unwrap(); assert_eq!(app.mode, Mode::Tasks(Action::ContextMenu)); let work_index = app .contexts .filtered_indices() .iter() .position(|&ri| app.contexts.rows[ri].name == "work") .unwrap(); app.contexts.table_state.select(Some(work_index)); let initial_context_filter = app.current_context_filter.clone(); let expected_selected_context_filter = selected_context_filter(&app).unwrap(); app.handle_input(KeyCode::Char('o')).await.unwrap(); assert_eq!( selected_context_filter(&app).as_deref(), Some(expected_selected_context_filter.as_str()), "context combo select_on_move={select_on_move} close_on_select={close_on_select}" ); let expected_active_context_filter = if select_on_move { expected_selected_context_filter.as_str() } else { initial_context_filter.as_str() }; assert_eq!( app.current_context_filter.as_str(), expected_active_context_filter, "context combo select_on_move={select_on_move} close_on_select={close_on_select}" ); } } async fn test_context_menu_selection_config_combinations_move_to_top_when_selection_is_filtered_out() { for (select_on_move, close_on_select) in [(false, false), (false, true), (true, false), (true, true)] { reset_test_context(); let mut app = TaskwarriorTui::new("next", false).await.unwrap(); assert!(app.update(true).await.is_ok()); configure_context_menu_selection(&mut app, select_on_move, close_on_select); app.handle_input(app.keyconfig.context_menu).await.unwrap(); assert_eq!(app.mode, Mode::Tasks(Action::ContextMenu)); let finance_index = app .contexts .filtered_indices() .iter() .position(|&ri| app.contexts.rows[ri].name == "finance") .unwrap(); app.contexts.table_state.select(Some(finance_index)); let initial_context_filter = app.current_context_filter.clone(); for c in "work".chars() { app.handle_input(KeyCode::Char(c)).await.unwrap(); } assert_eq!( selected_context_filter(&app).as_deref(), Some("-personal -private"), "context combo select_on_move={select_on_move} close_on_select={close_on_select}" ); let expected_active_context_filter = if select_on_move { "-personal -private" } else { initial_context_filter.as_str() }; assert_eq!( app.current_context_filter.as_str(), expected_active_context_filter, "context combo select_on_move={select_on_move} close_on_select={close_on_select}" ); } } async fn test_context_menu_type_to_filter_selects_top_match_when_select_on_move_is_enabled() { reset_test_context(); let mut app = TaskwarriorTui::new("next", false).await.unwrap(); assert!(app.update(true).await.is_ok()); enable_context_menu_move_selection(&mut app); app.handle_input(app.keyconfig.context_menu).await.unwrap(); assert_eq!(app.mode, Mode::Tasks(Action::ContextMenu)); for c in "finance".chars() { app.handle_input(KeyCode::Char(c)).await.unwrap(); } assert_eq!(app.mode, Mode::Tasks(Action::ContextMenu)); assert_eq!(app.current_context_filter, selected_context_filter(&app).unwrap()); } async fn test_context_menu_backspace_selects_top_match_and_stays_open_when_select_on_move_is_enabled() { reset_test_context(); let mut app = TaskwarriorTui::new("next", false).await.unwrap(); assert!(app.update(true).await.is_ok()); enable_context_menu_move_selection(&mut app); app.handle_input(app.keyconfig.context_menu).await.unwrap(); assert_eq!(app.mode, Mode::Tasks(Action::ContextMenu)); for c in "finance".chars() { app.handle_input(KeyCode::Char(c)).await.unwrap(); } app.handle_input(KeyCode::Backspace).await.unwrap(); assert_eq!(app.mode, Mode::Tasks(Action::ContextMenu)); assert_eq!(app.current_context_filter, selected_context_filter(&app).unwrap()); } async fn test_context_menu_escape_clears_search_selects_top_match_and_stays_open_when_select_on_move_is_enabled() { reset_test_context(); let mut app = TaskwarriorTui::new("next", false).await.unwrap(); assert!(app.update(true).await.is_ok()); enable_context_menu_move_selection(&mut app); app.handle_input(app.keyconfig.context_menu).await.unwrap(); assert_eq!(app.mode, Mode::Tasks(Action::ContextMenu)); for c in "finance".chars() { app.handle_input(KeyCode::Char(c)).await.unwrap(); } app.handle_input(KeyCode::Esc).await.unwrap(); assert_eq!(app.mode, Mode::Tasks(Action::ContextMenu)); assert!(app.contexts.search.is_empty()); assert_eq!(app.current_context_filter, selected_context_filter(&app).unwrap()); } async fn test_context_menu_enter_with_no_matches_stays_open_when_select_on_move_is_enabled() { reset_test_context(); let mut app = TaskwarriorTui::new("next", false).await.unwrap(); assert!(app.update(true).await.is_ok()); enable_context_menu_move_selection(&mut app); app.handle_input(app.keyconfig.context_menu).await.unwrap(); assert_eq!(app.mode, Mode::Tasks(Action::ContextMenu)); let initial_context_filter = app.current_context_filter.clone(); for c in "zzz-no-match".chars() { app.handle_input(KeyCode::Char(c)).await.unwrap(); } assert!(app.contexts.filtered_indices().is_empty()); assert_eq!(app.current_context_filter, initial_context_filter); app.handle_input(KeyCode::Char('\n')).await.unwrap(); assert_eq!(app.mode, Mode::Tasks(Action::ContextMenu)); assert_eq!(app.current_context_filter, initial_context_filter); } async fn test_context_menu_enter_selects_filtered_match_when_select_on_move_is_enabled() { reset_test_context(); let mut app = TaskwarriorTui::new("next", false).await.unwrap(); assert!(app.update(true).await.is_ok()); enable_context_menu_move_selection(&mut app); app.handle_input(app.keyconfig.context_menu).await.unwrap(); assert_eq!(app.mode, Mode::Tasks(Action::ContextMenu)); for c in "finance".chars() { app.handle_input(KeyCode::Char(c)).await.unwrap(); } let expected_context_filter = selected_context_filter(&app).unwrap(); app.handle_input(KeyCode::Char('\n')).await.unwrap(); assert_eq!(app.mode, Mode::Tasks(Action::Report)); assert_eq!(app.current_context_filter, expected_context_filter); } async fn test_report_menu_enter_closes_menu() { let mut app = TaskwarriorTui::new("next", false).await.unwrap(); assert!(app.update(true).await.is_ok()); app.handle_input(app.keyconfig.report_menu).await.unwrap(); assert_eq!(app.mode, Mode::Tasks(Action::ReportMenu)); let day_index = app.reports.rows.iter().position(|row| row.name == "day").unwrap(); app.reports.table_state.select(Some(day_index)); app.handle_input(KeyCode::Char('\n')).await.unwrap(); assert_eq!(app.mode, Mode::Tasks(Action::Report)); assert_eq!(app.report, "day"); assert_eq!(app.config.filter.trim(), "status:pending"); } async fn test_report_menu_enter_can_stay_open() { let mut app = TaskwarriorTui::new("next", false).await.unwrap(); assert!(app.update(true).await.is_ok()); app.config.uda_report_menu_close_on_select = false; app.handle_input(app.keyconfig.report_menu).await.unwrap(); assert_eq!(app.mode, Mode::Tasks(Action::ReportMenu)); let day_index = app.reports.rows.iter().position(|row| row.name == "day").unwrap(); app.reports.table_state.select(Some(day_index)); app.handle_input(KeyCode::Char('\n')).await.unwrap(); assert_eq!(app.mode, Mode::Tasks(Action::ReportMenu)); assert_eq!(app.report, "day"); assert_eq!(app.config.filter.trim(), "status:pending"); } async fn test_report_menu_enter_with_no_matches_does_not_select() { let mut app = TaskwarriorTui::new("next", false).await.unwrap(); assert!(app.update(true).await.is_ok()); app.handle_input(app.keyconfig.report_menu).await.unwrap(); assert_eq!(app.mode, Mode::Tasks(Action::ReportMenu)); for c in "zzz-no-match".chars() { app.handle_input(KeyCode::Char(c)).await.unwrap(); } assert!(app.reports.filtered_indices().is_empty()); app.handle_input(KeyCode::Char('\n')).await.unwrap(); assert_eq!(app.mode, Mode::Tasks(Action::ReportMenu)); assert_eq!(app.report, "next"); assert_eq!(app.config.filter.trim(), "(status:pending or status:waiting)"); } async fn test_report_menu_selection_config_combinations_select_filtered_match_on_enter() { for (select_on_move, close_on_select) in [(false, false), (false, true), (true, false), (true, true)] { let mut app = TaskwarriorTui::new("next", false).await.unwrap(); assert!(app.update(true).await.is_ok()); configure_report_menu_selection(&mut app, select_on_move, close_on_select); app.handle_input(app.keyconfig.report_menu).await.unwrap(); assert_eq!(app.mode, Mode::Tasks(Action::ReportMenu)); let initial_report = app.report.clone(); for c in "day".chars() { app.handle_input(KeyCode::Char(c)).await.unwrap(); } let expected_report = selected_report_name(&app).unwrap(); let expected_during_search = if select_on_move { expected_report.clone() } else { initial_report.clone() }; assert_eq!( app.report, expected_during_search, "report combo select_on_move={select_on_move} close_on_select={close_on_select}" ); app.handle_input(KeyCode::Char('\n')).await.unwrap(); let expected_mode = if close_on_select { Mode::Tasks(Action::Report) } else { Mode::Tasks(Action::ReportMenu) }; assert_eq!( app.mode, expected_mode, "report combo select_on_move={select_on_move} close_on_select={close_on_select}" ); assert_eq!( app.report, expected_report, "report combo select_on_move={select_on_move} close_on_select={close_on_select}" ); } } async fn test_report_menu_selection_config_combinations_keep_menu_open_with_no_matches() { for (select_on_move, close_on_select) in [(false, false), (false, true), (true, false), (true, true)] { let mut app = TaskwarriorTui::new("next", false).await.unwrap(); assert!(app.update(true).await.is_ok()); configure_report_menu_selection(&mut app, select_on_move, close_on_select); app.handle_input(app.keyconfig.report_menu).await.unwrap(); assert_eq!(app.mode, Mode::Tasks(Action::ReportMenu)); let initial_report = app.report.clone(); for c in "zzz-no-match".chars() { app.handle_input(KeyCode::Char(c)).await.unwrap(); } assert!( app.reports.filtered_indices().is_empty(), "report combo select_on_move={select_on_move} close_on_select={close_on_select}" ); assert_eq!( app.report, initial_report, "report combo select_on_move={select_on_move} close_on_select={close_on_select}" ); app.handle_input(KeyCode::Char('\n')).await.unwrap(); assert_eq!( app.mode, Mode::Tasks(Action::ReportMenu), "report combo select_on_move={select_on_move} close_on_select={close_on_select}" ); assert_eq!( app.report, initial_report, "report combo select_on_move={select_on_move} close_on_select={close_on_select}" ); } } async fn test_report_menu_selection_config_combinations_preserve_selection_while_filtering() { for (select_on_move, close_on_select) in [(false, false), (false, true), (true, false), (true, true)] { let mut app = TaskwarriorTui::new("next", false).await.unwrap(); assert!(app.update(true).await.is_ok()); configure_report_menu_selection(&mut app, select_on_move, close_on_select); app.handle_input(app.keyconfig.report_menu).await.unwrap(); assert_eq!(app.mode, Mode::Tasks(Action::ReportMenu)); let recurring_index = app.reports.rows.iter().position(|row| row.name == "recurring").unwrap(); app.reports.table_state.select(Some(recurring_index)); let initial_report = app.report.clone(); let expected_selected_report = selected_report_name(&app).unwrap(); app.handle_input(KeyCode::Char('r')).await.unwrap(); assert_eq!( selected_report_name(&app).as_deref(), Some(expected_selected_report.as_str()), "report combo select_on_move={select_on_move} close_on_select={close_on_select}" ); let expected_active_report = if select_on_move { expected_selected_report.as_str() } else { initial_report.as_str() }; assert_eq!( app.report.as_str(), expected_active_report, "report combo select_on_move={select_on_move} close_on_select={close_on_select}" ); } } async fn test_report_menu_selection_config_combinations_move_to_top_when_selection_is_filtered_out() { for (select_on_move, close_on_select) in [(false, false), (false, true), (true, false), (true, true)] { let mut app = TaskwarriorTui::new("next", false).await.unwrap(); assert!(app.update(true).await.is_ok()); configure_report_menu_selection(&mut app, select_on_move, close_on_select); app.handle_input(app.keyconfig.report_menu).await.unwrap(); assert_eq!(app.mode, Mode::Tasks(Action::ReportMenu)); let day_index = app.reports.rows.iter().position(|row| row.name == "day").unwrap(); app.reports.table_state.select(Some(day_index)); let initial_report = app.report.clone(); for c in "re".chars() { app.handle_input(KeyCode::Char(c)).await.unwrap(); } assert_eq!( selected_report_name(&app).as_deref(), Some("ready"), "report combo select_on_move={select_on_move} close_on_select={close_on_select}" ); let expected_active_report = if select_on_move { "ready" } else { initial_report.as_str() }; assert_eq!( app.report.as_str(), expected_active_report, "report combo select_on_move={select_on_move} close_on_select={close_on_select}" ); } } async fn test_report_menu_type_to_filter_selects_top_match_when_select_on_move_is_enabled() { let mut app = TaskwarriorTui::new("next", false).await.unwrap(); assert!(app.update(true).await.is_ok()); enable_report_menu_move_selection(&mut app); app.handle_input(app.keyconfig.report_menu).await.unwrap(); assert_eq!(app.mode, Mode::Tasks(Action::ReportMenu)); for c in "day".chars() { app.handle_input(KeyCode::Char(c)).await.unwrap(); } assert_eq!(app.mode, Mode::Tasks(Action::ReportMenu)); assert_eq!(app.report, selected_report_name(&app).unwrap()); } async fn test_report_menu_backspace_selects_top_match_and_stays_open_when_select_on_move_is_enabled() { let mut app = TaskwarriorTui::new("next", false).await.unwrap(); assert!(app.update(true).await.is_ok()); enable_report_menu_move_selection(&mut app); app.handle_input(app.keyconfig.report_menu).await.unwrap(); assert_eq!(app.mode, Mode::Tasks(Action::ReportMenu)); for c in "recurring".chars() { app.handle_input(KeyCode::Char(c)).await.unwrap(); } for _ in 0..7 { app.handle_input(KeyCode::Ctrl('h')).await.unwrap(); } assert_eq!(app.mode, Mode::Tasks(Action::ReportMenu)); assert_eq!(app.reports.search, "re"); assert_eq!(app.report, selected_report_name(&app).unwrap()); } async fn test_report_menu_escape_clears_search_selects_top_match_and_stays_open_when_select_on_move_is_enabled() { let mut app = TaskwarriorTui::new("next", false).await.unwrap(); assert!(app.update(true).await.is_ok()); enable_report_menu_move_selection(&mut app); app.handle_input(app.keyconfig.report_menu).await.unwrap(); assert_eq!(app.mode, Mode::Tasks(Action::ReportMenu)); for c in "day".chars() { app.handle_input(KeyCode::Char(c)).await.unwrap(); } app.handle_input(KeyCode::Esc).await.unwrap(); assert_eq!(app.mode, Mode::Tasks(Action::ReportMenu)); assert!(app.reports.search.is_empty()); assert_eq!(app.report, selected_report_name(&app).unwrap()); } async fn test_report_menu_enter_with_no_matches_stays_open_when_select_on_move_is_enabled() { let mut app = TaskwarriorTui::new("next", false).await.unwrap(); assert!(app.update(true).await.is_ok()); enable_report_menu_move_selection(&mut app); app.handle_input(app.keyconfig.report_menu).await.unwrap(); assert_eq!(app.mode, Mode::Tasks(Action::ReportMenu)); let initial_report = app.report.clone(); for c in "zzz-no-match".chars() { app.handle_input(KeyCode::Char(c)).await.unwrap(); } assert!(app.reports.filtered_indices().is_empty()); assert_eq!(app.report, initial_report); app.handle_input(KeyCode::Char('\n')).await.unwrap(); assert_eq!(app.mode, Mode::Tasks(Action::ReportMenu)); assert_eq!(app.report, initial_report); } async fn test_report_menu_enter_selects_filtered_match_when_select_on_move_is_enabled() { let mut app = TaskwarriorTui::new("next", false).await.unwrap(); assert!(app.update(true).await.is_ok()); enable_report_menu_move_selection(&mut app); app.handle_input(app.keyconfig.report_menu).await.unwrap(); assert_eq!(app.mode, Mode::Tasks(Action::ReportMenu)); for c in "day".chars() { app.handle_input(KeyCode::Char(c)).await.unwrap(); } let expected_report = selected_report_name(&app).unwrap(); app.handle_input(KeyCode::Char('\n')).await.unwrap(); assert_eq!(app.mode, Mode::Tasks(Action::Report)); assert_eq!(app.report, expected_report); } async fn test_task_context() { reset_test_context(); let mut app = TaskwarriorTui::new("next", false).await.unwrap(); assert!(app.update(true).await.is_ok()); app.context_select().unwrap(); assert_eq!(app.tasks.len(), 26); assert_eq!(app.current_context_filter, ""); assert_eq!(app.contexts.table_state.current_selection(), Some(0)); app.context_next(); app.context_next(); app.context_select().unwrap(); assert_eq!(app.contexts.table_state.current_selection(), Some(2)); assert!(app.update(true).await.is_ok()); assert_eq!(app.tasks.len(), 1); assert_eq!(app.current_context_filter, "+finance -private"); assert_eq!(app.contexts.table_state.current_selection(), Some(2)); app.context_previous(); app.context_previous(); app.context_select().unwrap(); assert_eq!(app.contexts.table_state.current_selection(), Some(0)); assert!(app.update(true).await.is_ok()); assert_eq!(app.tasks.len(), 26); assert_eq!(app.current_context_filter, ""); } async fn test_task_tomorrow() { let total_tasks: u64 = 26; let mut app = TaskwarriorTui::new("next", false).await.unwrap(); assert!(app.update(true).await.is_ok()); assert_eq!(app.tasks.len(), total_tasks as usize); assert_eq!(app.current_context_filter, ""); let now = Local::now(); let now = TimeZone::from_utc_datetime(now.offset(), &now.naive_utc()); let mut command = std::process::Command::new(&task_exe()); command.arg("add"); let tomorrow = now + chrono::Duration::days(1); let message = format!( "'new task for testing tomorrow' due:{:04}-{:02}-{:02}", tomorrow.year(), tomorrow.month(), tomorrow.day(), ); let shell = message.as_str().replace("'", "\\'"); let cmd = shlex::split(&shell).unwrap(); for s in cmd { command.arg(&s); } let output = command.output().unwrap(); let s = String::from_utf8_lossy(&output.stdout); let re = Regex::new(r"^Created task (?P\d+).\n$").unwrap(); let caps = re.captures(&s).unwrap(); let task_id = caps["task_id"].parse::().unwrap(); assert_eq!(task_id, total_tasks + 1); assert!(app.update(true).await.is_ok()); assert_eq!(app.tasks.len(), (total_tasks + 1) as usize); assert_eq!(app.current_context_filter, ""); let task = app.task_by_id(task_id).unwrap(); assert_task_has_tags(&task, &["DUE", "MONTH", "PENDING", "QUARTER", "TOMORROW", "UDA", "UNBLOCKED", "YEAR"]); let output = std::process::Command::new(&task_exe()) .arg("rc.confirmation=off") .arg("undo") .output() .unwrap(); let mut app = TaskwarriorTui::new("next", false).await.unwrap(); assert!(app.update(true).await.is_ok()); assert_eq!(app.tasks.len(), total_tasks as usize); assert_eq!(app.current_context_filter, ""); } async fn test_task_earlier_today() { let total_tasks: u64 = 26; let mut app = TaskwarriorTui::new("next", false).await.unwrap(); assert!(app.update(true).await.is_ok()); assert_eq!(app.tasks.len(), total_tasks as usize); assert_eq!(app.current_context_filter, ""); let now = Local::now(); let now = TimeZone::from_utc_datetime(now.offset(), &now.naive_utc()); let mut command = std::process::Command::new(&task_exe()); command.arg("add"); let message = "'new task for testing earlier today' due:now"; let shell = message.replace("'", "\\'"); let cmd = shlex::split(&shell).unwrap(); for s in cmd { command.arg(&s); } let output = command.output().unwrap(); let s = String::from_utf8_lossy(&output.stdout); let re = Regex::new(r"^Created task (?P\d+).\n$").unwrap(); let caps = re.captures(&s).unwrap(); let task_id = caps["task_id"].parse::().unwrap(); assert_eq!(task_id, total_tasks + 1); assert!(app.update(true).await.is_ok()); assert_eq!(app.tasks.len(), (total_tasks + 1) as usize); assert_eq!(app.current_context_filter, ""); let task = app.task_by_id(task_id).unwrap(); assert_task_has_tags( &task, &[ "DUE", "DUETODAY", "MONTH", "OVERDUE", "PENDING", "QUARTER", "TODAY", "UDA", "UNBLOCKED", "YEAR", ], ); let output = std::process::Command::new(&task_exe()) .arg("rc.confirmation=off") .arg("undo") .output() .unwrap(); let mut app = TaskwarriorTui::new("next", false).await.unwrap(); assert!(app.update(true).await.is_ok()); assert_eq!(app.tasks.len(), total_tasks as usize); assert_eq!(app.current_context_filter, ""); } async fn test_task_later_today() { let total_tasks: u64 = 26; let mut app = TaskwarriorTui::new("next", false).await.unwrap(); assert!(app.update(true).await.is_ok()); assert_eq!(app.tasks.len(), total_tasks as usize); assert_eq!(app.current_context_filter, ""); let now = Local::now(); let now = TimeZone::from_utc_datetime(now.offset(), &now.naive_utc()); let mut command = std::process::Command::new(&task_exe()); command.arg("add"); let later_today = now .with_hour(23) .and_then(|dt| dt.with_minute(59)) .and_then(|dt| dt.with_second(59)) .unwrap(); assert!(later_today > now, "cannot exercise the later-today path at the end of the local day"); let message = format!( "'new task for testing later today' due:'{:04}-{:02}-{:02}T{:02}:{:02}:{:02}'", later_today.year(), later_today.month(), later_today.day(), later_today.hour(), later_today.minute(), later_today.second(), ); let shell = message.as_str().replace("'", "\\'"); let cmd = shlex::split(&shell).unwrap(); for s in cmd { command.arg(&s); } let output = command.output().unwrap(); let s = String::from_utf8_lossy(&output.stdout); let re = Regex::new(r"^Created task (?P\d+).\n$").unwrap(); let caps = re.captures(&s).unwrap(); let task_id = caps["task_id"].parse::().unwrap(); assert_eq!(task_id, total_tasks + 1); assert!(app.update(true).await.is_ok()); assert_eq!(app.tasks.len(), (total_tasks + 1) as usize); assert_eq!(app.current_context_filter, ""); let task = app.task_by_id(task_id).unwrap(); assert_task_has_tags( &task, &["DUE", "DUETODAY", "MONTH", "PENDING", "QUARTER", "TODAY", "UDA", "UNBLOCKED", "YEAR"], ); let output = std::process::Command::new(&task_exe()) .arg("rc.confirmation=off") .arg("undo") .output() .unwrap(); let mut app = TaskwarriorTui::new("next", false).await.unwrap(); assert!(app.update(true).await.is_ok()); assert_eq!(app.tasks.len(), total_tasks as usize); assert_eq!(app.current_context_filter, ""); } async fn test_draw_empty_task_report() { let mut expected = Buffer::with_lines(vec![ " Tasks Projects Timesheet Calendanext [none]", " ", " ", " ", " ", " ", " ", "──────────────────────────────────────────────────", "Task not found ", " ", " ", " ", " ", "Filter Tasks ", "(status:pending or status:waiting) ", ]); for i in 0..=49 { // First line expected[(i, 0)].set_style(Style::default().add_modifier(Modifier::REVERSED)); } for i in 1..=5 { // Tasks expected[(i, 0)].set_style(Style::default().add_modifier(Modifier::BOLD).add_modifier(Modifier::REVERSED)); } for i in 0..=49 { // Command line expected[(i, 13)].set_style(Style::default().add_modifier(Modifier::REVERSED)); } let mut app = TaskwarriorTui::new("next", false).await.unwrap(); app.task_info_location_override = Some(TaskInfoLocation::Bottom); app.task_report_next(); app.context_next(); let total_tasks: u64 = 0; assert!(app.update(true).await.is_ok()); assert_eq!(app.tasks.len(), total_tasks as usize); assert_eq!(app.current_context_filter, ""); let now = Local::now(); let now = TimeZone::from_utc_datetime(now.offset(), &now.naive_utc()); app.update(true).await.unwrap(); let backend = TestBackend::new(50, 15); let mut terminal = Terminal::new(backend).unwrap(); terminal .draw(|f| { app.draw(f); }) .unwrap(); assert_eq!(terminal.backend().size().unwrap(), expected.area.into()); terminal.backend().assert_buffer(&expected); } async fn test_draw_task_report_with_extended_modify_command() { let mut expected1 = Buffer::with_lines(vec![ "Modify Task 10 ", " based on your .taskrc ", " ", ]); let mut expected2 = Buffer::with_lines(vec![ "Modify Task 10 ", "Support color for tasks b", " ", ]); for i in 0..=13 { // Task expected1[(i, 0)].set_style(Style::default().add_modifier(Modifier::BOLD)); expected2[(i, 0)].set_style(Style::default().add_modifier(Modifier::BOLD)); } for i in 0..=24 { // Command line expected1[(i, 0)].set_style(Style::default().add_modifier(Modifier::REVERSED)); expected2[(i, 0)].set_style(Style::default().add_modifier(Modifier::REVERSED)); } let mut app = TaskwarriorTui::new("next", false).await.unwrap(); let total_tasks: u64 = 26; assert!(app.update(true).await.is_ok()); assert_eq!(app.tasks.len(), total_tasks as usize); assert_eq!(app.current_context_filter, ""); let now = Local::now(); let now = TimeZone::from_utc_datetime(now.offset(), &now.naive_utc()); app.mode = Mode::Tasks(Action::Modify); match app.task_table_state.mode() { TableMode::SingleSelection => match app.task_current() { Some(t) => { let s = format!("{} ", t.description()); app.modify.update(&s, s.as_str().len(), &mut app.changes) } None => app.modify.update("", 0, &mut app.changes), }, TableMode::MultipleSelection => app.modify.update("", 0, &mut app.changes), } app.update(true).await.unwrap(); let backend = TestBackend::new(25, 3); let mut terminal = Terminal::new(backend).unwrap(); terminal .draw(|f| { let rects = Layout::default() .direction(Direction::Vertical) .constraints([Constraint::Min(0), Constraint::Length(3)].as_ref()) .split(f.area()); let taskwarrior_position = TaskwarriorTui::get_position(&app.modify); let position = Position::new( std::cmp::min(rects[1].x + taskwarrior_position as u16, rects[1].x + rects[1].width.saturating_sub(2)), rects[1].y + 1, ); f.set_cursor_position(position); f.render_widget(Clear, rects[1]); let selected = app.current_selection; let task_ids = if app.tasks.is_empty() { vec!["0".to_string()] } else { match app.task_table_state.mode() { TableMode::SingleSelection => { vec![app.tasks[selected].id().unwrap_or_default().to_string()] } TableMode::MultipleSelection => { let mut tids = vec![]; for uuid in app.marked.iter() { if let Some(t) = app.task_by_uuid(*uuid) { tids.push(t.id().unwrap_or_default().to_string()); } } tids } } }; let label = if task_ids.len() > 1 { format!("Modify Tasks {}", task_ids.join(",")) } else { format!("Modify Task {}", task_ids.join(",")) }; app.draw_command( f, rects[1], app.modify.as_str(), (Span::styled(label, Style::default().add_modifier(Modifier::BOLD)), None), taskwarrior_position, true, app.error.clone(), None, ); }) .unwrap(); assert_eq!(terminal.backend().size().unwrap(), expected1.area.into()); terminal.backend().assert_buffer(&expected1); app.modify.move_home(); terminal .draw(|f| { let rects = Layout::default() .direction(Direction::Vertical) .constraints([Constraint::Min(0), Constraint::Length(3)].as_ref()) .split(f.area()); let taskwarrior_position = TaskwarriorTui::get_position(&app.modify); let position = Position::new( std::cmp::min(rects[1].x + taskwarrior_position as u16, rects[1].x + rects[1].width.saturating_sub(2)), rects[1].y + 1, ); f.set_cursor_position(position); f.render_widget(Clear, rects[1]); let selected = app.current_selection; let task_ids = if app.tasks.is_empty() { vec!["0".to_string()] } else { match app.task_table_state.mode() { TableMode::SingleSelection => { vec![app.tasks[selected].id().unwrap_or_default().to_string()] } TableMode::MultipleSelection => { let mut tids = vec![]; for uuid in app.marked.iter() { if let Some(t) = app.task_by_uuid(*uuid) { tids.push(t.id().unwrap_or_default().to_string()); } } tids } } }; let label = if task_ids.len() > 1 { format!("Modify Tasks {}", task_ids.join(",")) } else { format!("Modify Task {}", task_ids.join(",")) }; app.draw_command( f, rects[1], app.modify.as_str(), (Span::styled(label, Style::default().add_modifier(Modifier::BOLD)), None), taskwarrior_position, true, app.error.clone(), None, ); }) .unwrap(); assert_eq!(terminal.backend().size().unwrap(), expected2.area.into()); terminal.backend().assert_buffer(&expected2); } async fn test_draw_task_report() { let mut expected = Buffer::with_lines(vec![ "╭Task|Calendar───────────────────────────────────╮", "│ ID Age Deps P Projec Tag Due Descrip Urg │", "│ │", "│• 27 0s U new ta… 15.00│", "│ 28 0s U none new ta… 15.00│", "╰────────────────────────────────────────────────╯", "╭Task 27─────────────────────────────────────────╮", "│ │", "│Name Value │", "│------------- ----------------------------------│", "│ID 27 │", "╰────────────────────────────────────────────────╯", "╭Filter Tasks────────────────────────────────────╮", "│(status:pending or status:waiting) │", "╰────────────────────────────────────────────────╯", ]); for i in 1..=4 { // Task expected[(i, 0)].set_style(Style::default().add_modifier(Modifier::BOLD)); } for i in 6..=13 { // Calendar expected[(i, 0)].set_style(Style::default().add_modifier(Modifier::DIM)); } for r in &[ 1..=4, // ID 6..=8, // Age 10..=13, // Deps 15..=15, // P 17..=22, // Projec 24..=30, // Tag 32..=34, // Due 36..=42, // Descr 44..=48, // Urg ] { for i in r.clone() { expected[(i, 1)].set_style(Style::default().add_modifier(Modifier::UNDERLINED)); } } for i in 1..expected.area().width - 1 { expected[(i, 3)].set_style(Style::default().fg(Color::Indexed(1)).bg(Color::Reset).add_modifier(Modifier::BOLD)); } for i in 1..expected.area().width - 1 { expected[(i, 4)].set_style(Style::default().fg(Color::Indexed(1)).bg(Color::Indexed(4))); } let mut app = TaskwarriorTui::new("next", false).await.unwrap(); app.task_info_location_override = Some(TaskInfoLocation::Bottom); app.task_report_next(); app.context_next(); let total_tasks: u64 = 26; assert!(app.update(true).await.is_ok()); assert_eq!(app.tasks.len(), total_tasks as usize); assert_eq!(app.current_context_filter, ""); let now = Local::now(); let now = TimeZone::from_utc_datetime(now.offset(), &now.naive_utc()); let mut command = std::process::Command::new(&task_exe()); command.arg("add"); let message = "'new task 1 for testing draw' priority:U"; let shell = message.replace("'", "\\'"); let cmd = shlex::split(&shell).unwrap(); for s in cmd { command.arg(&s); } let output = command.output().unwrap(); let s = String::from_utf8_lossy(&output.stdout); let re = Regex::new(r"^Created task (?P\d+).\n$").unwrap(); let caps = re.captures(&s).unwrap(); let task_id = caps["task_id"].parse::().unwrap(); assert_eq!(task_id, total_tasks + 1); let mut command = std::process::Command::new(&task_exe()); command.arg("add"); let message = "'new task 2 for testing draw' priority:U +none"; let shell = message.replace("'", "\\'"); let cmd = shlex::split(&shell).unwrap(); for s in cmd { command.arg(&s); } let output = command.output().unwrap(); let s = String::from_utf8_lossy(&output.stdout); let re = Regex::new(r"^Created task (?P\d+).\n$").unwrap(); let caps = re.captures(&s).unwrap(); let task_id = caps["task_id"].parse::().unwrap(); assert_eq!(task_id, total_tasks + 2); app.task_report_next(); app.task_report_previous(); app.task_report_next_page(); app.task_report_previous_page(); app.task_report_bottom(); app.task_report_top(); app.update(true).await.unwrap(); let backend = TestBackend::new(50, 15); let mut terminal = Terminal::new(backend).unwrap(); app.task_report_info_show = !app.task_report_info_show; terminal .draw(|f| { app.draw(f); app.draw(f); }) .unwrap(); app.task_report_info_show = !app.task_report_info_show; terminal .draw(|f| { app.draw(f); app.draw(f); }) .unwrap(); let output = std::process::Command::new(&task_exe()) .arg("rc.confirmation=off") .arg("undo") .output() .unwrap(); let output = std::process::Command::new(&task_exe()) .arg("rc.confirmation=off") .arg("undo") .output() .unwrap(); assert_eq!(terminal.backend().size().unwrap(), expected.area.into()); terminal.backend().assert_buffer(&expected); } async fn test_draw_calendar() { let mut expected = Buffer::with_lines(vec![ " Tasks Projects Timesheet Calendanext [none]", " ", " 2020 ", " ", " January February ", " Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa ", " 1 2 3 4 1 ", " 5 6 7 8 9 10 11 2 3 4 5 6 7 8 ", " 12 13 14 15 16 17 18 9 10 11 12 13 14 15 ", " 19 20 21 22 23 24 25 16 17 18 19 20 21 22 ", " 26 27 28 29 30 31 23 24 25 26 27 28 29 ", " ", " ", " ", " ", ]); for i in 0..=49 { // First line expected[(i, 0)].set_style(Style::default().add_modifier(Modifier::REVERSED)); } for i in 32..=38 { // Calendar expected[(i, 0)].set_style(Style::default().add_modifier(Modifier::BOLD).add_modifier(Modifier::REVERSED)); } for i in 0..=49 { expected[(i, 2)].set_style(Style::default().add_modifier(Modifier::UNDERLINED)); } for i in 3..=22 { expected[(i, 4)].set_style(Style::default().bg(Color::Reset)); } for i in 25..=44 { expected[(i, 4)].set_style(Style::default().bg(Color::Reset)); } for i in 3..=22 { expected[(i, 5)].set_style(Style::default().bg(Color::Reset).add_modifier(Modifier::UNDERLINED)); } for i in 25..=44 { expected[(i, 5)].set_style(Style::default().bg(Color::Reset).add_modifier(Modifier::UNDERLINED)); } let mut app = TaskwarriorTui::new("next", false).await.unwrap(); app.task_report_next(); app.context_next(); app.update(true).await.unwrap(); app.calendar_year = 2020; app.mode = Mode::Calendar; app.update(true).await.unwrap(); let backend = TestBackend::new(50, 15); let mut terminal = Terminal::new(backend).unwrap(); terminal .draw(|f| { app.draw(f); app.draw(f); }) .unwrap(); assert_eq!(terminal.backend().size().unwrap(), expected.area.into()); terminal.backend().assert_buffer(&expected); } async fn test_draw_help_popup() { let mut expected = Buffer::with_lines(vec![ "╭Help──────────────────────────────────╮", "│# Keybindings │", "│ │", "│Keybindings: │", "│ │", "│ Esc: │", "│ │", "│ ]: Next view │", "│ │", "│ [: Previous view │", "╰──────────────────────────────────────╯", " 7% ───────────────────────────────────", ]); for i in 1..=4 { // Calendar expected[(i, 0)].set_style(Style::default().add_modifier(Modifier::BOLD)); } expected[(3, 11)].set_style(Style::default().fg(Color::Reset)); expected[(4, 11)].set_style(Style::default().fg(Color::Reset)); expected[(5, 11)].set_style(Style::default().fg(Color::Gray)); expected[(6, 11)].set_style(Style::default().fg(Color::Gray)); let mut app = TaskwarriorTui::new("next", false).await.unwrap(); app.mode = Mode::Tasks(Action::HelpPopup); app.task_report_next(); app.context_next(); app.update(true).await.unwrap(); let backend = TestBackend::new(40, 12); let mut terminal = Terminal::new(backend).unwrap(); terminal .draw(|f| { app.draw_help_popup(f, 100, 100); }) .unwrap(); assert_eq!(terminal.backend().size().unwrap(), expected.area.into()); terminal.backend().assert_buffer(&expected); } async fn test_draw_timesheet_styles_week_headers_with_actions() { let mut app = TaskwarriorTui::new("next", false).await.unwrap(); let header_style = Style::default().add_modifier(Modifier::BOLD); let active_style = Style::default().fg(Color::Indexed(2)); let completed_style = Style::default().fg(Color::Indexed(1)); let alternate_style = Style::default().bg(Color::Indexed(4)); let footnote_style = Style::default().fg(Color::Indexed(3)); app.config.color.insert("color.label".to_string(), header_style); app.config.color.insert("color.active".to_string(), active_style); app.config.color.insert("color.completed".to_string(), completed_style); app.config.color.insert("color.alternate".to_string(), alternate_style); app.config.color.insert("color.footnote".to_string(), footnote_style); app.timesheet_data = [ "Wk Date Day ID Action Project Due Task", "--- ---------- --- -------- --------- ------- --- ----------------", "W14 2026-04-05 Sun 1 Started first-week-started", " first-week continuation", "", "W15 2026-04-12 Sun 2 Started second-week-started", " fa6b0fe3 Completed second-week-completed", "2 completed, 2 started.", ] .join("\n"); app.timesheet_line_count = app.timesheet_data.lines().count() as u16; let lines = app.styled_timesheet_lines(); assert_eq!(lines[2].style.fg, active_style.fg); assert_eq!(lines[3].style.fg, active_style.fg); assert_eq!(lines[5].style.fg, active_style.fg); assert_eq!(lines[5].style.bg, alternate_style.bg); assert_eq!(lines[6].style.fg, completed_style.fg); assert_eq!(lines[6].style.bg, alternate_style.bg); assert_eq!(lines[7].style.fg, footnote_style.fg); } async fn test_update_timesheet_uses_current_week_events() { let started_description = "timesheet-started"; let completed_description = "timesheet-completed"; let now = Local::now(); let started_entry = now - chrono::Duration::minutes(4); let started_start = now - chrono::Duration::minutes(3); let completed_entry = now - chrono::Duration::minutes(2); let completed_end = now - chrono::Duration::minutes(1); fn task_datetime_arg(dt: DateTime) -> String { dt.format("%Y-%m-%dT%H:%M:%S%:z").to_string() } fn created_task_id(output: &std::process::Output) -> u64 { let stdout = String::from_utf8_lossy(&output.stdout); let re = Regex::new(r"Created task (?P\d+)\.").unwrap(); let caps = re.captures(&stdout).unwrap_or_else(|| panic!("unexpected `task add` output: {stdout}")); caps["task_id"].parse::().unwrap() } fn undo_history(times: usize) { for _ in 0..times { std::process::Command::new(task_exe()) .arg("rc.confirmation=off") .arg("undo") .output() .unwrap(); } } struct UndoGuard { undo_count: usize, } impl UndoGuard { fn new() -> Self { Self { undo_count: 0 } } fn push(&mut self) { self.undo_count += 1; } } impl Drop for UndoGuard { fn drop(&mut self) { undo_history(self.undo_count); } } let mut undo_guard = UndoGuard::new(); let started_output = std::process::Command::new(task_exe()) .arg("add") .arg(format!("entry:{}", task_datetime_arg(started_entry))) .arg(format!("start:{}", task_datetime_arg(started_start))) .arg(started_description) .output() .unwrap(); assert!(started_output.status.success(), "{:?}", started_output); let started_task_id = created_task_id(&started_output); undo_guard.push(); let completed_output = std::process::Command::new(task_exe()) .arg("add") .arg(format!("entry:{}", task_datetime_arg(completed_entry))) .arg(completed_description) .output() .unwrap(); assert!(completed_output.status.success(), "{:?}", completed_output); let completed_task_id = created_task_id(&completed_output); undo_guard.push(); let modify_output = std::process::Command::new(task_exe()) .arg("rc.confirmation=off") .arg(completed_task_id.to_string()) .arg("modify") .arg("status:completed") .arg(format!("entry:{}", task_datetime_arg(completed_entry))) .arg(format!("end:{}", task_datetime_arg(completed_end))) .output() .unwrap(); assert!(modify_output.status.success(), "{:?}", modify_output); undo_guard.push(); let mut app = TaskwarriorTui::new("next", false).await.unwrap(); app.terminal_width = 80; app.terminal_height = 10; app.update_timesheet().unwrap(); assert!( app.timesheet_data.contains("Wk Date Day ID Action"), "{}", app.timesheet_data ); assert!(app.timesheet_data.contains(&started_task_id.to_string()), "{}", app.timesheet_data); assert!(app.timesheet_data.contains("Started"), "{}", app.timesheet_data); assert!(app.timesheet_data.contains(started_description), "{}", app.timesheet_data); assert!(app.timesheet_data.contains("Completed"), "{}", app.timesheet_data); assert!(app.timesheet_data.contains(completed_description), "{}", app.timesheet_data); assert!(app.timesheet_line_count >= 3); assert_eq!(app.timesheet_scroll, 0); let backend = TestBackend::new(80, 10); let mut terminal = Terminal::new(backend).unwrap(); terminal .draw(|f| { app.draw_timesheet(f, f.area()); }) .unwrap(); let view = buffer_view(terminal.backend().buffer()); assert!(view.contains(started_description), "{view}"); assert!(view.contains(completed_description), "{view}"); } // #[test] async fn test_draw_context_menu() { let mut expected = Buffer::with_lines(vec![ "╭Context───────────────────────────────────────────────────────────────────────╮", "│Name Description Active│", "│ │", "│• none yes │", "│ finance +finance -private no │", "│ personal +personal -private no │", "│ work -personal -private no │", "│ │", "│ │", "╰──────────────────────────────────────────────────────────────────────────────╯", ]); for i in 1..=7 { // Task expected[(i, 0)].set_style(Style::default().add_modifier(Modifier::BOLD)); } for i in 1..=10 { // Task expected[(i, 1)].set_style(Style::default().add_modifier(Modifier::UNDERLINED)); } for i in 12..=71 { // Task expected[(i, 1)].set_style(Style::default().add_modifier(Modifier::UNDERLINED)); } for i in 73..=78 { // Task expected[(i, 1)].set_style(Style::default().add_modifier(Modifier::UNDERLINED)); } for i in 1..=78 { // Task expected[(i, 3)].set_style(Style::default().add_modifier(Modifier::BOLD)); } let mut app = TaskwarriorTui::new("next", false).await.unwrap(); app.mode = Mode::Tasks(Action::ContextMenu); app.task_report_next(); app.update(true).await.unwrap(); let backend = TestBackend::new(80, 10); let mut terminal = Terminal::new(backend).unwrap(); terminal .draw(|f| { app.draw_context_menu(f, 100, 100); app.draw_context_menu(f, 100, 100); }) .unwrap(); assert_eq!(terminal.backend().size().unwrap(), expected.area.into()); terminal.backend().assert_buffer(&expected); } // #[test] async fn test_graphemes() { dbg!("写作业".graphemes(true).count()); dbg!(UnicodeWidthStr::width("写作业")); dbg!(UnicodeWidthStr::width("abc")); let mut app = TaskwarriorTui::new("next", false).await.unwrap(); if let Some(task) = app.task_by_id(27) { let i = app.task_index_by_uuid(*task.uuid()).unwrap_or_default(); app.current_selection = i; app.current_selection_id = None; app.current_selection_uuid = None; } app.update(true).await.unwrap(); app.mode = Mode::Tasks(Action::Modify); match app.task_current() { Some(t) => { let s = format!("{} ", t.description()); app.modify.update(&s, s.as_str().len(), &mut app.changes) } None => app.modify.update("", 0, &mut app.changes), } app.update(true).await.unwrap(); dbg!(app.modify.as_str()); dbg!(app.modify.as_str().len()); dbg!(app.modify.graphemes(true).count()); dbg!(app.modify.pos()); let position = TaskwarriorTui::get_position(&app.modify); dbg!(position); } // #[test] async fn test_taskwarrior_tui_completion() { let mut app = TaskwarriorTui::new("next", false).await.unwrap(); app.handle_input(KeyCode::Char('z')).await.unwrap(); app.mode = Mode::Tasks(Action::Add); app.update_completion_list(); let input = "Wash car"; for c in input.chars() { app.handle_input(KeyCode::Char(c)).await.unwrap(); } app.handle_input(KeyCode::Ctrl('e')).await.unwrap(); let input = " project:CO"; for c in input.chars() { app.handle_input(KeyCode::Char(c)).await.unwrap(); } app.mode = Mode::Tasks(Action::Add); app.update_completion_list(); app.handle_input(KeyCode::Tab).await.unwrap(); app.handle_input(KeyCode::Char('\n')).await.unwrap(); let backend = TestBackend::new(80, 50); let mut terminal = Terminal::new(backend).unwrap(); terminal .draw(|f| { app.draw(f); app.draw(f); }) .unwrap(); println!("{}", buffer_view(terminal.backend().buffer())); } } taskwarrior-tui-0.27.0/src/calendar.rs000064400000000000000000000167771046102023000160110ustar 00000000000000// Based on https://gist.github.com/diwic/5c20a283ca3a03752e1a27b0f3ebfa30 // See https://old.reddit.com/r/rust/comments/4xneq5/the_calendar_example_challenge_ii_why_eddyb_all/ use std::fmt; const COL_WIDTH: usize = 21; use std::cmp::min; use chrono::{DateTime, Datelike, Duration, FixedOffset, Local, Month, NaiveDate, NaiveDateTime, TimeZone, format::Fixed}; use ratatui::{ buffer::Buffer, layout::{Position, Rect}, style::{Color, Modifier, Style}, symbols, widgets::{Block, Widget}, }; #[derive(Debug, Clone)] pub struct Calendar<'a> { pub block: Option>, pub year: i32, pub month: u32, pub style: Style, pub months_per_row: usize, pub date_style: Vec<(NaiveDate, Style)>, pub today_style: Style, pub start_on_monday: bool, pub title_background_color: Color, } impl<'a> Default for Calendar<'a> { fn default() -> Calendar<'a> { let year = Local::now().year(); let month = Local::now().month(); Calendar { block: None, style: Style::default(), months_per_row: 0, year, month, date_style: vec![], today_style: Style::default(), start_on_monday: false, title_background_color: Color::Reset, } } } impl<'a> Calendar<'a> { pub fn block(mut self, block: Block<'a>) -> Self { self.block = Some(block); self } pub fn style(mut self, style: Style) -> Self { self.style = style; self } pub fn year(mut self, year: i32) -> Self { self.year = year; if self.year < 0 { self.year = 0; } self } pub fn month(mut self, month: u32) -> Self { self.month = month; self } pub fn date_style(mut self, date_style: Vec<(NaiveDate, Style)>) -> Self { self.date_style = date_style; self } pub fn today_style(mut self, today_style: Style) -> Self { self.today_style = today_style; self } pub fn months_per_row(mut self, months_per_row: usize) -> Self { self.months_per_row = months_per_row; self } pub fn start_on_monday(mut self, start_on_monday: bool) -> Self { self.start_on_monday = start_on_monday; self } } impl Widget for Calendar<'_> { fn render(mut self, area: Rect, buf: &mut Buffer) { let month_names = Self::generate_month_names(); buf.set_style(area, self.style); let area = match self.block.take() { Some(b) => { let inner_area = b.inner(area); b.render(area, buf); inner_area } None => area, }; if area.height < 7 { return; } // Only write to the buffer when the position is inside the allocated area. let set_string = |buf: &mut Buffer, x: u16, y: u16, s: &str, style: Style| { if area.contains(Position { x, y }) { buf.set_string(x, y, s, style); } }; let style = self.style; let today = Local::now(); let year = self.year; let month = self.month; let months: Vec<_> = (0..12).collect(); let mut days: Vec<(NaiveDate, NaiveDate)> = months .iter() .map(|i| { let first = NaiveDate::from_ymd_opt(year, i + 1, 1).unwrap(); let num_days = if self.start_on_monday { first.weekday().num_days_from_monday() } else { first.weekday().num_days_from_sunday() }; (first, first - Duration::days(i64::from(num_days))) }) .collect(); let mut start_m = 0_usize; if self.months_per_row > area.width as usize / 8 / 3 || self.months_per_row == 0 { self.months_per_row = area.width as usize / 8 / 3; } let mut y = area.y; y += 1; let x = area.x; let s = format!("{year:^width$}", year = year, width = area.width as usize); let mut new_year = 0; let style = Style::default().add_modifier(Modifier::UNDERLINED); if self.year + new_year as i32 == today.year() { set_string(buf, x, y, &s, self.today_style.add_modifier(Modifier::UNDERLINED)); } else { set_string(buf, x, y, &s, style); } let start_x = (area.width - 3 * 7 * self.months_per_row as u16 - self.months_per_row as u16) / 2; y += 2; loop { let endm = std::cmp::min(start_m + self.months_per_row, 12); let mut x = area.x + start_x; for (c, d) in days.iter_mut().enumerate().take(endm).skip(start_m) { if c > start_m { x += 1; } let m = d.0.month() as usize; let s = format!("{:^20}", month_names[m - 1]); let style = Style::default().bg(self.title_background_color); if m == today.month() as usize && self.year + new_year as i32 == today.year() { set_string(buf, x, y, &s, self.today_style); } else { set_string(buf, x, y, &s, style); } x += s.len() as u16 + 1; } y += 1; let mut x = area.x + start_x; for d in days.iter_mut().take(endm).skip(start_m) { let m = d.0.month() as usize; let style = Style::default().bg(self.title_background_color); let days_string = if self.start_on_monday { "Mo Tu We Th Fr Sa Su" } else { "Su Mo Tu We Th Fr Sa" }; set_string(buf, x, y, days_string, style.add_modifier(Modifier::UNDERLINED)); x += 21 + 1; } y += 1; loop { let mut moredays = false; let mut x = area.x + start_x; for c in start_m..endm { if c > start_m { x += 1; } let d = &mut days[c + new_year * 12]; for _ in 0..7 { let s = if d.0.month() == d.1.month() { format!("{:>2}", d.1.day()) } else { " ".to_string() }; let mut style = Style::default(); let index = self.date_style.iter().position(|(date, style)| d.1 == *date); if let Some(i) = index { style = self.date_style[i].1; } if d.1 == Local::now().date_naive() { set_string(buf, x, y, &s, self.today_style); } else { set_string(buf, x, y, &s, style); } x += 3; d.1 += Duration::days(1); } moredays |= d.0.month() == d.1.month() || d.1 < d.0; } y += 1; if !moredays { break; } } start_m += self.months_per_row; y += 2; if y + 8 > area.y + area.height { break; } else if start_m >= 12 { start_m = 0; new_year += 1; days.append( &mut months .iter() .map(|i| { let first = NaiveDate::from_ymd_opt(self.year + new_year as i32, i + 1, 1).unwrap(); (first, first - Duration::days(i64::from(first.weekday().num_days_from_sunday()))) }) .collect::>(), ); let x = area.x; let s = format!("{year:^width$}", year = self.year as usize + new_year, width = area.width as usize); let mut style = Style::default().add_modifier(Modifier::UNDERLINED); if self.year + new_year as i32 == today.year() { style = style.add_modifier(Modifier::BOLD); } set_string(buf, x, y, &s, style); y += 1; } y += 1; } } } impl<'a> Calendar<'a> { fn generate_month_names() -> [&'a str; 12] { [ Month::January.name(), Month::February.name(), Month::March.name(), Month::April.name(), Month::May.name(), Month::June.name(), Month::July.name(), Month::August.name(), Month::September.name(), Month::October.name(), Month::November.name(), Month::December.name(), ] } } taskwarrior-tui-0.27.0/src/cli.rs000064400000000000000000000026721046102023000147740ustar 00000000000000use clap::Arg; const APP_VERSION: &str = env!("CARGO_PKG_VERSION"); const APP_NAME: &str = env!("CARGO_PKG_NAME"); pub fn generate_cli_app() -> clap::Command { let mut app = clap::Command::new(APP_NAME) .version(APP_VERSION) .author("Dheepak Krishnamurthy <@kdheepak>") .about("A taskwarrior terminal user interface") .arg( Arg::new("data") .short('d') .long("data") .value_name("FOLDER") .help("Sets the data folder for taskwarrior-tui") .action(clap::ArgAction::Set), ) .arg( Arg::new("config") .short('c') .long("config") .value_name("FOLDER") .help("Sets the config folder for taskwarrior-tui (currently not used)") .action(clap::ArgAction::Set), ) .arg( Arg::new("taskdata") .long("taskdata") .value_name("FOLDER") .help("Sets the .task folder using the TASKDATA environment variable for taskwarrior") .action(clap::ArgAction::Set), ) .arg( Arg::new("taskrc") .long("taskrc") .value_name("FILE") .help("Sets the .taskrc file using the TASKRC environment variable for taskwarrior") .action(clap::ArgAction::Set), ) .arg( Arg::new("report") .short('r') .long("report") .value_name("STRING") .help("Sets default report") .action(clap::ArgAction::Set), ); app.set_bin_name(APP_NAME); app } taskwarrior-tui-0.27.0/src/completion.rs000064400000000000000000000157631046102023000164030ustar 00000000000000use std::{error::Error, io}; use log::{Level, LevelFilter, debug, error, info, log_enabled, trace, warn}; use ratatui::{ Terminal, layout::{Constraint, Direction, Layout}, style::{Color, Modifier, Style}, text::{Line, Span}, widgets::{Block, Borders, List, ListItem, ListState}, }; use rustyline::{ Context, error::ReadlineError, highlight::{Highlighter, MatchingBracketHighlighter}, hint::Hinter, history::FileHistory, line_buffer::LineBuffer, }; use unicode_segmentation::{Graphemes, UnicodeSegmentation}; use unicode_width::UnicodeWidthStr; pub fn get_start_word_under_cursor(line: &str, cursor_pos: usize) -> usize { let mut chars = line[..cursor_pos].chars(); let mut res = cursor_pos; while let Some(c) = chars.next_back() { if c == ' ' || c == '(' || c == ')' { break; } res -= c.len_utf8(); } // if iter == None, res == 0. res } pub struct TaskwarriorTuiCompletionHelper { pub candidates: Vec<(String, String)>, pub context: String, pub input: String, } type Completion = (String, String, String, String, String); impl TaskwarriorTuiCompletionHelper { fn complete(&self, word: &str, pos: usize, _ctx: &Context) -> rustyline::Result<(usize, Vec)> { let candidates: Vec = self .candidates .iter() .filter_map(|(context, candidate)| { if context == &self.context && (candidate.starts_with(&word[..pos]) || candidate.to_lowercase().starts_with(&word[..pos].to_lowercase())) && (!self.input.contains(candidate) || !self.input.to_lowercase().contains(&candidate.to_lowercase())) { Some(( candidate.clone(), // display candidate.to_string(), // replacement word[..pos].to_string(), // original candidate[..pos].to_string(), candidate[pos..].to_string(), )) } else { None } }) .collect(); Ok((pos, candidates)) } } pub struct CompletionList { pub state: ListState, pub current: String, pub pos: usize, pub helper: TaskwarriorTuiCompletionHelper, } impl CompletionList { pub fn new() -> CompletionList { CompletionList { state: ListState::default(), current: String::new(), pos: 0, helper: TaskwarriorTuiCompletionHelper { candidates: vec![], context: String::new(), input: String::new(), }, } } pub fn with_items(items: Vec<(String, String)>) -> CompletionList { let mut candidates = vec![]; for i in items { if !candidates.contains(&i) { candidates.push(i); } } let context = String::new(); let input = String::new(); CompletionList { state: ListState::default(), current: String::new(), pos: 0, helper: TaskwarriorTuiCompletionHelper { candidates, context, input }, } } pub fn insert(&mut self, item: (String, String)) { if !self.helper.candidates.contains(&item) { self.helper.candidates.push(item); } } pub fn next(&mut self) { let i = match self.state.selected() { Some(i) => { if i >= self.candidates().len() - 1 { 0 } else { i + 1 } } None => 0, }; self.state.select(Some(i)); } pub fn previous(&mut self) { let i = match self.state.selected() { Some(i) => { if i == 0 { self.candidates().len() - 1 } else { i - 1 } } None => 0, }; self.state.select(Some(i)); } pub fn unselect(&mut self) { self.state.select(None); } pub fn clear(&mut self) { self.helper.candidates.clear(); self.state.select(None); } pub fn len(&self) -> usize { self.candidates().len() } pub fn max_width(&self) -> Option { self.candidates().iter().map(|p| p.1.width() + 4).max() } pub fn get(&self, i: usize) -> Option { let candidates = self.candidates(); if i < candidates.len() { Some(candidates[i].clone()) } else { None } } pub fn selected(&self) -> Option<(usize, Completion)> { self.state.selected().and_then(|i| self.get(i)).map(|s| (self.pos, s)) } pub fn is_empty(&self) -> bool { self.candidates().is_empty() } /// Returns the ghost text suffix when there is exactly one completion candidate /// and the suffix is non-empty (i.e., there is something left to complete). /// The returned tuple is `(replacement, original, suffix)` where: /// - `replacement` is the full candidate string /// - `original` is the already-typed prefix /// - `suffix` is the remaining text to show as ghost text pub fn ghost_text(&self) -> Option { let candidates = self.candidates(); if candidates.len() == 1 { let c = &candidates[0]; let suffix = &c.4; if suffix.is_empty() { None } else { Some(suffix.clone()) } } else { None } } pub fn candidates(&self) -> Vec { let hist = FileHistory::new(); let ctx = rustyline::Context::new(&hist); let (pos, candidates) = self.helper.complete(&self.current, self.pos, &ctx).unwrap(); candidates } pub fn input(&mut self, current: String, i: String) { self.helper.input = i; if current.contains('.') && current.contains(':') { self.current = current.split_once(':').unwrap().1.to_string(); self.helper.context = current.split_once('.').unwrap().0.to_string(); } else if current.contains('.') { self.current = format!(".{}", current.split_once('.').unwrap().1); self.helper.context = "modifier".to_string(); } else if current.contains(':') { self.current = current.split_once(':').unwrap().1.to_string(); self.helper.context = current.split_once(':').unwrap().0.to_string(); } else if current.contains('+') { self.current = format!("+{}", current.split_once('+').unwrap().1); self.helper.context = "+".to_string(); } else { self.current = current; self.helper.context = "attribute".to_string(); } self.pos = self.current.len(); } } #[cfg(test)] mod tests { use super::CompletionList; #[test] fn attribute_context_completion_includes_tag_prefix() { let mut completion_list = CompletionList::with_items(vec![("attribute".to_string(), "tag:".to_string())]); completion_list.input("ta".to_string(), String::new()); completion_list.next(); let (_, (replacement, _, original, _, _)) = completion_list.selected().unwrap(); let completed = format!("{}{}{}", "ta".trim_end_matches(&original), replacement, ""); assert_eq!(completed, "tag:"); } #[test] fn tag_context_completion_keeps_existing_prefix() { let mut completion_list = CompletionList::with_items(vec![("tag".to_string(), "home".to_string())]); completion_list.input("tag:".to_string(), String::new()); completion_list.next(); let (_, (replacement, _, original, _, _)) = completion_list.selected().unwrap(); let completed = format!("{}{}{}", "tag:".trim_end_matches(&original), replacement, ""); assert_eq!(completed, "tag:home"); } } taskwarrior-tui-0.27.0/src/config.rs000064400000000000000000001435031046102023000154710ustar 00000000000000use std::{collections::HashMap, error::Error, str}; use anyhow::{Context, Result}; use ratatui::{ style::{Color, Modifier, Style}, symbols::{bar::FULL, line::DOUBLE_VERTICAL}, }; trait TaskWarriorBool { fn get_bool(&self) -> Option; } impl TaskWarriorBool for String { fn get_bool(&self) -> Option { if self == "true" || self == "1" || self == "y" || self == "yes" || self == "on" { Some(true) } else if self == "false" || self == "0" || self == "n" || self == "no" || self == "off" { Some(false) } else { None } } } impl TaskWarriorBool for str { fn get_bool(&self) -> Option { if self == "true" || self == "1" || self == "y" || self == "yes" || self == "on" { Some(true) } else if self == "false" || self == "0" || self == "n" || self == "no" || self == "off" { Some(false) } else { None } } } #[derive(Debug)] pub struct Uda { label: String, kind: String, values: Option>, default: Option, urgency: Option, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum TaskInfoLocation { #[default] Auto, Bottom, Right, } impl TaskInfoLocation { pub const AUTO_WIDTH_THRESHOLD: u16 = 160; fn from_config_value(value: &str) -> Option { let value = value.trim(); if value.eq_ignore_ascii_case("auto") { Some(Self::Auto) } else if value.eq_ignore_ascii_case("bottom") { Some(Self::Bottom) } else if value.eq_ignore_ascii_case("right") { Some(Self::Right) } else { None } } pub fn resolve(self, width: u16) -> Self { match self { Self::Auto if width <= Self::AUTO_WIDTH_THRESHOLD => Self::Bottom, Self::Auto => Self::Right, value => value, } } } #[derive(Debug)] pub struct Config { pub enabled: bool, pub color: HashMap, pub color_keywords: Vec<(String, Style)>, pub filter: String, pub data_location: String, pub obfuscate: bool, pub print_empty_columns: bool, pub due: usize, pub weekstart: bool, pub rule_precedence_color: Vec, pub uda_priority_values: Vec, pub uda_tick_rate: u64, pub uda_auto_insert_double_quotes_on_add: bool, pub uda_auto_insert_double_quotes_on_annotate: bool, pub uda_auto_insert_double_quotes_on_log: bool, pub uda_prefill_task_metadata: bool, pub uda_reset_filter_on_esc: bool, pub uda_task_detail_prefetch: usize, pub uda_task_report_use_all_tasks_for_completion: bool, pub uda_task_report_use_alternate_style: bool, pub uda_task_report_info_show: bool, pub uda_task_report_looping: bool, pub uda_task_report_jump_to_task_on_add: bool, pub uda_selection_indicator: String, pub uda_mark_highlight_indicator: String, pub uda_unmark_highlight_indicator: String, pub uda_mark_indicator: String, pub uda_unmark_indicator: String, pub uda_scrollbar_indicator: String, pub uda_scrollbar_area: String, pub uda_style_report_scrollbar: Style, pub uda_style_report_scrollbar_area: Style, pub uda_selection_bold: bool, pub uda_selection_italic: bool, pub uda_selection_dim: bool, pub uda_selection_blink: bool, pub uda_selection_reverse: bool, pub uda_calendar_months_per_row: usize, pub uda_style_context_active: Style, pub uda_style_report_menu_active: Style, pub uda_style_report_selection: Style, pub uda_style_calendar_title: Style, pub uda_style_calendar_today: Style, pub uda_style_navbar: Style, pub uda_style_command: Style, pub uda_style_report_completion_pane: Style, pub uda_style_report_completion_pane_highlight: Style, pub uda_style_title: Style, pub uda_style_title_border: Style, pub uda_style_help_gauge: Style, pub uda_style_command_error: Style, pub uda_shortcuts: Vec, pub uda_change_focus_rotate: bool, pub uda_background_process: String, pub uda_background_process_period: usize, pub uda_quick_tag_name: String, pub uda_task_report_info_location: TaskInfoLocation, pub uda_task_report_prompt_on_undo: bool, pub uda_task_report_prompt_on_delete: bool, pub uda_task_report_prompt_on_done: bool, pub uda_task_report_date_time_vague_more_precise: bool, pub uda_context_menu_select_on_move: bool, pub uda_context_menu_close_on_select: bool, pub uda_report_menu_select_on_move: bool, pub uda_report_menu_close_on_select: bool, pub uda: Vec, } impl Config { pub fn new(data: &str, report: &str) -> Result { let bool_collection = Self::get_bool_collection(); let enabled = true; let obfuscate = bool_collection.get("obfuscate").copied().unwrap_or(false); let print_empty_columns = bool_collection.get("print_empty_columns").copied().unwrap_or(false); let color = Self::get_color_collection(data); let color_keywords: Vec<(String, Style)> = color .iter() .filter_map(|(key, style)| key.strip_prefix("color.keyword.").map(|kw| (kw.to_string(), *style))) .collect(); let filter = Self::get_filter(data, report)?; let filter = if filter.trim_start().trim_end().is_empty() { filter } else { format!("{} ", filter) }; let data_location = Self::get_data_location(data); let due = Self::get_due(data); let weekstart = Self::get_weekstart(data); let rule_precedence_color = Self::get_rule_precedence_color(data); let uda_priority_values = Self::get_uda_priority_values(data); let uda_tick_rate = Self::get_uda_tick_rate(data); let uda_change_focus_rotate = Self::get_uda_change_focus_rotate(data); let uda_auto_insert_double_quotes_on_add = Self::get_uda_auto_insert_double_quotes_on_add(data); let uda_auto_insert_double_quotes_on_annotate = Self::get_uda_auto_insert_double_quotes_on_annotate(data); let uda_auto_insert_double_quotes_on_log = Self::get_uda_auto_insert_double_quotes_on_log(data); let uda_prefill_task_metadata = Self::get_uda_prefill_task_metadata(data); let uda_reset_filter_on_esc = Self::get_uda_reset_filter_on_esc(data); let uda_task_detail_prefetch = Self::get_uda_task_detail_prefetch(data); let uda_task_report_use_all_tasks_for_completion = Self::get_uda_task_report_use_all_tasks_for_completion(data); let uda_task_report_use_alternate_style = Self::get_uda_task_report_use_alternate_style(data); let uda_task_report_info_show = Self::get_uda_task_report_info_show(data); let uda_task_report_looping = Self::get_uda_task_report_looping(data); let uda_task_report_jump_to_task_on_add = Self::get_uda_task_report_jump_to_task_on_add(data); let uda_selection_indicator = Self::get_uda_selection_indicator(data); let uda_mark_highlight_indicator = Self::get_uda_mark_highlight_indicator(data); let uda_unmark_highlight_indicator = Self::get_uda_unmark_highlight_indicator(data); let uda_mark_indicator = Self::get_uda_mark_indicator(data); let uda_unmark_indicator = Self::get_uda_unmark_indicator(data); let uda_scrollbar_indicator = Self::get_uda_scrollbar_indicator(data); let uda_scrollbar_area = Self::get_uda_scrollbar_area(data); let uda_selection_bold = Self::get_uda_selection_bold(data); let uda_selection_italic = Self::get_uda_selection_italic(data); let uda_selection_dim = Self::get_uda_selection_dim(data); let uda_selection_blink = Self::get_uda_selection_blink(data); let uda_selection_reverse = Self::get_uda_selection_reverse(data); let uda_calendar_months_per_row = Self::get_uda_months_per_row(data); let uda_style_report_selection = Self::get_uda_style("report.selection", data); let uda_style_report_scrollbar = Self::get_uda_style("report.scrollbar", data); let uda_style_report_scrollbar_area = Self::get_uda_style("report.scrollbar.area", data); let uda_style_calendar_title = Self::get_uda_style("calendar.title", data); let uda_style_calendar_today = Self::get_uda_style("calendar.today", data); let uda_style_navbar = Self::get_uda_style("navbar", data); let uda_style_command = Self::get_uda_style("command", data); let uda_style_context_active = Self::get_uda_style("context.active", data); let uda_style_report_menu_active = Self::get_uda_style("report-menu.active", data); let uda_style_report_completion_pane = Self::get_uda_style("report.completion-pane", data); let uda_style_report_completion_pane_highlight = Self::get_uda_style("report.completion-pane-highlight", data); let uda_style_title = Self::get_uda_style("title", data); let uda_style_title_border = Self::get_uda_style("title.border", data); let uda_style_help_gauge = Self::get_uda_style("help.gauge", data); let uda_style_command_error = Self::get_uda_style("command.error", data); let uda_shortcuts = Self::get_uda_shortcuts(data); let uda_background_process = Self::get_uda_background_process(data); let uda_background_process_period = Self::get_uda_background_process_period(data); let uda_style_report_selection = uda_style_report_selection.unwrap_or_default(); let uda_style_report_scrollbar = uda_style_report_scrollbar.unwrap_or_else(|| Style::default().fg(Color::Black)); let uda_style_report_scrollbar_area = uda_style_report_scrollbar_area.unwrap_or_default(); let uda_style_calendar_title = uda_style_calendar_title.unwrap_or_default(); let uda_style_calendar_today = uda_style_calendar_today.unwrap_or_else(|| Style::default().add_modifier(Modifier::BOLD)); let uda_style_navbar = uda_style_navbar.unwrap_or_else(|| Style::default().add_modifier(Modifier::REVERSED)); let uda_style_command = uda_style_command.unwrap_or_else(|| Style::default().add_modifier(Modifier::REVERSED)); let uda_style_context_active = uda_style_context_active.unwrap_or_else(|| Style::default().add_modifier(Modifier::BOLD)); let uda_style_report_menu_active = uda_style_report_menu_active.unwrap_or_else(|| Style::default().add_modifier(Modifier::BOLD)); let uda_style_report_completion_pane = uda_style_report_completion_pane.unwrap_or_else(|| Style::default().fg(Color::Black).bg(Color::Rgb(223, 223, 223))); let uda_style_report_completion_pane_highlight = uda_style_report_completion_pane_highlight.unwrap_or(uda_style_report_completion_pane); let uda_style_title = uda_style_title.unwrap_or_else(|| Style::default().fg(Color::LightCyan)); let uda_style_title_border = uda_style_title_border.unwrap_or_else(|| Style::default().fg(Color::White)); let uda_style_help_gauge = uda_style_help_gauge.unwrap_or_else(|| Style::default().fg(Color::Gray)); let uda_style_command_error = uda_style_command_error.unwrap_or_else(|| Style::default().fg(Color::Red)); let uda_quick_tag_name = Self::get_uda_quick_tag_name(data); let uda_task_report_info_location = Self::get_uda_task_report_info_location(data); let uda_task_report_prompt_on_undo = Self::get_uda_task_report_prompt_on_undo(data); let uda_task_report_prompt_on_delete = Self::get_uda_task_report_prompt_on_delete(data); let uda_task_report_prompt_on_done = Self::get_uda_task_report_prompt_on_done(data); let uda_context_menu_select_on_move = Self::get_uda_context_menu_select_on_move(data); let uda_context_menu_close_on_select = Self::get_uda_context_menu_close_on_select(data); let uda_report_menu_select_on_move = Self::get_uda_report_menu_select_on_move(data); let uda_report_menu_close_on_select = Self::get_uda_report_menu_close_on_select(data); let uda_task_report_date_time_vague_more_precise = Self::get_uda_task_report_date_time_vague_more_precise(data); Ok(Self { enabled, color, color_keywords, filter, data_location, obfuscate, print_empty_columns, due, weekstart, rule_precedence_color, uda_priority_values, uda_tick_rate, uda_change_focus_rotate, uda_auto_insert_double_quotes_on_add, uda_auto_insert_double_quotes_on_annotate, uda_auto_insert_double_quotes_on_log, uda_prefill_task_metadata, uda_reset_filter_on_esc, uda_task_detail_prefetch, uda_task_report_use_all_tasks_for_completion, uda_task_report_use_alternate_style, uda_task_report_info_show, uda_task_report_looping, uda_task_report_jump_to_task_on_add, uda_selection_indicator, uda_mark_highlight_indicator, uda_unmark_highlight_indicator, uda_mark_indicator, uda_unmark_indicator, uda_scrollbar_indicator, uda_scrollbar_area, uda_selection_bold, uda_selection_italic, uda_selection_dim, uda_selection_blink, uda_selection_reverse, uda_calendar_months_per_row, uda_style_report_selection, uda_style_report_scrollbar, uda_style_report_scrollbar_area, uda_style_calendar_title, uda_style_calendar_today, uda_style_navbar, uda_style_command, uda_style_context_active, uda_style_report_menu_active, uda_style_report_completion_pane, uda_style_report_completion_pane_highlight, uda_style_title, uda_style_title_border, uda_style_help_gauge, uda_style_command_error, uda_shortcuts, uda_background_process, uda_background_process_period, uda_quick_tag_name, uda_task_report_info_location, uda_task_report_prompt_on_undo, uda_task_report_prompt_on_delete, uda_task_report_prompt_on_done, uda_context_menu_select_on_move, uda_context_menu_close_on_select, uda_report_menu_select_on_move, uda_report_menu_close_on_select, uda_task_report_date_time_vague_more_precise, uda: vec![], }) } fn get_bool_collection() -> HashMap { HashMap::new() } fn get_uda_background_process(data: &str) -> String { Self::get_config("uda.taskwarrior-tui.background_process", data).unwrap_or_default() } fn get_uda_background_process_period(data: &str) -> usize { Self::get_config("uda.taskwarrior-tui.background_process_period", data) .unwrap_or_default() .parse::() .unwrap_or(60) } fn get_uda_shortcuts(data: &str) -> Vec { let mut v = vec![]; for s in 0..=9 { let c = format!("uda.taskwarrior-tui.shortcuts.{}", s); let s = Self::get_config(&c, data).unwrap_or_default(); v.push(s); } v } fn get_uda_style(config: &str, data: &str) -> Option