Add entry editing functionality with auto-save; enhance input validation and navigation
This commit is contained in:
		
							parent
							
								
									d477328bea
								
							
						
					
					
						commit
						5a117fb624
					
				
					 1 changed files with 244 additions and 3 deletions
				
			
		| 
						 | 
					@ -6,10 +6,12 @@ This module contains the main application class and entry point function.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from textual.app import App, ComposeResult
 | 
					from textual.app import App, ComposeResult
 | 
				
			||||||
from textual.containers import Horizontal, Vertical
 | 
					from textual.containers import Horizontal, Vertical
 | 
				
			||||||
from textual.widgets import Header, Footer, Static, DataTable
 | 
					from textual.widgets import Header, Footer, Static, DataTable, Input, Checkbox, Label
 | 
				
			||||||
from textual.binding import Binding
 | 
					from textual.binding import Binding
 | 
				
			||||||
from textual.reactive import reactive
 | 
					from textual.reactive import reactive
 | 
				
			||||||
from rich.text import Text
 | 
					from rich.text import Text
 | 
				
			||||||
 | 
					import ipaddress
 | 
				
			||||||
 | 
					import re
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .core.parser import HostsParser
 | 
					from .core.parser import HostsParser
 | 
				
			||||||
from .core.models import HostsFile
 | 
					from .core.models import HostsFile
 | 
				
			||||||
