diff --git a/CHANGELOG.md b/CHANGELOG.md
index fb1ad4ed9..154c41a7b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,17 +9,20 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
### Changed
-- Breaking change: standard keyboard scrollable navigation bindings have been moved off `Widget` and onto a new base class for scrollable containers (see also below addition) https://github.com/Textualize/textual/issues/2332
-- `ScrollView` now inherits from `ScrollableContainer` rather than `Widget` https://github.com/Textualize/textual/issues/2332
-- Containers no longer inherit any bindings from `Widget` https://github.com/Textualize/textual/issues/2331
+- `textual run` execs apps in a new context.
### Added
+- Added `-c` switch to `textual run` which runs commands in a Textual dev environment.
+- Breaking change: standard keyboard scrollable navigation bindings have been moved off `Widget` and onto a new base class for scrollable containers (see also below addition) https://github.com/Textualize/textual/issues/2332
+- `ScrollView` now inherits from `ScrollableContainer` rather than `Widget` https://github.com/Textualize/textual/issues/2332
+- Containers no longer inherit any bindings from `Widget` https://github.com/Textualize/textual/issues/2331
- Added `ScrollableContainer`; a container class that binds the common navigation keys to scroll actions (see also above breaking change) https://github.com/Textualize/textual/issues/2332
### Fixed
- Fixed dark mode toggles in a "child" screen not updating a "parent" screen https://github.com/Textualize/textual/issues/1999
+- Fixed "panel" border not exposed via CSS
## [0.20.1] - 2023-04-18
diff --git a/src/textual/app.py b/src/textual/app.py
index 77311a522..5efe4e380 100644
--- a/src/textual/app.py
+++ b/src/textual/app.py
@@ -1048,9 +1048,19 @@ class App(Generic[ReturnType], DOMNode):
auto_pilot_task: Task | None = None
+ if auto_pilot is None and constants.PRESS:
+ keys = constants.PRESS.split(",")
+
+ async def press_keys(pilot: Pilot) -> None:
+ """Auto press keys."""
+ await pilot.press(*keys)
+
+ auto_pilot = press_keys
+
async def app_ready() -> None:
"""Called by the message loop when the app is ready."""
nonlocal auto_pilot_task
+
if auto_pilot is not None:
async def run_auto_pilot(
@@ -1762,23 +1772,16 @@ class App(Generic[ReturnType], DOMNode):
May be used as a hook for any operations that should run first.
"""
- try:
- screenshot_timer = float(os.environ.get("TEXTUAL_SCREENSHOT", "0"))
- except ValueError:
- return
- screenshot_title = os.environ.get("TEXTUAL_SCREENSHOT_TITLE")
-
- if not screenshot_timer:
- return
-
- async def on_screenshot():
- """Used by docs plugin."""
- svg = self.export_screenshot(title=screenshot_title)
- self._screenshot = svg # type: ignore
+ async def take_screenshot() -> None:
+ """Take a screenshot and exit."""
+ self.save_screenshot()
self.exit()
- self.set_timer(screenshot_timer, on_screenshot, name="screenshot timer")
+ if constants.SCREENSHOT_DELAY >= 0:
+ self.set_timer(
+ constants.SCREENSHOT_DELAY, take_screenshot, name="screenshot timer"
+ )
async def _on_compose(self) -> None:
try:
@@ -1966,6 +1969,14 @@ class App(Generic[ReturnType], DOMNode):
self._print_error_renderables()
+ if constants.SHOW_RETURN:
+ from rich.console import Console
+ from rich.pretty import Pretty
+
+ console = Console()
+ console.print("[b]The app returned:")
+ console.print(Pretty(self._return_value))
+
async def _on_exit_app(self) -> None:
self._begin_batch() # Prevent repaint / layout while shutting down
await self._message_queue.put(None)
diff --git a/src/textual/cli/_run.py b/src/textual/cli/_run.py
new file mode 100644
index 000000000..b00818e85
--- /dev/null
+++ b/src/textual/cli/_run.py
@@ -0,0 +1,163 @@
+"""
+
+Functions to run Textual apps with an updated environment.
+
+Note that these methods will execute apps in a new process, and abandon the current process.
+This means that (if they succeed) they will never return.
+
+"""
+
+from __future__ import annotations
+
+import importlib
+import os
+import platform
+import shlex
+import sys
+from string import Template
+from typing import NoReturn
+
+WINDOWS = platform.system() == "Windows"
+"""True if we're running on Windows."""
+
+EXEC_SCRIPT = Template(
+ """\
+from textual.app import App
+from $MODULE import $APP as app;
+if isinstance(app, App):
+ # If we imported an app, run it
+ app.run()
+else:
+ # Otherwise it is assumed to be a class
+ app().run()
+"""
+)
+"""A template script to import and run an app."""
+
+
+class ExecImportError(Exception):
+ """Raised if a Python import is invalid."""
+
+
+def run_app(command_args: str, environment: dict[str, str] | None = None) -> None:
+ """Run a textual app.
+
+ Note:
+ The current process is abandoned.
+
+ Args:
+ command_args: Arguments to pass to the Textual app.
+ environment: Environment variables, or None to use current process.
+ """
+ import_name, *args = shlex.split(command_args, posix=not WINDOWS)
+ if environment is None:
+ app_environment = dict(os.environ)
+ else:
+ app_environment = environment
+
+ if _is_python_path(import_name):
+ # If it is a Python path we can exec it now
+ exec_python([import_name, *args], app_environment)
+
+ else:
+ # Otherwise it is assumed to be a Python import
+ try:
+ exec_import(import_name, args, app_environment)
+ except (SyntaxError, ExecImportError):
+ print(f"Unable to import Textual app from {import_name!r}")
+
+
+def _is_python_path(path: str) -> bool:
+ """Does the given file look like it's run with Python?
+
+ Args:
+ path: The path to check.
+
+ Returns:
+ True if the path references Python code, False it it doesn't.
+ """
+ if path.endswith(".py"):
+ return True
+ try:
+ with open(path, "r") as source:
+ first_line = source.readline()
+ except IOError:
+ return False
+ return first_line.startswith("#!") and "py" in first_line
+
+
+def _flush() -> None:
+ """Flush output before exec."""
+ sys.stderr.flush()
+ sys.stdout.flush()
+
+
+def exec_python(args: list[str], environment: dict[str, str]) -> NoReturn:
+ """Execute a Python script.
+
+ Args:
+ args: Additional arguments.
+ environment: Environment variables.
+
+ """
+ _flush()
+ os.execvpe(sys.executable, ["python", *args], environment)
+
+
+def exec_command(command: str, environment: dict[str, str]) -> NoReturn:
+ """Execute a command with the given environment.
+
+ Args:
+ command: Command to execute.
+ environment: Environment variables.
+ """
+ _flush()
+ command, *args = shlex.split(command, posix=not WINDOWS)
+ os.execvpe(command, [command, *args], environment)
+
+
+def check_import(module_name: str, app_name: str) -> bool:
+ """Check if a symbol can be imported.
+
+ Args:
+ module_name: Name of the module
+ app_name: Name of the app.
+
+ Returns:
+ True if the app may be imported from the module.
+ """
+
+ try:
+ sys.path.insert(0, "")
+ module = importlib.import_module(module_name)
+ except ImportError as error:
+ return False
+ return hasattr(module, app_name)
+
+
+def exec_import(
+ import_name: str, args: list[str], environment: dict[str, str]
+) -> NoReturn:
+ """Import and execute an app.
+
+ Raises:
+ SyntaxError: If any imports are not valid Python symbols.
+ ExecImportError: If the module could not be imported.
+
+ Args:
+ import_name: The Python import.
+ args: Command line arguments.
+ environment: Environment variables.
+
+ """
+ module, _colon, app = import_name.partition(":")
+ app = app or "app"
+
+ if not check_import(module, app):
+ raise ExecImportError(f"Unable to import {app!r} from {import_name!r}")
+
+ script = EXEC_SCRIPT.substitute(MODULE=module, APP=app)
+ # Compiling the script will raise a SyntaxError if there are any invalid symbols
+ compile(script, "textual-exec", "exec")
+ _flush()
+ exec_python(["-c", script, *args], environment)
diff --git a/src/textual/cli/cli.py b/src/textual/cli/cli.py
index c1186f99d..9ecff8573 100644
--- a/src/textual/cli/cli.py
+++ b/src/textual/cli/cli.py
@@ -2,7 +2,8 @@ from __future__ import annotations
import sys
-from ..constants import DEFAULT_DEVTOOLS_PORT, DEVTOOLS_PORT_ENVIRON_VARIABLE
+from ..constants import DEVTOOLS_PORT
+from ._run import exec_command, run_app
try:
import click
@@ -12,9 +13,6 @@ except ImportError:
from importlib_metadata import version
-from textual._import_app import AppFail, import_app
-from textual.pilot import Pilot
-
@click.group()
@click.version_option(version("textual"))
@@ -29,31 +27,27 @@ def run():
type=int,
default=None,
metavar="PORT",
- help=f"Port to use for the development mode console. Defaults to {DEFAULT_DEVTOOLS_PORT}.",
+ help=f"Port to use for the development mode console. Defaults to {DEVTOOLS_PORT}.",
)
@click.option("-v", "verbose", help="Enable verbose logs.", is_flag=True)
@click.option("-x", "--exclude", "exclude", help="Exclude log group(s)", multiple=True)
def console(port: int | None, verbose: bool, exclude: list[str]) -> None:
"""Launch the textual console."""
- import os
from rich.console import Console
from textual.devtools.server import _run_devtools
- if port is not None:
- os.environ[DEVTOOLS_PORT_ENVIRON_VARIABLE] = str(port)
-
console = Console()
console.clear()
console.show_cursor(False)
try:
- _run_devtools(verbose=verbose, exclude=exclude)
+ _run_devtools(verbose=verbose, exclude=exclude, port=port)
finally:
console.show_cursor(True)
-def _post_run_warnings() -> None:
+def _pre_run_warnings() -> None:
"""Look for and report any issues with the environment.
This is the right place to add code that looks at the terminal, or other
@@ -73,8 +67,10 @@ def _post_run_warnings() -> None:
# second item is a message to show the user on exit from `textual run`.
warnings = [
(
- platform.system() == "Darwin"
- and os.environ.get("TERM_PROGRAM") == "Apple_Terminal",
+ (
+ platform.system() == "Darwin"
+ and os.environ.get("TERM_PROGRAM") == "Apple_Terminal"
+ ),
"The default terminal app on macOS is limited to 256 colors. See our FAQ for more details:\n\n"
"https://github.com/Textualize/textual/blob/main/FAQ.md#why-doesn't-textual-look-good-on-macos",
)
@@ -82,7 +78,11 @@ def _post_run_warnings() -> None:
for concerning, concern in warnings:
if concerning:
- console.print(Panel.fit(f"⚠️ [bold green] {concern}[/]", style="cyan"))
+ console.print(
+ Panel.fit(
+ f"⚠️ {concern}", style="yellow", border_style="red", padding=(1, 2)
+ )
+ )
@run.command(
@@ -92,25 +92,51 @@ def _post_run_warnings() -> None:
},
)
@click.argument("import_name", metavar="FILE or FILE:APP")
-@click.option("--dev", "dev", help="Enable development mode", is_flag=True)
+@click.option("--dev", "dev", help="Enable development mode.", is_flag=True)
@click.option(
"--port",
"port",
type=int,
default=None,
metavar="PORT",
- help=f"Port to use for the development mode console. Defaults to {DEFAULT_DEVTOOLS_PORT}.",
+ help=f"Port to use for the development mode console. Defaults to {DEVTOOLS_PORT}.",
+)
+@click.option(
+ "--press", "press", default=None, help="Comma separated keys to simulate press."
)
-@click.option("--press", "press", help="Comma separated keys to simulate press")
@click.option(
"--screenshot",
type=int,
default=None,
metavar="DELAY",
- help="Take screenshot after DELAY seconds",
+ help="Take screenshot after DELAY seconds.",
)
-def run_app(
- import_name: str, dev: bool, port: int | None, press: str, screenshot: int | None
+@click.option(
+ "-c",
+ "--command",
+ "command",
+ type=bool,
+ default=False,
+ help="Run as command rather that a file / module.",
+ is_flag=True,
+)
+@click.option(
+ "-r",
+ "--show-return",
+ "show_return",
+ type=bool,
+ default=False,
+ help="Show any return value on exit.",
+ is_flag=True,
+)
+def _run_app(
+ import_name: str,
+ dev: bool,
+ port: int | None,
+ press: str | None,
+ screenshot: int | None,
+ command: bool = False,
+ show_return: bool = False,
) -> None:
"""Run a Textual app.
@@ -132,53 +158,39 @@ def run_app(
in quotes:
textual run "foo.py arg --option"
+
+ Use the -c switch to run a command that launches a Textual app.
+
+ textual run -c "textual colors"
"""
import os
- import sys
- from asyncio import sleep
from textual.features import parse_features
- if port is not None:
- os.environ[DEVTOOLS_PORT_ENVIRON_VARIABLE] = str(port)
+ environment = dict(os.environ)
- features = set(parse_features(os.environ.get("TEXTUAL", "")))
+ features = set(parse_features(environment.get("TEXTUAL", "")))
if dev:
features.add("debug")
features.add("devtools")
- os.environ["TEXTUAL"] = ",".join(sorted(features))
- try:
- app = import_app(import_name)
- except AppFail as error:
- from rich.console import Console
+ environment["TEXTUAL"] = ",".join(sorted(features))
+ if port is not None:
+ environment["TEXTUAL_DEVTOOLS_PORT"] = str(port)
+ if press is not None:
+ environment["TEXTUAL_PRESS"] = str(press)
+ if screenshot is not None:
+ environment["TEXTUAL_SCREENSHOT"] = str(screenshot)
+ if show_return:
+ environment["TEXTUAL_SHOW_RETURN"] = "1"
- console = Console(stderr=True)
- console.print(str(error))
- sys.exit(1)
+ _pre_run_warnings()
- press_keys = press.split(",") if press else None
-
- async def run_press_keys(pilot: Pilot) -> None:
- if press_keys is not None:
- await pilot.press(*press_keys)
- if screenshot is not None:
- await sleep(screenshot)
- filename = pilot.app.save_screenshot()
- pilot.app.exit(message=f"Saved {filename!r}")
-
- result = app.run(auto_pilot=run_press_keys)
-
- if result is not None:
- from rich.console import Console
- from rich.pretty import Pretty
-
- console = Console()
- console.print("[b]The app returned:")
- console.print(Pretty(result))
-
- _post_run_warnings()
+ if command:
+ exec_command(import_name, environment)
+ else:
+ run_app(import_name, environment)
@run.command("borders")
@@ -215,7 +227,7 @@ def keys():
@run.command("diagnose")
def run_diagnose():
- """Print information about the Textual environment"""
+ """Print information about the Textual environment."""
from textual.cli.tools.diagnose import diagnose
diagnose()
diff --git a/src/textual/cli/previews/borders.py b/src/textual/cli/previews/borders.py
index 52ce7f73d..282a42826 100644
--- a/src/textual/cli/previews/borders.py
+++ b/src/textual/cli/previews/borders.py
@@ -1,6 +1,6 @@
from textual.app import App, ComposeResult
-from textual.constants import BORDERS
from textual.containers import VerticalScroll
+from textual.css.constants import VALID_BORDER
from textual.widgets import Button, Label
TEXT = """I must not fear.
@@ -26,7 +26,7 @@ class BorderButtons(VerticalScroll):
"""
def compose(self) -> ComposeResult:
- for border in BORDERS:
+ for border in sorted(VALID_BORDER):
if border:
yield Button(border, id=border)
diff --git a/src/textual/cli/run.py b/src/textual/cli/run.py
new file mode 100644
index 000000000..da3c9ea63
--- /dev/null
+++ b/src/textual/cli/run.py
@@ -0,0 +1,5 @@
+import sys
+
+from ._run import run
+
+run(sys.argv[1], sys.argv[1:])
diff --git a/src/textual/constants.py b/src/textual/constants.py
index a29c1b312..eae559c9b 100644
--- a/src/textual/constants.py
+++ b/src/textual/constants.py
@@ -9,10 +9,6 @@ import os
from typing_extensions import Final
-from ._border import BORDER_CHARS
-
-__all__ = ["BORDERS"]
-
get_environ = os.environ.get
@@ -42,16 +38,18 @@ def get_environ_int(name: str, default: int) -> int:
or the default value otherwise.
"""
try:
- return int(get_environ(name, default))
+ return int(os.environ[name])
+ except KeyError:
+ return default
except ValueError:
return default
-BORDERS = list(BORDER_CHARS)
-
DEBUG: Final[bool] = get_environ_bool("TEXTUAL_DEBUG")
+"""Enable debug mode."""
DRIVER: Final[str | None] = get_environ("TEXTUAL_DRIVER", None)
+"""Import for replacement driver."""
FILTERS: Final[str] = get_environ("TEXTUAL_FILTERS", "")
"""A list of filters to apply to renderables."""
@@ -59,12 +57,14 @@ FILTERS: Final[str] = get_environ("TEXTUAL_FILTERS", "")
LOG_FILE: Final[str | None] = get_environ("TEXTUAL_LOG", None)
"""A last resort log file that appends all logs, when devtools isn't working."""
-
-DEVTOOLS_PORT_ENVIRON_VARIABLE: Final[str] = "TEXTUAL_CONSOLE_PORT"
-"""The name of the environment variable that sets the port for the devtools."""
-DEFAULT_DEVTOOLS_PORT: Final[int] = 8081
-"""The default port to use for the devtools."""
-DEVTOOLS_PORT: Final[int] = get_environ_int(
- DEVTOOLS_PORT_ENVIRON_VARIABLE, DEFAULT_DEVTOOLS_PORT
-)
+DEVTOOLS_PORT: Final[int] = get_environ_int("TEXTUAL_DEVTOOLS_PORT", 8081)
"""Constant with the port that the devtools will connect to."""
+
+SCREENSHOT_DELAY: Final[int] = get_environ_int("TEXTUAL_SCREENSHOT", -1)
+"""Seconds delay before taking screenshot."""
+
+PRESS: Final[str] = get_environ("TEXTUAL_PRESS", "")
+"""Keys to automatically press."""
+
+SHOW_RETURN: Final[bool] = get_environ_bool("TEXTUAL_SHOW_RETURN")
+"""Write the return value on exit."""
diff --git a/src/textual/css/constants.py b/src/textual/css/constants.py
index d89129daf..84f874813 100644
--- a/src/textual/css/constants.py
+++ b/src/textual/css/constants.py
@@ -20,6 +20,7 @@ VALID_BORDER: Final = {
"inner",
"none",
"outer",
+ "panel",
"round",
"solid",
"tall",
diff --git a/src/textual/devtools/server.py b/src/textual/devtools/server.py
index 4e7f66814..11f631f00 100644
--- a/src/textual/devtools/server.py
+++ b/src/textual/devtools/server.py
@@ -8,8 +8,9 @@ from aiohttp.web_request import Request
from aiohttp.web_routedef import get
from aiohttp.web_ws import WebSocketResponse
-from textual.devtools.client import DEVTOOLS_PORT
-from textual.devtools.service import DevtoolsService
+from ..constants import DEVTOOLS_PORT
+from .client import DEVTOOLS_PORT
+from .service import DevtoolsService
DEFAULT_SIZE_CHANGE_POLL_DELAY_SECONDS = 2
@@ -38,7 +39,9 @@ async def _on_startup(app: Application) -> None:
await service.start()
-def _run_devtools(verbose: bool, exclude: list[str] | None = None) -> None:
+def _run_devtools(
+ verbose: bool, exclude: list[str] | None = None, port: int | None = None
+) -> None:
app = _make_devtools_aiohttp_app(verbose=verbose, exclude=exclude)
def noop_print(_: str) -> None:
@@ -47,7 +50,7 @@ def _run_devtools(verbose: bool, exclude: list[str] | None = None) -> None:
try:
run_app(
app,
- port=DEVTOOLS_PORT,
+ port=DEVTOOLS_PORT if port is None else port,
print=noop_print,
loop=asyncio.get_event_loop(),
)
diff --git a/tests/devtools/test_devtools_client.py b/tests/devtools/test_devtools_client.py
index b67f558ba..cc5231de7 100644
--- a/tests/devtools/test_devtools_client.py
+++ b/tests/devtools/test_devtools_client.py
@@ -10,7 +10,7 @@ from rich.console import ConsoleDimensions
from rich.panel import Panel
from tests.utilities.render import wait_for_predicate
-from textual.constants import DEFAULT_DEVTOOLS_PORT
+from textual.constants import DEVTOOLS_PORT
from textual.devtools.client import DevtoolsClient
from textual.devtools.redirect_output import DevtoolsLog
@@ -22,7 +22,7 @@ TIMESTAMP = 1649166819
def test_devtools_client_initialize_defaults():
devtools = DevtoolsClient()
- assert devtools.url == f"ws://127.0.0.1:{DEFAULT_DEVTOOLS_PORT}"
+ assert devtools.url == f"ws://127.0.0.1:{DEVTOOLS_PORT}"
async def test_devtools_client_is_connected(devtools):
diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots.ambr b/tests/snapshot_tests/__snapshots__/test_snapshots.ambr
index c80298cc6..1fe2e6519 100644
--- a/tests/snapshot_tests/__snapshots__/test_snapshots.ambr
+++ b/tests/snapshot_tests/__snapshots__/test_snapshots.ambr
@@ -893,141 +893,141 @@
font-weight: 700;
}
- .terminal-4039043553-matrix {
+ .terminal-1095690712-matrix {
font-family: Fira Code, monospace;
font-size: 20px;
line-height: 24.4px;
font-variant-east-asian: full-width;
}
- .terminal-4039043553-title {
+ .terminal-1095690712-title {
font-size: 18px;
font-weight: bold;
font-family: arial;
}
- .terminal-4039043553-r1 { fill: #05080f }
- .terminal-4039043553-r2 { fill: #e1e1e1 }
- .terminal-4039043553-r3 { fill: #c5c8c6 }
- .terminal-4039043553-r4 { fill: #1e2226;font-weight: bold }
- .terminal-4039043553-r5 { fill: #35393d }
- .terminal-4039043553-r6 { fill: #454a50 }
- .terminal-4039043553-r7 { fill: #fea62b }
- .terminal-4039043553-r8 { fill: #e2e3e3;font-weight: bold }
- .terminal-4039043553-r9 { fill: #000000 }
- .terminal-4039043553-r10 { fill: #e2e3e3 }
- .terminal-4039043553-r11 { fill: #14191f }
+ .terminal-1095690712-r1 { fill: #05080f }
+ .terminal-1095690712-r2 { fill: #e1e1e1 }
+ .terminal-1095690712-r3 { fill: #c5c8c6 }
+ .terminal-1095690712-r4 { fill: #1e2226;font-weight: bold }
+ .terminal-1095690712-r5 { fill: #35393d }
+ .terminal-1095690712-r6 { fill: #454a50 }
+ .terminal-1095690712-r7 { fill: #fea62b }
+ .terminal-1095690712-r8 { fill: #e2e3e3;font-weight: bold }
+ .terminal-1095690712-r9 { fill: #000000 }
+ .terminal-1095690712-r10 { fill: #e2e3e3 }
+ .terminal-1095690712-r11 { fill: #14191f }
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
- BorderApp
+ BorderApp
-
-
-
- ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
- ascii
- ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁
- ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔+------------------- ascii --------------------+
- none||
- ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁||
- ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔|I must not fear.|
- hidden|Fear is the mind-killer.|
- ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁|Fear is the little-death that brings |
- ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔|total obliteration.|
- blank|I will face my fear.|
- ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▅▅|I will permit it to pass over me and |
- ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔|through me.|
- round|And when it has gone past, I will turn|
- ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁|the inner eye to see its path.|
- ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔|Where the fear has gone there will be |
- solid|nothing. Only I will remain.|
- ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁||
- ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔||
- double+----------------------------------------------+
- ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁
- ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
- dashed
- ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁
+
+
+
+ ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
+ ascii
+ ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁
+ ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔+------------------- ascii --------------------+
+ blank||
+ ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁||
+ ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔|I must not fear.|
+ dashed|Fear is the mind-killer.|
+ ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁|Fear is the little-death that brings |
+ ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔|total obliteration.|
+ double|I will face my fear.|
+ ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▅▅|I will permit it to pass over me and |
+ ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔|through me.|
+ heavy|And when it has gone past, I will turn|
+ ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁|the inner eye to see its path.|
+ ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔|Where the fear has gone there will be |
+ hidden|nothing. Only I will remain.|
+ ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁||
+ ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔||
+ hkey+----------------------------------------------+
+ ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁
+ ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
+ inner
+ ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