From dca6ab90dab02c23fa697a271d600bef81913334 Mon Sep 17 00:00:00 2001 From: Shahryar Tayeb Date: Sun, 17 Dec 2023 14:22:08 +0430 Subject: [PATCH 01/16] MVP running successfully --- .gitignore | 142 +++++++ pyproject.toml | 1 + src/django_tui/management/commands/ish.py | 391 ++++++++++++++++++++ src/django_tui/management/commands/ish.tcss | 7 + 4 files changed, 541 insertions(+) create mode 100644 src/django_tui/management/commands/ish.py create mode 100644 src/django_tui/management/commands/ish.tcss diff --git a/.gitignore b/.gitignore index a60cd30..ee4fccc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,144 @@ dist tests/db.sqlite3 +# Django # +*.log +*.pot +*.pyc +__pycache__ +db.sqlite3 +media + +# Backup files # +*.bak + +# If you are using PyCharm # +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# File-based project format +*.iws + +# IntelliJ +out/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Python # +*.py[cod] +*$py.class + +# Distribution / packaging +.Python build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +.pytest_cache/ +nosetests.xml +coverage.xml +*.cover +.hypothesis/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery +celerybeat-schedule.* + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ + +# Sublime Text # +*.tmlanguage.cache +*.tmPreferences.cache +*.stTheme.cache +*.sublime-workspace +*.sublime-project + +# sftp configuration file +sftp-config.json + +# Package control specific files Package +Control.last-run +Control.ca-list +Control.ca-bundle +Control.system-ca-bundle +GitHub.sublime-settings + +# Visual Studio Code # +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +.history + +node_modules +static/build +.vite +staticfiles \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index d675929..bae3d14 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,6 +37,7 @@ dependencies = [ "django>=3.2", "textual", "trogon", + "textual[syntax]", ] [project.urls] diff --git a/src/django_tui/management/commands/ish.py b/src/django_tui/management/commands/ish.py new file mode 100644 index 0000000..0fa099e --- /dev/null +++ b/src/django_tui/management/commands/ish.py @@ -0,0 +1,391 @@ +from __future__ import annotations + +import os +import shlex +import sys +from pathlib import Path +from subprocess import run +from typing import Any +from webbrowser import open as open_url + +import click +from django.core.management import BaseCommand, get_commands, load_command_class +from rich.console import Console +from rich.highlighter import ReprHighlighter +from rich.text import Text +from textual import events, on +from textual.app import App, AutopilotCallbackType, ComposeResult +from textual.binding import Binding +from textual.containers import Horizontal, Vertical, VerticalScroll, HorizontalScroll +from textual.css.query import NoMatches +from textual.screen import Screen +from textual.widgets import ( + Button, + Footer, + Label, + Static, + Tree, + Header, +) +from textual.widgets.tree import TreeNode +from trogon.introspect import ArgumentSchema, CommandSchema, MultiValueParamData, OptionSchema +from trogon.run_command import UserCommandData +from trogon.widgets.about import TextDialog +from trogon.widgets.command_info import CommandInfo +from trogon.widgets.command_tree import CommandTree +from trogon.widgets.form import CommandForm +from trogon.widgets.multiple_choice import NonFocusableVerticalScroll +from textual.widgets import TextArea,Static +import django +import traceback +import importlib +import warnings +from django.apps import apps + + +from pprint import PrettyPrinter +from textual.widgets.text_area import Selection +from textual.widgets import Markdown +from textual.screen import ModalScreen +from textual.containers import Center +from textual.widgets._button import ButtonVariant +from textual.widgets import MarkdownViewer + +try: + # Only for python 2 + from StringIO import StringIO +except ImportError: + # For python 3 + from io import StringIO + + + +def get_py_version(): + ver = sys.version_info + return "{0}.{1}.{2}".format(ver.major, ver.minor, ver.micro) + +def get_dj_version(): + return django.__version__ + + +DEFAULT_IMPORT = { + 'django.db.models': [ + 'Avg', + 'Case', + 'Count', + 'F', + 'Max', + 'Min', + 'Prefetch', + 'Q', + 'Sum', + 'When', + ], + 'django.conf': [ + 'settings', + ], + 'django.core.cache': [ + 'cache', + ], + 'django.contrib.auth': [ + 'get_user_model', + ], + 'django.utils': [ + 'timezone', + ], + 'django.urls': [ + 'reverse' + ], +} + +class Importer(object): + + def __init__(self, import_django=None, import_models=None, extra_imports=None): + self.import_django = import_django or True + self.import_models = import_models or True + self.FROM_DJANGO = DEFAULT_IMPORT + if extra_imports is not None and isinstance(extra_imports, dict): + self.FROM_DJANGO.update(extra_imports) + + _mods = None + + def get_modules(self): + """ + Return list of modules and symbols to import + """ + if self._mods is None: + self._mods = {} + + if self.import_django and self.FROM_DJANGO: + + for module_name, symbols in self.FROM_DJANGO.items(): + try: + module = importlib.import_module(module_name) + except ImportError as e: + warnings.warn( + "django_admin_shell - autoimport warning :: {msg}".format( + msg=str(e) + ), + ImportWarning + ) + continue + + self._mods[module_name] = [] + for symbol_name in symbols: + if hasattr(module, symbol_name): + self._mods[module_name].append(symbol_name) + else: + warnings.warn( + "django_admin_shell - autoimport warning :: " + "AttributeError module '{mod}' has no attribute '{attr}'".format( + mod=module_name, + attr=symbol_name + ), + ImportWarning + ) + + if self.import_models: + for model_class in apps.get_models(): + _mod = model_class.__module__ + classes = self._mods.get(_mod, []) + classes.append(model_class.__name__) + self._mods[_mod] = classes + + return self._mods + + _scope = None + + def get_scope(self): + """ + Return map with symbols to module/object + Like: + "reverse" -> "django.urls.reverse" + """ + if self._scope is None: + self._scope = {} + for module_name, symbols in self.get_modules().items(): + module = importlib.import_module(module_name) + for symbol_name in symbols: + self._scope[symbol_name] = getattr( + module, + symbol_name + ) + + return self._scope + + def clear_scope(self): + """ + clear the scope. + + Freeing declared variables to be garbage collected. + """ + self._scope = None + + def __str__(self): + buf = "" + for module, symbols in self.get_modules().items(): + if symbols: + buf += "from {mod} import {symbols}\n".format( + mod=module, + symbols=", ".join(symbols) + ) + return buf + +class Runner(object): + + def __init__(self): + self.importer = Importer() + + def run_code(self, code): + """ + Execute code and return result with status = success|error + Function manipulate stdout to grab output from exec + """ + status = "success" + out = "" + tmp_stdout = sys.stdout + buf = StringIO() + + try: + sys.stdout = buf + exec(code, None, self.importer.get_scope()) + # exec(code, globals()) + except Exception: + out = traceback.format_exc() + status = 'error' + else: + out = buf.getvalue() + finally: + sys.stdout = tmp_stdout + + result = { + 'code': code, + 'out': out, + 'status': status, + } + return result + + +class ExtendedTextArea(TextArea): + """A subclass of TextArea with parenthesis-closing functionality.""" + + def _on_key(self, event: events.Key) -> None: + if event.character == "(": + self.insert("()") + self.move_cursor_relative(columns=-1) + event.prevent_default() + + +class TextEditorBingingsInfo(ModalScreen[None]): + BINDINGS = [ + Binding("escape", "dismiss(None)", "", show=False), + ] + + DEFAULT_CSS = """ + MarkdownViewer { + align: center middle; + } + + MarkdownViewer Center { + width: 80%; + } + + MarkdownViewer > Vertical { + background: $boost; + min-width: 30%; + border: round blue; + } + +""" + + key_bindings = """ +Text Editor Key Bindings List +| Key(s) | Description | +|-------------|---------------------------------------------| +| escape | Focus on the next item. | +| up | Move the cursor up. | +| down | Move the cursor down. | +| left | Move the cursor left. | +| ctrl+left | Move the cursor to the start of the word. | +| ctrl+shift+left | Move the cursor to the start of the word and select. | +| right | Move the cursor right. | +| ctrl+right | Move the cursor to the end of the word. | +| ctrl+shift+right | Move the cursor to the end of the word and select. | +| home,ctrl+a | Move the cursor to the start of the line. | +| end,ctrl+e | Move the cursor to the end of the line. | +| shift+home | Move the cursor to the start of the line and select. | +| shift+end | Move the cursor to the end of the line and select. | +| pageup | Move the cursor one page up. | +| pagedown | Move the cursor one page down. | +| shift+up | Select while moving the cursor up. | +| shift+down | Select while moving the cursor down. | +| shift+left | Select while moving the cursor left. | +| shift+right | Select while moving the cursor right. | +| backspace | Delete character to the left of cursor. | +| ctrl+w | Delete from cursor to start of the word. | +| delete,ctrl+d | Delete character to the right of cursor. | +| ctrl+f | Delete from cursor to end of the word. | +| ctrl+x | Delete the current line. | +| ctrl+u | Delete from cursor to the start of the line. | +| ctrl+k | Delete from cursor to the end of the line. | +| f6 | Select the current line. | +| f7 | Select all text in the document. | +""" + _title = "Editor Keys Bindings" + + def compose(self) -> ComposeResult: + """Compose the content of the modal dialog.""" + with Vertical(): + yield MarkdownViewer(self.key_bindings,classes="spaced",show_table_of_contents=False) +class AboutDialog(TextDialog): + + DEFAULT_CSS = """ + TextDialog > Vertical { + border: thick $primary 50%; + } + """ + + def __init__(self) -> None: + title = "About" + message = "Test" + super().__init__(title, message) + +class ShellApp(App): + CSS_PATH = "ish.tcss" + + input_tarea = ExtendedTextArea("", language="python", theme="dracula") + output_tarea = TextArea("# Output", language="python", theme="dracula",classes="text-area") + + runner = Runner() + + BINDINGS = [ + Binding(key="ctrl+r", action="test", description="Run the query"), + Binding(key="ctrl+z", action="copy_command", description="Copy to Clipboard"), + Binding(key="f1", action="editor_keys", description="Key Bindings"), + Binding(key="q", action="quit", description="Quit"), + ] + + def compose(self) -> ComposeResult: + self.input_tarea.focus() + yield HorizontalScroll( + self.input_tarea, + self.output_tarea, + ) + yield Label(f"Python: {get_py_version()} Django: {get_dj_version()}") + yield Footer() + + def action_test(self) -> None: + # get Code from start till the position of the cursor + self.input_tarea.selection = Selection(start=(0, 0), end=self.input_tarea.cursor_location) + code = self.input_tarea.get_text_range(start=(0,0),end=self.input_tarea.cursor_location) + + + + if len(code) > 0: + # Because the cli - texualize is running on a loop - has an event loop + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'rest.settings') + os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true" + django.setup() + + result = self.runner.run_code(code) + + printer = PrettyPrinter() + formatted = printer.pformat(result["out"]) + + self.output_tarea.load_text(formatted) + + def action_copy_command(self) -> None: + if sys.platform == "win32": + copy_command = ["clip"] + elif sys.platform == "darwin": + copy_command = ["pbcopy"] + else: + copy_command = ["xclip", "-selection", "clipboard"] + + try: + text_to_copy = self.input_tarea.selected_text + + # self.notify(f"`{copy_command}`") + # command = 'echo ' + text.strip() + '| clip' + # os.system(command) + + run( + copy_command, + input=text_to_copy, + text=True, + check=False, + ) + self.notify("Selction copied to clipboard.") + except FileNotFoundError: + self.notify(f"Could not copy to clipboard. `{copy_command[0]}` not found.", severity="error") + + def action_editor_keys(self) -> None: + # self.notify(f"Selction:{self.input_tarea.BINDINGS}") + self.app.push_screen(TextEditorBingingsInfo()) + +class Command(BaseCommand): + help = """Run and inspect Django commands in a text-based user interface (TUI).""" + + def handle(self, *args: Any, **options: Any) -> None: + app = ShellApp() + app.run() diff --git a/src/django_tui/management/commands/ish.tcss b/src/django_tui/management/commands/ish.tcss new file mode 100644 index 0000000..90e342a --- /dev/null +++ b/src/django_tui/management/commands/ish.tcss @@ -0,0 +1,7 @@ +.text-area{ + border-left: solid rgb(28, 28, 109); +} + +.status_bar{ + background: blue; +} \ No newline at end of file From 24e816193855212a88f5460bc6d4ef874d538662 Mon Sep 17 00:00:00 2001 From: Shahryar Tayeb Date: Sun, 17 Dec 2023 14:34:24 +0430 Subject: [PATCH 02/16] move the cursor to the end of the line in execution --- src/django_tui/management/commands/ish.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/django_tui/management/commands/ish.py b/src/django_tui/management/commands/ish.py index 0fa099e..61ec54f 100644 --- a/src/django_tui/management/commands/ish.py +++ b/src/django_tui/management/commands/ish.py @@ -337,13 +337,12 @@ def compose(self) -> ComposeResult: def action_test(self) -> None: # get Code from start till the position of the cursor self.input_tarea.selection = Selection(start=(0, 0), end=self.input_tarea.cursor_location) + self.input_tarea.action_cursor_line_end() code = self.input_tarea.get_text_range(start=(0,0),end=self.input_tarea.cursor_location) - - if len(code) > 0: # Because the cli - texualize is running on a loop - has an event loop - os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'rest.settings') + # os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'rest.settings') os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true" django.setup() From ecda536beec095d161bf7b025a7c451b23d5d916 Mon Sep 17 00:00:00 2001 From: Shahryar Tayeb Date: Sun, 17 Dec 2023 14:46:08 +0430 Subject: [PATCH 03/16] cleaned unused imports --- src/django_tui/management/commands/ish.py | 54 +++++------------------ 1 file changed, 10 insertions(+), 44 deletions(-) diff --git a/src/django_tui/management/commands/ish.py b/src/django_tui/management/commands/ish.py index 61ec54f..e746ab4 100644 --- a/src/django_tui/management/commands/ish.py +++ b/src/django_tui/management/commands/ish.py @@ -1,41 +1,20 @@ from __future__ import annotations import os -import shlex import sys -from pathlib import Path from subprocess import run from typing import Any -from webbrowser import open as open_url - -import click -from django.core.management import BaseCommand, get_commands, load_command_class -from rich.console import Console -from rich.highlighter import ReprHighlighter -from rich.text import Text -from textual import events, on -from textual.app import App, AutopilotCallbackType, ComposeResult + +from django.core.management import BaseCommand +from textual import events +from textual.app import App, ComposeResult from textual.binding import Binding -from textual.containers import Horizontal, Vertical, VerticalScroll, HorizontalScroll -from textual.css.query import NoMatches -from textual.screen import Screen +from textual.containers import Vertical,HorizontalScroll from textual.widgets import ( - Button, Footer, Label, - Static, - Tree, - Header, ) -from textual.widgets.tree import TreeNode -from trogon.introspect import ArgumentSchema, CommandSchema, MultiValueParamData, OptionSchema -from trogon.run_command import UserCommandData -from trogon.widgets.about import TextDialog -from trogon.widgets.command_info import CommandInfo -from trogon.widgets.command_tree import CommandTree -from trogon.widgets.form import CommandForm -from trogon.widgets.multiple_choice import NonFocusableVerticalScroll -from textual.widgets import TextArea,Static +from textual.widgets import TextArea import django import traceback import importlib @@ -45,10 +24,7 @@ from pprint import PrettyPrinter from textual.widgets.text_area import Selection -from textual.widgets import Markdown from textual.screen import ModalScreen -from textual.containers import Center -from textual.widgets._button import ButtonVariant from textual.widgets import MarkdownViewer try: @@ -59,7 +35,6 @@ from io import StringIO - def get_py_version(): ver = sys.version_info return "{0}.{1}.{2}".format(ver.major, ver.minor, ver.micro) @@ -225,7 +200,6 @@ def run_code(self, code): } return result - class ExtendedTextArea(TextArea): """A subclass of TextArea with parenthesis-closing functionality.""" @@ -235,6 +209,10 @@ def _on_key(self, event: events.Key) -> None: self.move_cursor_relative(columns=-1) event.prevent_default() + if event.character == '"': + self.insert('""') + self.move_cursor_relative(columns=-1) + event.prevent_default() class TextEditorBingingsInfo(ModalScreen[None]): BINDINGS = [ @@ -297,18 +275,6 @@ def compose(self) -> ComposeResult: """Compose the content of the modal dialog.""" with Vertical(): yield MarkdownViewer(self.key_bindings,classes="spaced",show_table_of_contents=False) -class AboutDialog(TextDialog): - - DEFAULT_CSS = """ - TextDialog > Vertical { - border: thick $primary 50%; - } - """ - - def __init__(self) -> None: - title = "About" - message = "Test" - super().__init__(title, message) class ShellApp(App): CSS_PATH = "ish.tcss" From cba56017c5b2f8fc604cac62899fd9be42a2f2c8 Mon Sep 17 00:00:00 2001 From: Shahryar Tayeb Date: Sun, 17 Dec 2023 15:39:04 +0430 Subject: [PATCH 04/16] split screens for commands and shell --- src/django_tui/management/commands/ish.py | 24 ++++++++--------- src/django_tui/management/commands/tui.py | 32 ++++++++++++++++++++--- 2 files changed, 41 insertions(+), 15 deletions(-) diff --git a/src/django_tui/management/commands/ish.py b/src/django_tui/management/commands/ish.py index e746ab4..45d33c0 100644 --- a/src/django_tui/management/commands/ish.py +++ b/src/django_tui/management/commands/ish.py @@ -3,11 +3,9 @@ import os import sys from subprocess import run -from typing import Any -from django.core.management import BaseCommand from textual import events -from textual.app import App, ComposeResult +from textual.app import ComposeResult from textual.binding import Binding from textual.containers import Vertical,HorizontalScroll from textual.widgets import ( @@ -24,7 +22,7 @@ from pprint import PrettyPrinter from textual.widgets.text_area import Selection -from textual.screen import ModalScreen +from textual.screen import ModalScreen,Screen from textual.widgets import MarkdownViewer try: @@ -276,7 +274,16 @@ def compose(self) -> ComposeResult: with Vertical(): yield MarkdownViewer(self.key_bindings,classes="spaced",show_table_of_contents=False) -class ShellApp(App): +class InteractiveShellScreen(Screen): + + def __init__( + self, + name: str | None = None, + id: str | None = None, + classes: str | None = None, + ): + super().__init__(name, id, classes) + CSS_PATH = "ish.tcss" input_tarea = ExtendedTextArea("", language="python", theme="dracula") @@ -347,10 +354,3 @@ def action_copy_command(self) -> None: def action_editor_keys(self) -> None: # self.notify(f"Selction:{self.input_tarea.BINDINGS}") self.app.push_screen(TextEditorBingingsInfo()) - -class Command(BaseCommand): - help = """Run and inspect Django commands in a text-based user interface (TUI).""" - - def handle(self, *args: Any, **options: Any) -> None: - app = ShellApp() - app.run() diff --git a/src/django_tui/management/commands/tui.py b/src/django_tui/management/commands/tui.py index bb9ef7e..990140c 100644 --- a/src/django_tui/management/commands/tui.py +++ b/src/django_tui/management/commands/tui.py @@ -25,6 +25,7 @@ Label, Static, Tree, + Header, ) from textual.widgets.tree import TreeNode from trogon.introspect import ArgumentSchema, CommandSchema, MultiValueParamData, OptionSchema @@ -34,7 +35,7 @@ from trogon.widgets.command_tree import CommandTree from trogon.widgets.form import CommandForm from trogon.widgets.multiple_choice import NonFocusableVerticalScroll - +from .ish import InteractiveShellScreen def introspect_django_commands() -> dict[str, CommandSchema]: groups = {} @@ -142,7 +143,6 @@ def introspect_django_commands() -> dict[str, CommandSchema]: return groups - class AboutDialog(TextDialog): DEFAULT_CSS = """ TextDialog > Vertical { @@ -161,6 +161,7 @@ def __init__(self) -> None: super().__init__(title, message) +# 2 For the command screen class DjangoCommandBuilder(Screen): COMPONENT_CLASSES = {"version-string", "prompt", "command-name-syntax"} @@ -337,6 +338,30 @@ async def _update_form_body(self, node: TreeNode[CommandSchema]) -> None: if not self.is_grouped_cli: command_form.focus() +# 1 The main screen +class HomeScreen(Screen): + def __init__( + self, + name: str | None = None, + id: str | None = None, + classes: str | None = None, + ): + super().__init__(name, id, classes) + + def compose(self) -> ComposeResult: + yield Header() + yield Button("Django Commands", id="commands", variant="success") + yield Button("Django Shell", id="shell", variant="error") + yield Footer() + + def on_button_pressed(self, event: Button.Pressed) -> None: + """Event handler called when a button is pressed.""" + if event.button.id == "commands": + self.app.push_screen(DjangoCommandBuilder("pyhton manage.py", "Test command name")) + + elif event.button.id == "shell": + self.app.push_screen(InteractiveShellScreen("Interactive Shell")) + class DjangoTui(App): CSS_PATH = Path(__file__).parent / "trogon.scss" @@ -352,7 +377,8 @@ def __init__( self.command_name = "django-tui" def on_mount(self): - self.push_screen(DjangoCommandBuilder(self.app_name, self.command_name)) + # self.push_screen(DjangoCommandBuilder(self.app_name, self.command_name)) + self.push_screen(HomeScreen(self.app_name)) @on(Button.Pressed, "#home-exec-button") def on_button_pressed(self): From f63de8ec73f0e9bb4f2499964e82c415e951f9de Mon Sep 17 00:00:00 2001 From: Shahryar Tayeb Date: Sun, 17 Dec 2023 15:47:28 +0430 Subject: [PATCH 05/16] Cleanup css --- src/django_tui/management/commands/ish.py | 2 -- src/django_tui/management/commands/ish.tcss | 7 ------- src/django_tui/management/commands/trogon.scss | 8 ++++++++ 3 files changed, 8 insertions(+), 9 deletions(-) delete mode 100644 src/django_tui/management/commands/ish.tcss diff --git a/src/django_tui/management/commands/ish.py b/src/django_tui/management/commands/ish.py index 45d33c0..2150014 100644 --- a/src/django_tui/management/commands/ish.py +++ b/src/django_tui/management/commands/ish.py @@ -284,8 +284,6 @@ def __init__( ): super().__init__(name, id, classes) - CSS_PATH = "ish.tcss" - input_tarea = ExtendedTextArea("", language="python", theme="dracula") output_tarea = TextArea("# Output", language="python", theme="dracula",classes="text-area") diff --git a/src/django_tui/management/commands/ish.tcss b/src/django_tui/management/commands/ish.tcss deleted file mode 100644 index 90e342a..0000000 --- a/src/django_tui/management/commands/ish.tcss +++ /dev/null @@ -1,7 +0,0 @@ -.text-area{ - border-left: solid rgb(28, 28, 109); -} - -.status_bar{ - background: blue; -} \ No newline at end of file diff --git a/src/django_tui/management/commands/trogon.scss b/src/django_tui/management/commands/trogon.scss index 60bb3b4..d602acd 100644 --- a/src/django_tui/management/commands/trogon.scss +++ b/src/django_tui/management/commands/trogon.scss @@ -286,3 +286,11 @@ Select.command-form-select:focus SelectCurrent { border: tall $accent; } + +.text-area{ + border-left: solid $accent; +} + +.status_bar{ + background: $primary-darken-3; +} \ No newline at end of file From b8b97df9006b530220a20acc864623baa24a3909 Mon Sep 17 00:00:00 2001 From: Shahryar Tayeb Date: Sun, 17 Dec 2023 16:05:31 +0430 Subject: [PATCH 06/16] go back command --- src/django_tui/management/commands/ish.py | 2 +- src/django_tui/management/commands/tui.py | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/django_tui/management/commands/ish.py b/src/django_tui/management/commands/ish.py index 2150014..74dcde2 100644 --- a/src/django_tui/management/commands/ish.py +++ b/src/django_tui/management/commands/ish.py @@ -293,7 +293,7 @@ def __init__( Binding(key="ctrl+r", action="test", description="Run the query"), Binding(key="ctrl+z", action="copy_command", description="Copy to Clipboard"), Binding(key="f1", action="editor_keys", description="Key Bindings"), - Binding(key="q", action="quit", description="Quit"), + Binding(key="q", action="back", description="Go Back"), ] def compose(self) -> ComposeResult: diff --git a/src/django_tui/management/commands/tui.py b/src/django_tui/management/commands/tui.py index 990140c..bebce42 100644 --- a/src/django_tui/management/commands/tui.py +++ b/src/django_tui/management/commands/tui.py @@ -171,6 +171,7 @@ class DjangoCommandBuilder(Screen): Binding(key="ctrl+t", action="focus_command_tree", description="Focus Command Tree"), # Binding(key="ctrl+o", action="show_command_info", description="Command Info"), Binding(key="ctrl+s", action="focus('search')", description="Search"), + Binding(key="q", action="back", description="Go Back"), Binding(key="f1", action="about", description="About"), ] @@ -366,6 +367,10 @@ def on_button_pressed(self, event: Button.Pressed) -> None: class DjangoTui(App): CSS_PATH = Path(__file__).parent / "trogon.scss" + # SCREENS = { + # "home": HomeScreen, + # } + def __init__( self, ) -> None: From a78381f31ec1829bd7f08eef59f8d0c4a7211292 Mon Sep 17 00:00:00 2001 From: Shahryar Tayeb Date: Sun, 17 Dec 2023 20:28:04 +0430 Subject: [PATCH 07/16] shell screen freeze fix --- src/django_tui/management/commands/ish.py | 34 +++++++++++-------- src/django_tui/management/commands/tui.py | 41 ++++++++++++++++------- 2 files changed, 49 insertions(+), 26 deletions(-) diff --git a/src/django_tui/management/commands/ish.py b/src/django_tui/management/commands/ish.py index 74dcde2..20e2553 100644 --- a/src/django_tui/management/commands/ish.py +++ b/src/django_tui/management/commands/ish.py @@ -218,18 +218,24 @@ class TextEditorBingingsInfo(ModalScreen[None]): ] DEFAULT_CSS = """ - MarkdownViewer { + + TextEditorBingingsInfo { align: center middle; } - MarkdownViewer Center { - width: 80%; + #dialog { + grid-size: 2; + grid-gutter: 1 2; + grid-rows: 1fr 3; + padding: 0 1; + width: 90; + height: 20; + border: thick $background 80%; + background: $surface; } - MarkdownViewer > Vertical { - background: $boost; - min-width: 30%; - border: round blue; + Button { + width: 100%; } """ @@ -271,7 +277,7 @@ class TextEditorBingingsInfo(ModalScreen[None]): def compose(self) -> ComposeResult: """Compose the content of the modal dialog.""" - with Vertical(): + with Vertical(id="dialog"): yield MarkdownViewer(self.key_bindings,classes="spaced",show_table_of_contents=False) class InteractiveShellScreen(Screen): @@ -283,19 +289,19 @@ def __init__( classes: str | None = None, ): super().__init__(name, id, classes) - - input_tarea = ExtendedTextArea("", language="python", theme="dracula") - output_tarea = TextArea("# Output", language="python", theme="dracula",classes="text-area") - - runner = Runner() + self.runner = Runner() + self.input_tarea = ExtendedTextArea("", language="python", theme="dracula") + self.output_tarea = TextArea("# Output", language="python", theme="dracula",classes="text-area") + BINDINGS = [ Binding(key="ctrl+r", action="test", description="Run the query"), Binding(key="ctrl+z", action="copy_command", description="Copy to Clipboard"), Binding(key="f1", action="editor_keys", description="Key Bindings"), - Binding(key="q", action="back", description="Go Back"), + ("escape", "app.back()", "Back") ] + def compose(self) -> ComposeResult: self.input_tarea.focus() yield HorizontalScroll( diff --git a/src/django_tui/management/commands/tui.py b/src/django_tui/management/commands/tui.py index bebce42..993d1ec 100644 --- a/src/django_tui/management/commands/tui.py +++ b/src/django_tui/management/commands/tui.py @@ -171,7 +171,7 @@ class DjangoCommandBuilder(Screen): Binding(key="ctrl+t", action="focus_command_tree", description="Focus Command Tree"), # Binding(key="ctrl+o", action="show_command_info", description="Command Info"), Binding(key="ctrl+s", action="focus('search')", description="Search"), - Binding(key="q", action="back", description="Go Back"), + ("escape", "app.pop_screen", "Back"), Binding(key="f1", action="about", description="About"), ] @@ -340,20 +340,41 @@ async def _update_form_body(self, node: TreeNode[CommandSchema]) -> None: command_form.focus() # 1 The main screen +# BUG: in switching screen - the shell app stucks class HomeScreen(Screen): - def __init__( - self, - name: str | None = None, - id: str | None = None, - classes: str | None = None, - ): - super().__init__(name, id, classes) + + CSS = """ + Screen { + layout: grid; + grid-size: 2; + grid-gutter: 2; + padding: 2; + } + """ + + BINDINGS = [ + Binding(key="s", action="action_select_mode('shell')", description="Shell"), + Binding(key="c", action="action_select_mode('commands')", description="Commands"), + Binding(key="t", action="action_hello", description="Hello"), + ] + def compose(self) -> ComposeResult: yield Header() yield Button("Django Commands", id="commands", variant="success") yield Button("Django Shell", id="shell", variant="error") yield Footer() + + def action_hello(self) -> None: + self.notify("hello") + + def action_select_mode(self,mode_id:str) -> None: + self.notify(mode_id) + if mode_id == "commands": + self.app.push_screen(DjangoCommandBuilder("pyhton manage.py", "Test command name")) + + elif mode_id == "shell": + self.app.push_screen(InteractiveShellScreen("Interactive Shell")) def on_button_pressed(self, event: Button.Pressed) -> None: """Event handler called when a button is pressed.""" @@ -367,10 +388,6 @@ def on_button_pressed(self, event: Button.Pressed) -> None: class DjangoTui(App): CSS_PATH = Path(__file__).parent / "trogon.scss" - # SCREENS = { - # "home": HomeScreen, - # } - def __init__( self, ) -> None: From ba1ba94340abb3a7d8a37d70306049afe1441849 Mon Sep 17 00:00:00 2001 From: Shahryar Tayeb Date: Sun, 17 Dec 2023 20:32:43 +0430 Subject: [PATCH 08/16] MVP WIP --- src/django_tui/management/commands/tui.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/django_tui/management/commands/tui.py b/src/django_tui/management/commands/tui.py index 993d1ec..4545c3c 100644 --- a/src/django_tui/management/commands/tui.py +++ b/src/django_tui/management/commands/tui.py @@ -340,7 +340,6 @@ async def _update_form_body(self, node: TreeNode[CommandSchema]) -> None: command_form.focus() # 1 The main screen -# BUG: in switching screen - the shell app stucks class HomeScreen(Screen): CSS = """ @@ -353,16 +352,16 @@ class HomeScreen(Screen): """ BINDINGS = [ - Binding(key="s", action="action_select_mode('shell')", description="Shell"), - Binding(key="c", action="action_select_mode('commands')", description="Commands"), - Binding(key="t", action="action_hello", description="Hello"), + Binding(key="s", action="select_mode('shell')", description="Shell"), + Binding(key="c", action="select_mode('commands')", description="Commands"), + Binding(key="t", action="hello", description="Hello"), ] def compose(self) -> ComposeResult: yield Header() - yield Button("Django Commands", id="commands", variant="success") - yield Button("Django Shell", id="shell", variant="error") + yield Button("C - Django Commands", id="commands", variant="success") + yield Button("S - Django Shell", id="shell", variant="error") yield Footer() def action_hello(self) -> None: From c8ab849e4ab4259915b57ef5ca0c3df8da8ace66 Mon Sep 17 00:00:00 2001 From: Shahryar Tayeb Date: Sun, 17 Dec 2023 20:34:21 +0430 Subject: [PATCH 09/16] WIP ready --- src/django_tui/management/commands/tui.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/django_tui/management/commands/tui.py b/src/django_tui/management/commands/tui.py index 4545c3c..23cc4eb 100644 --- a/src/django_tui/management/commands/tui.py +++ b/src/django_tui/management/commands/tui.py @@ -160,7 +160,6 @@ def __init__(self) -> None: ) super().__init__(title, message) - # 2 For the command screen class DjangoCommandBuilder(Screen): COMPONENT_CLASSES = {"version-string", "prompt", "command-name-syntax"} @@ -368,7 +367,6 @@ def action_hello(self) -> None: self.notify("hello") def action_select_mode(self,mode_id:str) -> None: - self.notify(mode_id) if mode_id == "commands": self.app.push_screen(DjangoCommandBuilder("pyhton manage.py", "Test command name")) @@ -383,7 +381,6 @@ def on_button_pressed(self, event: Button.Pressed) -> None: elif event.button.id == "shell": self.app.push_screen(InteractiveShellScreen("Interactive Shell")) - class DjangoTui(App): CSS_PATH = Path(__file__).parent / "trogon.scss" @@ -453,7 +450,6 @@ def action_visit(self, url: str) -> None: """ open_url(url) - class Command(BaseCommand): help = """Run and inspect Django commands in a text-based user interface (TUI).""" From bc24d1f84a190467d2f3d31572c36f71005324e2 Mon Sep 17 00:00:00 2001 From: Shahryar Tayeb Date: Mon, 18 Dec 2023 11:29:30 +0430 Subject: [PATCH 10/16] some cleanups --- src/django_tui/management/commands/ish.py | 31 +++++++++++++++++------ 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/src/django_tui/management/commands/ish.py b/src/django_tui/management/commands/ish.py index 20e2553..eaa0790 100644 --- a/src/django_tui/management/commands/ish.py +++ b/src/django_tui/management/commands/ish.py @@ -20,7 +20,6 @@ from django.apps import apps -from pprint import PrettyPrinter from textual.widgets.text_area import Selection from textual.screen import ModalScreen,Screen from textual.widgets import MarkdownViewer @@ -42,6 +41,10 @@ def get_dj_version(): DEFAULT_IMPORT = { + 'rich':[ + 'print_json', + 'print' + ], 'django.db.models': [ 'Avg', 'Case', @@ -207,11 +210,26 @@ def _on_key(self, event: events.Key) -> None: self.move_cursor_relative(columns=-1) event.prevent_default() + if event.character == "[": + self.insert("[]") + self.move_cursor_relative(columns=-1) + event.prevent_default() + + if event.character == "{": + self.insert("{}") + self.move_cursor_relative(columns=-1) + event.prevent_default() + if event.character == '"': self.insert('""') self.move_cursor_relative(columns=-1) event.prevent_default() + if event.character == "'": + self.insert("''") + self.move_cursor_relative(columns=-1) + event.prevent_default() + class TextEditorBingingsInfo(ModalScreen[None]): BINDINGS = [ Binding("escape", "dismiss(None)", "", show=False), @@ -290,8 +308,8 @@ def __init__( ): super().__init__(name, id, classes) self.runner = Runner() - self.input_tarea = ExtendedTextArea("", language="python", theme="dracula") - self.output_tarea = TextArea("# Output", language="python", theme="dracula",classes="text-area") + self.input_tarea = ExtendedTextArea("",id="input", language="python", theme="dracula") + self.output_tarea = TextArea("# Output", id="output",language="python", theme="dracula",classes="text-area") BINDINGS = [ @@ -304,6 +322,7 @@ def __init__( def compose(self) -> ComposeResult: self.input_tarea.focus() + yield HorizontalScroll( self.input_tarea, self.output_tarea, @@ -324,11 +343,7 @@ def action_test(self) -> None: django.setup() result = self.runner.run_code(code) - - printer = PrettyPrinter() - formatted = printer.pformat(result["out"]) - - self.output_tarea.load_text(formatted) + self.output_tarea.load_text(result["out"]) def action_copy_command(self) -> None: if sys.platform == "win32": From 7965067f3d81a3ce7d155c9e03c9167219056341 Mon Sep 17 00:00:00 2001 From: Shahryar Tayeb Date: Mon, 18 Dec 2023 13:13:46 +0430 Subject: [PATCH 11/16] add toggle comment action --- src/django_tui/management/commands/ish.py | 54 ++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/src/django_tui/management/commands/ish.py b/src/django_tui/management/commands/ish.py index eaa0790..6788152 100644 --- a/src/django_tui/management/commands/ish.py +++ b/src/django_tui/management/commands/ish.py @@ -20,9 +20,10 @@ from django.apps import apps -from textual.widgets.text_area import Selection +from textual.widgets.text_area import Selection,Location from textual.screen import ModalScreen,Screen from textual.widgets import MarkdownViewer +from typing import List, Tuple try: # Only for python 2 @@ -315,6 +316,7 @@ def __init__( BINDINGS = [ Binding(key="ctrl+r", action="test", description="Run the query"), Binding(key="ctrl+z", action="copy_command", description="Copy to Clipboard"), + Binding(key="ctrl+underscore", action="toggle_comment", description="Toggle Comment",show=True), Binding(key="f1", action="editor_keys", description="Key Bindings"), ("escape", "app.back()", "Back") ] @@ -370,6 +372,56 @@ def action_copy_command(self) -> None: except FileNotFoundError: self.notify(f"Could not copy to clipboard. `{copy_command[0]}` not found.", severity="error") + def _get_selected_lines(self) -> Tuple[List[str], Location, Location]: + [first, last] = sorted([self.input_tarea.selection.start, self.input_tarea.selection.end]) + lines = [self.input_tarea.document.get_line(i) for i in range(first[0], last[0] + 1)] + return lines, first, last + + def action_toggle_comment(self) -> None: + # Setup for multiple language support + # INLINE_MARKERS = { + # "python": "#", + # "py": "#", + # } + # inline_comment_marker = INLINE_MARKERS.get(self.input_tarea.language) + inline_comment_marker = "#" + + if inline_comment_marker: + lines, first, last = self._get_selected_lines() + stripped_lines = [line.lstrip() for line in lines] + indents = [len(line) - len(line.lstrip()) for line in lines] + # if lines are already commented, remove them + if lines and all( + not line or line.startswith(inline_comment_marker) + for line in stripped_lines + ): + offsets = [ + 0 + if not line + else (2 if line[len(inline_comment_marker)].isspace() else 1) + for line in stripped_lines + ] + for lno, indent, offset in zip( + range(first[0], last[0] + 1), indents, offsets + ): + self.input_tarea.delete( + start=(lno, indent), + end=(lno, indent + offset), + maintain_selection_offset=True, + ) + # add comment tokens to all lines + else: + indent = min( + [indent for indent, line in zip(indents, stripped_lines) if line] + ) + for lno, stripped_line in enumerate(stripped_lines, start=first[0]): + if stripped_line: + self.input_tarea.insert( + f"{inline_comment_marker} ", + location=(lno, indent), + maintain_selection_offset=True, + ) + def action_editor_keys(self) -> None: # self.notify(f"Selction:{self.input_tarea.BINDINGS}") self.app.push_screen(TextEditorBingingsInfo()) From b272e347d88454abb629a27c8c619c2a4f39de76 Mon Sep 17 00:00:00 2001 From: Shahryar Tayeb Date: Mon, 18 Dec 2023 13:42:09 +0430 Subject: [PATCH 12/16] display default imported modules --- src/django_tui/management/commands/ish.py | 63 ++++++++++++------- .../management/commands/trogon.scss | 13 +++- 2 files changed, 53 insertions(+), 23 deletions(-) diff --git a/src/django_tui/management/commands/ish.py b/src/django_tui/management/commands/ish.py index 6788152..d3d725e 100644 --- a/src/django_tui/management/commands/ish.py +++ b/src/django_tui/management/commands/ish.py @@ -24,6 +24,7 @@ from textual.screen import ModalScreen,Screen from textual.widgets import MarkdownViewer from typing import List, Tuple +from rich.syntax import Syntax try: # Only for python 2 @@ -231,32 +232,15 @@ def _on_key(self, event: events.Key) -> None: self.move_cursor_relative(columns=-1) event.prevent_default() -class TextEditorBingingsInfo(ModalScreen[None]): +class TextEditorBindingsInfo(ModalScreen[None]): BINDINGS = [ Binding("escape", "dismiss(None)", "", show=False), ] DEFAULT_CSS = """ - - TextEditorBingingsInfo { + TextEditorBindingsInfo { align: center middle; } - - #dialog { - grid-size: 2; - grid-gutter: 1 2; - grid-rows: 1fr 3; - padding: 0 1; - width: 90; - height: 20; - border: thick $background 80%; - background: $surface; - } - - Button { - width: 100%; - } - """ key_bindings = """ @@ -299,6 +283,38 @@ def compose(self) -> ComposeResult: with Vertical(id="dialog"): yield MarkdownViewer(self.key_bindings,classes="spaced",show_table_of_contents=False) +class DefaultImportsInfo(ModalScreen[None]): + BINDINGS = [ + Binding("escape", "dismiss(None)", "Close",), + ] + + DEFAULT_CSS = """ + DefaultImportsInfo { + align: center middle; + } +""" + + _title = "Default Imported Modules" + + def __init__(self,imported_modules:str,name: str | None = None, + id: str | None = None, + classes: str | None = None,): + self.imported_modules = imported_modules + super().__init__(name, id, classes) + + def compose(self) -> ComposeResult: + """Compose the content of the modal dialog.""" + syntax = Syntax( + code=self.imported_modules, + lexer="python", + line_numbers=True, + word_wrap=False, + indent_guides=True, + theme="dracula", + ) + with Vertical(id="dialog"): + yield Label(syntax) + class InteractiveShellScreen(Screen): def __init__( @@ -316,8 +332,9 @@ def __init__( BINDINGS = [ Binding(key="ctrl+r", action="test", description="Run the query"), Binding(key="ctrl+z", action="copy_command", description="Copy to Clipboard"), - Binding(key="ctrl+underscore", action="toggle_comment", description="Toggle Comment",show=True), + Binding(key="ctrl+underscore", action="toggle_comment", description="Toggle Comment",show=False), Binding(key="f1", action="editor_keys", description="Key Bindings"), + Binding(key="f2", action="default_imports", description="Default imports"), ("escape", "app.back()", "Back") ] @@ -332,6 +349,9 @@ def compose(self) -> ComposeResult: yield Label(f"Python: {get_py_version()} Django: {get_dj_version()}") yield Footer() + def action_default_imports(self) ->None: + self.app.push_screen(DefaultImportsInfo(self.runner.importer.__str__())) + def action_test(self) -> None: # get Code from start till the position of the cursor self.input_tarea.selection = Selection(start=(0, 0), end=self.input_tarea.cursor_location) @@ -423,5 +443,4 @@ def action_toggle_comment(self) -> None: ) def action_editor_keys(self) -> None: - # self.notify(f"Selction:{self.input_tarea.BINDINGS}") - self.app.push_screen(TextEditorBingingsInfo()) + self.app.push_screen(TextEditorBindingsInfo()) diff --git a/src/django_tui/management/commands/trogon.scss b/src/django_tui/management/commands/trogon.scss index d602acd..f1b448c 100644 --- a/src/django_tui/management/commands/trogon.scss +++ b/src/django_tui/management/commands/trogon.scss @@ -293,4 +293,15 @@ Select.command-form-select:focus SelectCurrent { .status_bar{ background: $primary-darken-3; -} \ No newline at end of file +} + +#dialog { + grid-size: 2; + grid-gutter: 1 2; + grid-rows: 1fr 3; + padding: 0 1; + width: 90; + height: 20; + border: thick $background 80%; + background: $surface; + } \ No newline at end of file From 8eeeb4a3bfa61d47c93d04e54c99c1659dd62817 Mon Sep 17 00:00:00 2001 From: Shahryar Tayeb Date: Mon, 18 Dec 2023 13:55:21 +0430 Subject: [PATCH 13/16] scrollable modal for default imports --- src/django_tui/management/commands/ish.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/django_tui/management/commands/ish.py b/src/django_tui/management/commands/ish.py index d3d725e..f3eb369 100644 --- a/src/django_tui/management/commands/ish.py +++ b/src/django_tui/management/commands/ish.py @@ -313,7 +313,9 @@ def compose(self) -> ComposeResult: theme="dracula", ) with Vertical(id="dialog"): - yield Label(syntax) + yield HorizontalScroll( + Label(syntax,expand=True) + ) class InteractiveShellScreen(Screen): From 08ad5efc685c2ce27d5c01e8d1ca2f3b5168f77e Mon Sep 17 00:00:00 2001 From: Shahryar Tayeb Date: Mon, 18 Dec 2023 14:26:23 +0430 Subject: [PATCH 14/16] fixes for default import modal --- src/django_tui/management/commands/ish.py | 8 +++----- src/django_tui/management/commands/trogon.scss | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/django_tui/management/commands/ish.py b/src/django_tui/management/commands/ish.py index f3eb369..70fbd8b 100644 --- a/src/django_tui/management/commands/ish.py +++ b/src/django_tui/management/commands/ish.py @@ -7,7 +7,7 @@ from textual import events from textual.app import ComposeResult from textual.binding import Binding -from textual.containers import Vertical,HorizontalScroll +from textual.containers import Vertical,HorizontalScroll,VerticalScroll from textual.widgets import ( Footer, Label, @@ -312,10 +312,8 @@ def compose(self) -> ComposeResult: indent_guides=True, theme="dracula", ) - with Vertical(id="dialog"): - yield HorizontalScroll( - Label(syntax,expand=True) - ) + with VerticalScroll(id="dialog"): + yield Label(syntax) class InteractiveShellScreen(Screen): diff --git a/src/django_tui/management/commands/trogon.scss b/src/django_tui/management/commands/trogon.scss index f1b448c..53e90a8 100644 --- a/src/django_tui/management/commands/trogon.scss +++ b/src/django_tui/management/commands/trogon.scss @@ -300,7 +300,7 @@ Select.command-form-select:focus SelectCurrent { grid-gutter: 1 2; grid-rows: 1fr 3; padding: 0 1; - width: 90; + width: 95; height: 20; border: thick $background 80%; background: $surface; From b260b8ed4a5595a5dedbfc168250c1795841f682 Mon Sep 17 00:00:00 2001 From: Shahryar Tayeb Date: Tue, 19 Dec 2023 08:49:20 +0430 Subject: [PATCH 15/16] command args and switch to shell key binding --- src/django_tui/management/commands/ish.py | 2 +- src/django_tui/management/commands/tui.py | 26 +++++++++++++++++++---- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/django_tui/management/commands/ish.py b/src/django_tui/management/commands/ish.py index f3eb369..034c94a 100644 --- a/src/django_tui/management/commands/ish.py +++ b/src/django_tui/management/commands/ish.py @@ -337,7 +337,7 @@ def __init__( Binding(key="ctrl+underscore", action="toggle_comment", description="Toggle Comment",show=False), Binding(key="f1", action="editor_keys", description="Key Bindings"), Binding(key="f2", action="default_imports", description="Default imports"), - ("escape", "app.back()", "Back") + Binding(key="ctrl+j", action="select_mode('commands')", description="Commands"), ] diff --git a/src/django_tui/management/commands/tui.py b/src/django_tui/management/commands/tui.py index 23cc4eb..ae681b7 100644 --- a/src/django_tui/management/commands/tui.py +++ b/src/django_tui/management/commands/tui.py @@ -170,7 +170,8 @@ class DjangoCommandBuilder(Screen): Binding(key="ctrl+t", action="focus_command_tree", description="Focus Command Tree"), # Binding(key="ctrl+o", action="show_command_info", description="Command Info"), Binding(key="ctrl+s", action="focus('search')", description="Search"), - ("escape", "app.pop_screen", "Back"), + Binding(key="ctrl+j", action="select_mode('shell')", description="Shell"), + ("escape", "app.back", "Back"), Binding(key="f1", action="about", description="About"), ] @@ -386,6 +387,7 @@ class DjangoTui(App): def __init__( self, + open_shell: bool = False, ) -> None: super().__init__() self.post_run_command: list[str] = [] @@ -393,10 +395,15 @@ def __init__( self.execute_on_exit = False self.app_name = "python manage.py" self.command_name = "django-tui" + self.open_shell = open_shell + def on_mount(self): - # self.push_screen(DjangoCommandBuilder(self.app_name, self.command_name)) - self.push_screen(HomeScreen(self.app_name)) + if self.open_shell: + self.push_screen(InteractiveShellScreen("Interactive Shell")) + else: + self.push_screen(DjangoCommandBuilder(self.app_name, self.command_name)) + # self.push_screen(HomeScreen(self.app_name)) @on(Button.Pressed, "#home-exec-button") def on_button_pressed(self): @@ -450,9 +457,20 @@ def action_visit(self, url: str) -> None: """ open_url(url) + def action_select_mode(self,mode_id:str) -> None: + if mode_id == "commands": + self.app.push_screen(DjangoCommandBuilder("pyhton manage.py", "Test command name")) + + elif mode_id == "shell": + self.app.push_screen(InteractiveShellScreen("Interactive Shell")) class Command(BaseCommand): help = """Run and inspect Django commands in a text-based user interface (TUI).""" + def add_arguments(self, parser): + parser.add_argument("--shell",action="store_true", help="Open django shell") + def handle(self, *args: Any, **options: Any) -> None: - app = DjangoTui() + open_shell = options.get("shell") + + app = DjangoTui(open_shell=open_shell) app.run() From 7671c1981bc042e80b4b28d35a6ac170f9e123e8 Mon Sep 17 00:00:00 2001 From: Shahryar Tayeb Date: Tue, 19 Dec 2023 08:52:40 +0430 Subject: [PATCH 16/16] removed unused screen --- src/django_tui/management/commands/tui.py | 43 ----------------------- 1 file changed, 43 deletions(-) diff --git a/src/django_tui/management/commands/tui.py b/src/django_tui/management/commands/tui.py index ae681b7..4a6823d 100644 --- a/src/django_tui/management/commands/tui.py +++ b/src/django_tui/management/commands/tui.py @@ -339,49 +339,6 @@ async def _update_form_body(self, node: TreeNode[CommandSchema]) -> None: if not self.is_grouped_cli: command_form.focus() -# 1 The main screen -class HomeScreen(Screen): - - CSS = """ - Screen { - layout: grid; - grid-size: 2; - grid-gutter: 2; - padding: 2; - } - """ - - BINDINGS = [ - Binding(key="s", action="select_mode('shell')", description="Shell"), - Binding(key="c", action="select_mode('commands')", description="Commands"), - Binding(key="t", action="hello", description="Hello"), - ] - - - def compose(self) -> ComposeResult: - yield Header() - yield Button("C - Django Commands", id="commands", variant="success") - yield Button("S - Django Shell", id="shell", variant="error") - yield Footer() - - def action_hello(self) -> None: - self.notify("hello") - - def action_select_mode(self,mode_id:str) -> None: - if mode_id == "commands": - self.app.push_screen(DjangoCommandBuilder("pyhton manage.py", "Test command name")) - - elif mode_id == "shell": - self.app.push_screen(InteractiveShellScreen("Interactive Shell")) - - def on_button_pressed(self, event: Button.Pressed) -> None: - """Event handler called when a button is pressed.""" - if event.button.id == "commands": - self.app.push_screen(DjangoCommandBuilder("pyhton manage.py", "Test command name")) - - elif event.button.id == "shell": - self.app.push_screen(InteractiveShellScreen("Interactive Shell")) - class DjangoTui(App): CSS_PATH = Path(__file__).parent / "trogon.scss"