diff --git a/README.md b/README.md index 16f8499..093c642 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,9 @@ With its latest version you can easily visualize the complete state tree, gainin - [x] Added dynamic value for "targets" checkbox (checkbox is marked when resources are selected) - [x] Added a short summary of the suggested plan before applying it - [x] Added a redacted error tracker on unhandeled exceptions (only when usage reporting is enabled) +- [x] Added a fullscreen mode to allow easier copying of resource / plan parts - [x] Fixed: search through full module names +- [x] Fixed: Copy to clipboard crashes on some systems ### Version 0.11 diff --git a/pyproject.toml b/pyproject.toml index 46bf6b7..7633571 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api" [tool.poetry] name = "tftui" -version = "0.12.5" +version = "0.12.6" description = "Terraform Textual User Interface" authors = ["Ido Avraham"] license = "Apache-2.0" diff --git a/tftui/__main__.py b/tftui/__main__.py index 729da46..57c564f 100644 --- a/tftui/__main__.py +++ b/tftui/__main__.py @@ -11,7 +11,7 @@ from tftui.state import State, Block, execute_async, split_resource_name from tftui.plan import PlanScreen from tftui.debug_log import setup_logging -from tftui.modal import HelpModal, YesNoModal, PlanInputsModal +from tftui.modal import HelpModal, YesNoModal, PlanInputsModal, FullTextModal from textual import work from textual.app import App, Binding from textual.containers import Horizontal @@ -224,6 +224,7 @@ def __init__(self, *args, **kwargs): Binding("escape", "back", "Back"), ("s", "select", "Select"), Binding("spacebar", "select", "Select"), + ("f", "fullscreen", "FullScreen"), ("d", "delete", "Delete"), ("t", "taint", "Taint"), ("u", "untaint", "Untaint"), @@ -463,12 +464,17 @@ async def action_untaint(self) -> None: await self.action_manipulate_resources("untaint") def action_copy(self) -> None: - if self.switcher.current == "resource": - pyperclip.copy(self.app.tree.current_node.data.contents) - self.notify("Copied resource definition to clipboard") - elif self.switcher.current == "tree": - pyperclip.copy(self.app.tree.current_node.label.plain) - self.notify("Copied resource name to clipboard") + try: + if self.switcher.current == "resource": + pyperclip.copy(self.app.tree.current_node.data.contents) + self.notify("Copied resource definition to clipboard") + elif self.switcher.current == "tree": + pyperclip.copy(self.app.tree.current_node.label.plain) + self.notify("Copied resource name to clipboard") + except Exception: + self.notify( + "Copy to clipboard is unsupported in this terminal", severity="warning" + ) def action_refresh(self) -> None: self.switcher.current = "tree" @@ -506,6 +512,19 @@ def action_search(self) -> None: self.switcher.current = "tree" self.search.focus() + def action_fullscreen(self) -> None: + if self.switcher.current not in ("plan", "resource"): + return + self.push_screen( + FullTextModal( + self.tree.current_node.data.contents + if self.switcher.current == "resource" + else self.plan.fulltext, + self.switcher.current == "resource", + ) + ) + self.plan.focus() + def _handle_exception(self, exception: Exception) -> None: self.error_message = "".join( traceback.format_exception( diff --git a/tftui/modal.py b/tftui/modal.py index 8e3b8a0..6dfc987 100644 --- a/tftui/modal.py +++ b/tftui/modal.py @@ -6,6 +6,30 @@ from textual.containers import Horizontal +class FullTextModal(ModalScreen): + contents = None + is_resource = False + + def __init__(self, contents: str, is_resource: bool, *args, **kwargs): + self.contents = contents + self.is_resource = is_resource + super().__init__(*args, **kwargs) + + def compose(self) -> ComposeResult: + fullscreen = RichLog(auto_scroll=False) + if self.is_resource: + fullscreen.highlight = True + fullscreen.markup = True + fullscreen.wrap = True + + fullscreen.write(self.contents) + yield fullscreen + + def on_key(self, event) -> None: + if event.key in ("f", "escape"): + self.app.pop_screen() + + class YesNoModal(ModalScreen): contents = None @@ -88,6 +112,7 @@ class HelpModal(ModalScreen): ("ENTER", "View resource details"), ("ESC", "Go back"), ("S / Space", "Select current resource (toggle)"), + ("F", "Show resource/plan on full screen; Hold SHIFT/OPTIONS to copy text"), ("D", "Delete selected resources, or highlighted resource if none is selected"), ("T", "Taint selected resources, or highlighted resource if none is selected"), ( diff --git a/tftui/plan.py b/tftui/plan.py index d4d5220..c72a92c 100644 --- a/tftui/plan.py +++ b/tftui/plan.py @@ -11,6 +11,7 @@ class PlanScreen(RichLog): executable = None active_plan = None + fulltext = None BINDINGS = [] @@ -25,6 +26,7 @@ async def create_plan(self, varfile, targets, destroy="") -> None: self.active_plan = Text("") self.auto_scroll = False self.parent.loading = True + self.fulltext = None self.clear() command = [ self.executable, @@ -56,6 +58,7 @@ async def create_plan(self, varfile, targets, destroy="") -> None: data = await proc.stdout.readline() if not data: break + self.parent.loading = False stripped_line = data.decode("utf-8").rstrip() stylzed_line = Text(stripped_line) @@ -66,6 +69,7 @@ async def create_plan(self, varfile, targets, destroy="") -> None: ): self.clear() self.auto_scroll = False + self.fulltext = Text("") if stripped_line == "": block_color = "" @@ -110,6 +114,10 @@ async def create_plan(self, varfile, targets, destroy="") -> None: stylzed_line.stylize(block_color) self.write(stylzed_line) + + if self.fulltext is not None: + self.fulltext += stylzed_line + Text("\n") + finally: await proc.wait() if proc.returncode != 2: @@ -121,6 +129,7 @@ async def create_plan(self, varfile, targets, destroy="") -> None: async def execute_apply(self) -> None: self.parent.loading = True self.auto_scroll = True + self.fulltext = Text("") command = [self.executable, "apply", "-no-color", "tftui.plan"] logger.debug(f"Executing command: {command}") @@ -142,6 +151,7 @@ async def execute_apply(self) -> None: if text.plain.startswith("Apply complete!"): text.stylize("bold white") self.write(text) + self.fulltext += text + Text("\n") finally: await proc.wait() self.active_plan = "" diff --git a/tftui/ui.tcss b/tftui/ui.tcss index 7981ab3..270a535 100644 --- a/tftui/ui.tcss +++ b/tftui/ui.tcss @@ -64,7 +64,7 @@ HelpModal { grid-rows: 1fr 3; padding: 0 1; width: 88; - height: 22; + height: 23; border: thick $background 80%; background: $surface; }