| 
						 | 
					@ -89,6 +91,29 @@ class HostsManagerApp(App):
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    /* DataTable row styling - colors are now handled via Rich Text objects */
 | 
					    /* DataTable row styling - colors are now handled via Rich Text objects */
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    .hidden {
 | 
				
			||||||
 | 
					        display: none;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    #entry-edit-form {
 | 
				
			||||||
 | 
					        height: auto;
 | 
				
			||||||
 | 
					        padding: 1;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    #entry-edit-form Label {
 | 
				
			||||||
 | 
					        margin-bottom: 1;
 | 
				
			||||||
 | 
					        color: $accent;
 | 
				
			||||||
 | 
					        text-style: bold;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    #entry-edit-form Input {
 | 
				
			||||||
 | 
					        margin-bottom: 1;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    #entry-edit-form Checkbox {
 | 
				
			||||||
 | 
					        margin-bottom: 1;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    BINDINGS = [
 | 
					    BINDINGS = [
 | 
				
			||||||
| 
						 | 
					@ -99,10 +124,14 @@ class HostsManagerApp(App):
 | 
				
			||||||
        Binding("n", "sort_by_hostname", "Sort by Hostname"),
 | 
					        Binding("n", "sort_by_hostname", "Sort by Hostname"),
 | 
				
			||||||
        Binding("c", "config", "Config"),
 | 
					        Binding("c", "config", "Config"),
 | 
				
			||||||
        Binding("ctrl+e", "toggle_edit_mode", "Edit Mode"),
 | 
					        Binding("ctrl+e", "toggle_edit_mode", "Edit Mode"),
 | 
				
			||||||
 | 
					        Binding("e", "edit_entry", "Edit Entry", show=False),
 | 
				
			||||||
        Binding("space", "toggle_entry", "Toggle Entry", show=False),
 | 
					        Binding("space", "toggle_entry", "Toggle Entry", show=False),
 | 
				
			||||||
        Binding("ctrl+s", "save_file", "Save", show=False),
 | 
					        Binding("ctrl+s", "save_file", "Save", show=False),
 | 
				
			||||||
        Binding("shift+up", "move_entry_up", "Move Up", show=False),
 | 
					        Binding("shift+up", "move_entry_up", "Move Up", show=False),
 | 
				
			||||||
        Binding("shift+down", "move_entry_down", "Move Down", show=False),
 | 
					        Binding("shift+down", "move_entry_down", "Move Down", show=False),
 | 
				
			||||||
 | 
					        Binding("escape", "exit_edit_entry", "Exit Edit", show=False),
 | 
				
			||||||
 | 
					        Binding("tab", "next_field", "Next Field", show=False),
 | 
				
			||||||
 | 
					        Binding("shift+tab", "prev_field", "Prev Field", show=False),
 | 
				
			||||||
        ("ctrl+c", "quit", "Quit"),
 | 
					        ("ctrl+c", "quit", "Quit"),
 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
| 
						 | 
					@ -110,6 +139,7 @@ class HostsManagerApp(App):
 | 
				
			||||||
    hosts_file: reactive[HostsFile] = reactive(HostsFile())
 | 
					    hosts_file: reactive[HostsFile] = reactive(HostsFile())
 | 
				
			||||||
    selected_entry_index: reactive[int] = reactive(0)
 | 
					    selected_entry_index: reactive[int] = reactive(0)
 | 
				
			||||||
    edit_mode: reactive[bool] = reactive(False)
 | 
					    edit_mode: reactive[bool] = reactive(False)
 | 
				
			||||||
 | 
					    entry_edit_mode: reactive[bool] = reactive(False)
 | 
				
			||||||
    sort_column: reactive[str] = reactive("")  # "ip" or "hostname"
 | 
					    sort_column: reactive[str] = reactive("")  # "ip" or "hostname"
 | 
				
			||||||
    sort_ascending: reactive[bool] = reactive(True)
 | 
					    sort_ascending: reactive[bool] = reactive(True)
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
| 
						 | 
					@ -137,6 +167,15 @@ class HostsManagerApp(App):
 | 
				
			||||||
                right_pane.border_title = "Entry Details"
 | 
					                right_pane.border_title = "Entry Details"
 | 
				
			||||||
                with right_pane:
 | 
					                with right_pane:
 | 
				
			||||||
                    yield Static("", id="entry-details")
 | 
					                    yield Static("", id="entry-details")
 | 
				
			||||||
 | 
					                    with Vertical(id="entry-edit-form", classes="hidden"):
 | 
				
			||||||
 | 
					                        yield Label("IP Address:")
 | 
				
			||||||
 | 
					                        yield Input(id="ip-input", placeholder="Enter IP address")
 | 
				
			||||||
 | 
					                        yield Label("Hostname:")
 | 
				
			||||||
 | 
					                        yield Input(id="hostname-input", placeholder="Enter hostname")
 | 
				
			||||||
 | 
					                        yield Label("Comment:")
 | 
				
			||||||
 | 
					                        yield Input(id="comment-input", placeholder="Enter comment (optional)")
 | 
				
			||||||
 | 
					                        yield Label("Active:")
 | 
				
			||||||
 | 
					                        yield Checkbox(id="active-checkbox", value=True)
 | 
				
			||||||
                yield right_pane
 | 
					                yield right_pane
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            yield Static("", classes="status-bar", id="status")
 | 
					            yield Static("", classes="status-bar", id="status")
 | 
				
			||||||
| 
						 | 
					@ -322,7 +361,19 @@ class HostsManagerApp(App):
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    def update_entry_details(self) -> None:
 | 
					    def update_entry_details(self) -> None:
 | 
				
			||||||
        """Update the right pane with selected entry details."""
 | 
					        """Update the right pane with selected entry details."""
 | 
				
			||||||
 | 
					        if self.entry_edit_mode:
 | 
				
			||||||
 | 
					            self.update_edit_form()
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            self.update_details_display()
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    def update_details_display(self) -> None:
 | 
				
			||||||
 | 
					        """Update the static details display."""
 | 
				
			||||||
        details_widget = self.query_one("#entry-details", Static)
 | 
					        details_widget = self.query_one("#entry-details", Static)
 | 
				
			||||||
 | 
					        edit_form = self.query_one("#entry-edit-form")
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        # Show details, hide edit form
 | 
				
			||||||
 | 
					        details_widget.remove_class("hidden")
 | 
				
			||||||
 | 
					        edit_form.add_class("hidden")
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        if not self.hosts_file.entries:
 | 
					        if not self.hosts_file.entries:
 | 
				
			||||||
            details_widget.update("No entries loaded")
 | 
					            details_widget.update("No entries loaded")
 | 
				
			||||||
| 
						 | 
					@ -374,6 +425,31 @@ class HostsManagerApp(App):
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        details_widget.update("\n".join(details_lines))
 | 
					        details_widget.update("\n".join(details_lines))
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
 | 
					    def update_edit_form(self) -> None:
 | 
				
			||||||
 | 
					        """Update the edit form with current entry values."""
 | 
				
			||||||
 | 
					        details_widget = self.query_one("#entry-details", Static)
 | 
				
			||||||
 | 
					        edit_form = self.query_one("#entry-edit-form")
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        # Hide details, show edit form
 | 
				
			||||||
 | 
					        details_widget.add_class("hidden")
 | 
				
			||||||
 | 
					        edit_form.remove_class("hidden")
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        if not self.hosts_file.entries or self.selected_entry_index >= len(self.hosts_file.entries):
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        entry = self.hosts_file.entries[self.selected_entry_index]
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        # Update form fields with current entry values
 | 
				
			||||||
 | 
					        ip_input = self.query_one("#ip-input", Input)
 | 
				
			||||||
 | 
					        hostname_input = self.query_one("#hostname-input", Input)
 | 
				
			||||||
 | 
					        comment_input = self.query_one("#comment-input", Input)
 | 
				
			||||||
 | 
					        active_checkbox = self.query_one("#active-checkbox", Checkbox)
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        ip_input.value = entry.ip_address
 | 
				
			||||||
 | 
					        hostname_input.value = ', '.join(entry.hostnames)
 | 
				
			||||||
 | 
					        comment_input.value = entry.comment or ""
 | 
				
			||||||
 | 
					        active_checkbox.value = entry.is_active
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    def update_status(self, message: str = "") -> None:
 | 
					    def update_status(self, message: str = "") -> None:
 | 
				
			||||||
        """Update the status bar."""
 | 
					        """Update the status bar."""
 | 
				
			||||||
        status_widget = self.query_one("#status", Static)
 | 
					        status_widget = self.query_one("#status", Static)
 | 
				
			||||||
| 
						 | 
					@ -437,7 +513,7 @@ class HostsManagerApp(App):
 | 
				
			||||||
    def action_help(self) -> None:
 | 
					    def action_help(self) -> None:
 | 
				
			||||||
        """Show help information."""
 | 
					        """Show help information."""
 | 
				
			||||||
        # For now, just update the status with help info
 | 
					        # For now, just update the status with help info
 | 
				
			||||||
        self.update_status("Help: ↑/↓ Navigate, r Reload, q Quit, h Help, c Config")
 | 
					        self.update_status("Help: ↑/↓ Navigate, r Reload, q Quit, h Help, c Config, e Edit")
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    def action_config(self) -> None:
 | 
					    def action_config(self) -> None:
 | 
				
			||||||
        """Show configuration modal."""
 | 
					        """Show configuration modal."""
 | 
				
			||||||
| 
						 | 
					@ -449,7 +525,6 @@ class HostsManagerApp(App):
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        self.push_screen(ConfigModal(self.config), handle_config_result)
 | 
					        self.push_screen(ConfigModal(self.config), handle_config_result)
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    def action_sort_by_ip(self) -> None:
 | 
					    def action_sort_by_ip(self) -> None:
 | 
				
			||||||
        """Sort entries by IP address, toggle ascending/descending."""
 | 
					        """Sort entries by IP address, toggle ascending/descending."""
 | 
				
			||||||
        # Toggle sort direction if already sorting by IP
 | 
					        # Toggle sort direction if already sorting by IP
 | 
				
			||||||
| 
						 | 
					@ -512,6 +587,168 @@ class HostsManagerApp(App):
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                self.update_status(f"Error entering edit mode: {message}")
 | 
					                self.update_status(f"Error entering edit mode: {message}")
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
 | 
					    def action_edit_entry(self) -> None:
 | 
				
			||||||
 | 
					        """Enter edit mode for the selected entry."""
 | 
				
			||||||
 | 
					        if not self.edit_mode:
 | 
				
			||||||
 | 
					            self.update_status("❌ Cannot edit entry: Application is in read-only mode. Press 'Ctrl+E' to enable edit mode.")
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        if not self.hosts_file.entries:
 | 
				
			||||||
 | 
					            self.update_status("No entries to edit")
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        if self.selected_entry_index >= len(self.hosts_file.entries):
 | 
				
			||||||
 | 
					            self.update_status("Invalid entry selected")
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        entry = self.hosts_file.entries[self.selected_entry_index]
 | 
				
			||||||
 | 
					        if entry.is_default_entry():
 | 
				
			||||||
 | 
					            self.update_status("❌ Cannot edit system default entry")
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        self.entry_edit_mode = True
 | 
				
			||||||
 | 
					        self.update_entry_details()
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        # Focus on the IP address input field
 | 
				
			||||||
 | 
					        ip_input = self.query_one("#ip-input", Input)
 | 
				
			||||||
 | 
					        ip_input.focus()
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        self.update_status("Editing entry - Use Tab/Shift+Tab to navigate, ESC to exit")
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    def action_exit_edit_entry(self) -> None:
 | 
				
			||||||
 | 
					        """Exit entry edit mode and return focus to the entries table."""
 | 
				
			||||||
 | 
					        if self.entry_edit_mode:
 | 
				
			||||||
 | 
					            self.entry_edit_mode = False
 | 
				
			||||||
 | 
					            self.update_entry_details()
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            # Return focus to the entries table
 | 
				
			||||||
 | 
					            table = self.query_one("#entries-table", DataTable)
 | 
				
			||||||
 | 
					            table.focus()
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            self.update_status("Exited entry edit mode")
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    def action_next_field(self) -> None:
 | 
				
			||||||
 | 
					        """Move to the next field in edit mode."""
 | 
				
			||||||
 | 
					        if not self.entry_edit_mode:
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        # Get all input fields in order
 | 
				
			||||||
 | 
					        fields = [
 | 
				
			||||||
 | 
					            self.query_one("#ip-input", Input),
 | 
				
			||||||
 | 
					            self.query_one("#hostname-input", Input),
 | 
				
			||||||
 | 
					            self.query_one("#comment-input", Input),
 | 
				
			||||||
 | 
					            self.query_one("#active-checkbox", Checkbox)
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        # Find currently focused field and move to next
 | 
				
			||||||
 | 
					        for i, field in enumerate(fields):
 | 
				
			||||||
 | 
					            if field.has_focus:
 | 
				
			||||||
 | 
					                next_field = fields[(i + 1) % len(fields)]
 | 
				
			||||||
 | 
					                next_field.focus()
 | 
				
			||||||
 | 
					                break
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    def action_prev_field(self) -> None:
 | 
				
			||||||
 | 
					        """Move to the previous field in edit mode."""
 | 
				
			||||||
 | 
					        if not self.entry_edit_mode:
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        # Get all input fields in order
 | 
				
			||||||
 | 
					        fields = [
 | 
				
			||||||
 | 
					            self.query_one("#ip-input", Input),
 | 
				
			||||||
 | 
					            self.query_one("#hostname-input", Input),
 | 
				
			||||||
 | 
					            self.query_one("#comment-input", Input),
 | 
				
			||||||
 | 
					            self.query_one("#active-checkbox", Checkbox)
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        # Find currently focused field and move to previous
 | 
				
			||||||
 | 
					        for i, field in enumerate(fields):
 | 
				
			||||||
 | 
					            if field.has_focus:
 | 
				
			||||||
 | 
					                prev_field = fields[(i - 1) % len(fields)]
 | 
				
			||||||
 | 
					                prev_field.focus()
 | 
				
			||||||
 | 
					                break
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    def on_key(self, event) -> None:
 | 
				
			||||||
 | 
					        """Handle key events to override default tab behavior in edit mode."""
 | 
				
			||||||
 | 
					        if self.entry_edit_mode and event.key == "tab":
 | 
				
			||||||
 | 
					            # Prevent default tab behavior and use our custom navigation
 | 
				
			||||||
 | 
					            event.prevent_default()
 | 
				
			||||||
 | 
					            self.action_next_field()
 | 
				
			||||||
 | 
					        elif self.entry_edit_mode and event.key == "shift+tab":
 | 
				
			||||||
 | 
					            # Prevent default shift+tab behavior and use our custom navigation
 | 
				
			||||||
 | 
					            event.prevent_default() 
 | 
				
			||||||
 | 
					            self.action_prev_field()
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    def on_input_changed(self, event: Input.Changed) -> None:
 | 
				
			||||||
 | 
					        """Handle input field changes and auto-save."""
 | 
				
			||||||
 | 
					        if not self.entry_edit_mode or not self.edit_mode:
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        if event.input.id in ["ip-input", "hostname-input", "comment-input"]:
 | 
				
			||||||
 | 
					            self.save_entry_changes()
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    def on_checkbox_changed(self, event: Checkbox.Changed) -> None:
 | 
				
			||||||
 | 
					        """Handle checkbox changes and auto-save."""
 | 
				
			||||||
 | 
					        if not self.entry_edit_mode or not self.edit_mode:
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        if event.checkbox.id == "active-checkbox":
 | 
				
			||||||
 | 
					            self.save_entry_changes()
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    def save_entry_changes(self) -> None:
 | 
				
			||||||
 | 
					        """Save the current entry changes."""
 | 
				
			||||||
 | 
					        if not self.hosts_file.entries or self.selected_entry_index >= len(self.hosts_file.entries):
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        entry = self.hosts_file.entries[self.selected_entry_index]
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        # Get values from form fields
 | 
				
			||||||
 | 
					        ip_input = self.query_one("#ip-input", Input)
 | 
				
			||||||
 | 
					        hostname_input = self.query_one("#hostname-input", Input)
 | 
				
			||||||
 | 
					        comment_input = self.query_one("#comment-input", Input)
 | 
				
			||||||
 | 
					        active_checkbox = self.query_one("#active-checkbox", Checkbox)
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        # Validate IP address
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            ipaddress.ip_address(ip_input.value.strip())
 | 
				
			||||||
 | 
					        except ValueError:
 | 
				
			||||||
 | 
					            self.update_status("❌ Invalid IP address")
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        # Validate hostname(s)
 | 
				
			||||||
 | 
					        hostnames = [h.strip() for h in hostname_input.value.split(',') if h.strip()]
 | 
				
			||||||
 | 
					        if not hostnames:
 | 
				
			||||||
 | 
					            self.update_status("❌ At least one hostname is required")
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        hostname_pattern = re.compile(
 | 
				
			||||||
 | 
					            r'^[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?)*$'
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        for hostname in hostnames:
 | 
				
			||||||
 | 
					            if not hostname_pattern.match(hostname):
 | 
				
			||||||
 | 
					                self.update_status(f"❌ Invalid hostname: {hostname}")
 | 
				
			||||||
 | 
					                return
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        # Update the entry
 | 
				
			||||||
 | 
					        entry.ip_address = ip_input.value.strip()
 | 
				
			||||||
 | 
					        entry.hostnames = hostnames
 | 
				
			||||||
 | 
					        entry.comment = comment_input.value.strip() or None
 | 
				
			||||||
 | 
					        entry.is_active = active_checkbox.value
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        # Save to file
 | 
				
			||||||
 | 
					        success, message = self.manager.save_hosts_file(self.hosts_file)
 | 
				
			||||||
 | 
					        if success:
 | 
				
			||||||
 | 
					            # Update the table display
 | 
				
			||||||
 | 
					            self.populate_entries_table()
 | 
				
			||||||
 | 
					            # Restore cursor position
 | 
				
			||||||
 | 
					            table = self.query_one("#entries-table", DataTable)
 | 
				
			||||||
 | 
					            display_index = self.actual_index_to_display_index(self.selected_entry_index)
 | 
				
			||||||
 | 
					            if table.row_count > 0 and display_index < table.row_count:
 | 
				
			||||||
 | 
					                table.move_cursor(row=display_index)
 | 
				
			||||||
 | 
					            self.update_status("Entry saved successfully")
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            self.update_status(f"❌ Error saving entry: {message}")
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    def action_toggle_entry(self) -> None:
 | 
					    def action_toggle_entry(self) -> None:
 | 
				
			||||||
        """Toggle the active state of the selected entry."""
 | 
					        """Toggle the active state of the selected entry."""
 | 
				
			||||||
        if not self.edit_mode:
 | 
					        if not self.edit_mode:
 | 
				
			||||||
| 
						 | 
					@ -616,6 +853,10 @@ class HostsManagerApp(App):
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    def action_quit(self) -> None:
 | 
					    def action_quit(self) -> None:
 | 
				
			||||||
        """Quit the application."""
 | 
					        """Quit the application."""
 | 
				
			||||||
 | 
					        # If in entry edit mode, exit it first
 | 
				
			||||||
 | 
					        if self.entry_edit_mode:
 | 
				
			||||||
 | 
					            self.action_exit_edit_entry()
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
        # If in edit mode, exit it first
 | 
					        # If in edit mode, exit it first
 | 
				
			||||||
        if self.edit_mode:
 | 
					        if self.edit_mode:
 | 
				
			||||||
            self.manager.exit_edit_mode()
 | 
					            self.manager.exit_edit_mode()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue