Skip to content

Commit

Permalink
Prevent typing escape codes in text entry boxes
Browse files Browse the repository at this point in the history
Also reset extended keys support on exit
  • Loading branch information
joouha committed Oct 12, 2023
1 parent 96a9eca commit a1922e6
Show file tree
Hide file tree
Showing 10 changed files with 137 additions and 61 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ Added
Fixed
=====

- Prevent entry of type escape sequence codes into text areas
- Reset the terminal extended key mode at exit
- Limit horizontal scrolling of display areas
- Prevent error when commenting an empty cell
- Prevent moving through history in vi navigation mode
Expand Down
19 changes: 16 additions & 3 deletions euporie/console/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@
from euporie.core.widgets.status_bar import StatusBar

if TYPE_CHECKING:
from typing import Any
from typing import Any, TypeVar

_AppResult = TypeVar("_AppResult")

log = logging.getLogger(__name__)

Expand Down Expand Up @@ -150,11 +152,22 @@ def load_container(self) -> FloatContainer:
floats=self.floats, # type: ignore
)

def exit(self, **kwargs: Any) -> None:
def exit(
self,
result: _AppResult | None = None,
exception: BaseException | type[BaseException] | None = None,
style: str = "",
) -> None:
"""Close all tabs on exit."""
for tab in self.tabs:
tab.close()
super().exit(**kwargs)

if result is not None:
super().exit(result=result, style=style)
elif exception is not None:
super().exit(exception=exception, style=style)
else:
super().exit()

# ################################### Commands ####################################

Expand Down
32 changes: 30 additions & 2 deletions euporie/core/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@
from prompt_toolkit.enums import EditingMode
from prompt_toolkit.filters import Condition, buffer_has_focus, to_filter
from prompt_toolkit.input.defaults import create_input
from prompt_toolkit.key_binding.bindings.basic import load_basic_bindings
from prompt_toolkit.key_binding.bindings.basic import (
load_basic_bindings as load_ptk_basic_bindings,
)
from prompt_toolkit.key_binding.bindings.cpr import load_cpr_bindings
from prompt_toolkit.key_binding.bindings.emacs import (
load_emacs_bindings,
Expand Down Expand Up @@ -94,8 +96,9 @@
from asyncio import AbstractEventLoop
from pathlib import Path
from types import FrameType
from typing import Any, Callable
from typing import Any, Callable, TypeVar

# from prompt_toolkit.application import _AppResult
from prompt_toolkit.clipboard import Clipboard
from prompt_toolkit.contrib.ssh import PromptToolkitSSHSession
from prompt_toolkit.filters import Filter, FilterOrBool
Expand All @@ -111,6 +114,8 @@
from euporie.core.widgets.pager import Pager
from euporie.core.widgets.search_bar import SearchBar

_AppResult = TypeVar("_AppResult")

log = logging.getLogger(__name__)


Expand Down Expand Up @@ -331,6 +336,9 @@ def resume_rendering(self) -> None:

def pre_run(self, app: Application | None = None) -> None:
"""Call during the 'pre-run' stage of application loading."""
# Enable extend key support
if isinstance(self.output, Vt100_Output):
self.output.enable_extended_keys()
# Load key bindings
self.load_key_bindings()
# Determine what color depth to use
Expand Down Expand Up @@ -455,6 +463,7 @@ def post_load(self) -> None:

def load_key_bindings(self) -> None:
"""Load the application's key bindings."""
from euporie.core.key_binding.bindings.basic import load_basic_bindings
from euporie.core.key_binding.bindings.micro import load_micro_bindings
from euporie.core.key_binding.bindings.mouse import load_mouse_bindings

Expand All @@ -468,6 +477,7 @@ def load_key_bindings(self) -> None:
merge_key_bindings(
[
# Load basic bindings.
load_ptk_basic_bindings(),
load_basic_bindings(),
# Load micro bindings
load_micro_bindings(config=self.config),
Expand Down Expand Up @@ -530,6 +540,24 @@ def launch(cls) -> None:

return result

def exit(
self,
result: _AppResult | None = None,
exception: BaseException | type[BaseException] | None = None,
style: str = "",
) -> None:
"""Exit the application."""
# Reset extended keys on exit
if isinstance(self.output, Vt100_Output):
self.output.disable_extended_keys()

if result is not None:
super().exit(result=result, style=style)
elif exception is not None:
super().exit(exception=exception, style=style)
else:
super().exit()

def cleanup(self, signum: int, frame: FrameType | None) -> None:
"""Restore the state of the terminal on unexpected exit."""
log.critical("Unexpected exit signal, restoring terminal")
Expand Down
4 changes: 2 additions & 2 deletions euporie/core/key_binding/bindings/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""Define collections of generic key-bindings which do not belong to widgets."""

from euporie.core.key_binding.bindings import completion, micro
from euporie.core.key_binding.bindings import basic, completion, micro, mouse

__all__ = ["completion", "micro"]
__all__ = ["basic", "completion", "micro", "mouse"]
70 changes: 70 additions & 0 deletions euporie/core/key_binding/bindings/basic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
"""Define basic key-bindings for entering text."""

from __future__ import annotations

import logging
from typing import TYPE_CHECKING

from prompt_toolkit.filters import (
buffer_has_focus,
)
from prompt_toolkit.key_binding import ConditionalKeyBindings

from euporie.core.commands import add_cmd
from euporie.core.filters import (
micro_mode,
replace_mode,
)
from euporie.core.key_binding.registry import (
load_registered_bindings,
register_bindings,
)
from euporie.core.key_binding.utils import if_no_repeat

if TYPE_CHECKING:
from prompt_toolkit.key_binding import KeyBindingsBase, KeyPressEvent

from euporie.core.config import Config

log = logging.getLogger(__name__)


class TextEntry:
"""Basic key-bindings for text entry."""


# Register default bindings for micro edit mode
register_bindings(
{
"euporie.core.key_binding.bindings.basic.TextEntry": {
"type-key": "<any>",
},
}
)


def load_basic_bindings(config: Config | None = None) -> KeyBindingsBase:
"""Load basic key-bindings for text entry."""
return ConditionalKeyBindings(
load_registered_bindings(
"euporie.core.key_binding.bindings.basic.TextEntry", config=config
),
micro_mode,
)


# Commands


@add_cmd(
filter=buffer_has_focus,
save_before=if_no_repeat,
hidden=True,
)
def type_key(event: KeyPressEvent) -> None:
"""Enter a key."""
# Do not insert escape sequences
if not event.data.startswith("\x1b["):
event.current_buffer.insert_text(
event.data * event.arg, overwrite=replace_mode()
)
19 changes: 1 addition & 18 deletions euporie/core/key_binding/bindings/micro.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
load_registered_bindings,
register_bindings,
)
from euporie.core.key_binding.utils import if_no_repeat

if TYPE_CHECKING:
from prompt_toolkit.key_binding import KeyBindingsBase, KeyPressEvent
Expand All @@ -75,19 +76,13 @@ class EditMode:
"""Micro style editor key-bindings."""


def if_no_repeat(event: KeyPressEvent) -> bool:
"""Return True when the previous event was delivered to another handler."""
return not event.is_repeat


# Register micro edit mode
extend_enum(EditingMode, "MICRO", "MICRO")

# Register default bindings for micro edit mode
register_bindings(
{
"euporie.core.key_binding.bindings.micro.EditMode": {
"type-key": "<any>",
"move-cursor-right": "right",
"move-cursor-left": "left",
"newline": "enter",
Expand Down Expand Up @@ -203,18 +198,6 @@ def load_micro_bindings(config: Config | None = None) -> KeyBindingsBase:
# Commands


@add_cmd(
filter=buffer_has_focus,
save_before=if_no_repeat,
hidden=True,
)
def type_key(event: KeyPressEvent) -> None:
"""Enter a key."""
event.current_buffer.insert_text(
event.data * event.arg, overwrite=micro_replace_mode()
)


@add_cmd(
filter=buffer_has_focus,
)
Expand Down
7 changes: 7 additions & 0 deletions euporie/core/key_binding/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
from euporie.core.keys import Keys

if TYPE_CHECKING:
from prompt_toolkit.key_binding import KeyPressEvent

from euporie.core.key_binding.registry import AnyKeys


Expand All @@ -21,6 +23,11 @@
}


def if_no_repeat(event: KeyPressEvent) -> bool:
"""Return True when the previous event was delivered to another handler."""
return not event.is_repeat


def parse_keys(keys: AnyKeys) -> list[tuple[str | Keys, ...]]:
"""Pare a list of keys."""
output: list[tuple[str | Keys, ...]] = []
Expand Down
10 changes: 7 additions & 3 deletions euporie/core/keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from prompt_toolkit.input.ansi_escape_sequences import ANSI_SEQUENCES
from prompt_toolkit.keys import Keys

# Key-name and emacs style shortcut prefix
# Map key-modifier to key-name & emacs style shortcut prefix
_modifiers = {
# 0b00000000: ("", ""),
0b00000001: ("Shift", "s"),
Expand Down Expand Up @@ -41,6 +41,9 @@
"Left": (1, "D"),
"Home": (1, "H"),
"End": (1, "F"),
"PrintScreen": (57361, "u"),
"Pause": (57362, "u"),
"Menu": (57363, "u"),
"F1": (1, "P"),
"F2": (1, "Q"),
"F3": (13, "~"),
Expand All @@ -53,7 +56,7 @@
"F10": (21, "~"),
"F11": (23, "~"),
"F12": (24, "~"),
"Menu": (29, "~"),
# "Menu": (29, "~"),
}

# Add Alt-key shortcuts
Expand Down Expand Up @@ -89,7 +92,8 @@
for key, (number, suffix) in _kitty_functional_codes.items():
key_var = f"{mod_name}{key}"
if not hasattr(Keys, key_var):
extend_enum(Keys, key_var, f"{mod_short}-{key.lower()}")
mod_str = f"{mod_short}-" if mod_short else ""
extend_enum(Keys, key_var, f"{mod_str}{key.lower()}")
mod_str = str(mod_no)
num_str = str(number)
if mod_no == 1:
Expand Down
32 changes: 1 addition & 31 deletions euporie/core/renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from __future__ import annotations

import logging
from typing import TYPE_CHECKING, cast
from typing import TYPE_CHECKING

from prompt_toolkit.data_structures import Point, Size
from prompt_toolkit.filters import to_filter
Expand All @@ -13,8 +13,6 @@
from prompt_toolkit.renderer import Renderer as PtkRenderer
from prompt_toolkit.renderer import _StyleStringHasStyleCache, _StyleStringToAttrsCache

from euporie.core.io import Vt100_Output

if TYPE_CHECKING:
from typing import Any, Callable

Expand Down Expand Up @@ -270,41 +268,13 @@ def __init__(
self.extend_height = to_filter(extend_height)
self.extend_width = to_filter(extend_width)

def reset(self, _scroll: bool = False, leave_alternate_screen: bool = True) -> None:
"""Disable extended keys before resetting the output."""
from euporie.core.app import BaseApp

super().reset(_scroll, leave_alternate_screen)

output = self.output

# Disable extended keys
app = self.app
if (
app
and isinstance(app, BaseApp)
and app.term_info.csiu_status.value
and isinstance(output, Vt100_Output)
):
cast("Vt100_Output", self.output).disable_extended_keys()

def render(
self, app: Application[Any], layout: Layout, is_done: bool = False
) -> None:
"""Render the current interface to the output."""
from euporie.core.app import BaseApp

output = self.output
self.app = app

# Enable extended keys
if (
isinstance(app, BaseApp)
# and app.term_info.csiu_status.value
and isinstance(output, Vt100_Output)
):
output.enable_extended_keys()

# Enter alternate screen.
if self.full_screen and not self._in_alternate_screen:
self._in_alternate_screen = True
Expand Down
3 changes: 1 addition & 2 deletions euporie/core/widgets/dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -914,8 +914,7 @@ def format_key_info(self) -> StyleAndTextTuples:
table = Table(padding=0)

for group, bindings in BINDINGS.items():
log.info(group)
if bindings:
if any(not get_cmd(cmd_name).hidden() for cmd_name in bindings):
mod_name, cls_name = group.rsplit(".", maxsplit=1)
mod = importlib.import_module(mod_name)
app_cls = getattr(mod, cls_name)
Expand Down

0 comments on commit a1922e6

Please sign in to comment.