Fix variable indicators and add project caching

This commit is contained in:
Pavel Baksy 2026-01-06 01:45:36 +01:00
parent 51ec7a8e43
commit 4ecc7901b8
4 changed files with 86 additions and 30 deletions

View File

@ -42,25 +42,44 @@ class ProjectManager:
self.projects_file = self.data_dir / 'requests.json' self.projects_file = self.data_dir / 'requests.json'
self._ensure_data_dir() self._ensure_data_dir()
# In-memory cache for projects to reduce disk I/O
self._projects_cache: Optional[List[Project]] = None
def _ensure_data_dir(self): def _ensure_data_dir(self):
"""Create data directory if it doesn't exist.""" """Create data directory if it doesn't exist."""
self.data_dir.mkdir(parents=True, exist_ok=True) self.data_dir.mkdir(parents=True, exist_ok=True)
def load_projects(self) -> List[Project]: def load_projects(self, force_reload: bool = False) -> List[Project]:
"""Load projects from JSON file.""" """
Load projects from JSON file with in-memory caching.
Args:
force_reload: If True, bypass cache and reload from disk
Returns:
List of Project objects
"""
# Return cached data if available and not forcing reload
if self._projects_cache is not None and not force_reload:
return self._projects_cache
# Load from disk
if not self.projects_file.exists(): if not self.projects_file.exists():
self._projects_cache = []
return [] return []
try: try:
with open(self.projects_file, 'r') as f: with open(self.projects_file, 'r') as f:
data = json.load(f) data = json.load(f)
return [Project.from_dict(p) for p in data.get('projects', [])] self._projects_cache = [Project.from_dict(p) for p in data.get('projects', [])]
return self._projects_cache
except Exception as e: except Exception as e:
logger.error(f"Error loading projects: {e}") logger.error(f"Error loading projects: {e}")
self._projects_cache = []
return [] return []
def save_projects(self, projects: List[Project]): def save_projects(self, projects: List[Project]):
"""Save projects to JSON file.""" """Save projects to JSON file and update cache."""
try: try:
data = { data = {
'version': 1, 'version': 1,
@ -68,6 +87,9 @@ class ProjectManager:
} }
with open(self.projects_file, 'w') as f: with open(self.projects_file, 'w') as f:
json.dump(data, f, indent=2) json.dump(data, f, indent=2)
# Update cache with the saved data
self._projects_cache = projects
except Exception as e: except Exception as e:
logger.error(f"Error saving projects: {e}") logger.error(f"Error saving projects: {e}")

View File

@ -60,6 +60,7 @@ class RequestTabWidget(Gtk.Box):
self.env_separator: Optional[Gtk.Separator] = None self.env_separator: Optional[Gtk.Separator] = None
self.undefined_variables: Set[str] = set() self.undefined_variables: Set[str] = set()
self._update_indicators_timeout_id: Optional[int] = None self._update_indicators_timeout_id: Optional[int] = None
self._is_programmatically_changing_environment: bool = False
# Build the UI # Build the UI
self._build_ui() self._build_ui()
@ -819,6 +820,10 @@ class RequestTabWidget(Gtk.Box):
if request.syntax in syntax_options: if request.syntax in syntax_options:
self.body_language_dropdown.set_selected(syntax_options.index(request.syntax)) self.body_language_dropdown.set_selected(syntax_options.index(request.syntax))
# Update variable indicators after loading the request (if project is set)
if self.project_id:
self._update_variable_indicators()
def get_request(self) -> HttpRequest: def get_request(self) -> HttpRequest:
"""Build and return HttpRequest from current UI state.""" """Build and return HttpRequest from current UI state."""
method = self.method_dropdown.get_selected_item().get_string() method = self.method_dropdown.get_selected_item().get_string()
@ -1152,8 +1157,8 @@ class RequestTabWidget(Gtk.Box):
self.environment_dropdown.set_enable_search(False) self.environment_dropdown.set_enable_search(False)
self.environment_dropdown.set_tooltip_text("Select environment for variable substitution") self.environment_dropdown.set_tooltip_text("Select environment for variable substitution")
# Connect signal # Connect signal handler
self.environment_dropdown.connect("notify::selected", self._on_environment_changed) self._env_change_handler_id = self.environment_dropdown.connect("notify::selected", self._on_environment_changed)
# Add to container at the beginning (left side) # Add to container at the beginning (left side)
container.prepend(self.environment_dropdown) container.prepend(self.environment_dropdown)
@ -1178,6 +1183,11 @@ class RequestTabWidget(Gtk.Box):
if not project: if not project:
return return
# Set flag to indicate we're programmatically changing the dropdown
# This prevents the signal handler from triggering indicator updates during initialization
self._is_programmatically_changing_environment = True
try:
# Build string list with "None" + environment names # Build string list with "None" + environment names
string_list = Gtk.StringList() string_list = Gtk.StringList()
string_list.append("None") string_list.append("None")
@ -1201,8 +1211,12 @@ class RequestTabWidget(Gtk.Box):
else: else:
self.environment_dropdown.set_selected(0) # Default to "None" self.environment_dropdown.set_selected(0) # Default to "None"
# Update indicators after environment is set finally:
self._update_variable_indicators() # Always clear the flag
self._is_programmatically_changing_environment = False
# Note: Don't update indicators here as the request might not be loaded yet
# Indicators will be updated when environment changes or request is loaded
def _show_environment_selector(self): def _show_environment_selector(self):
"""Show environment selector (create if it doesn't exist).""" """Show environment selector (create if it doesn't exist)."""
@ -1226,16 +1240,28 @@ class RequestTabWidget(Gtk.Box):
# Populate if project_manager is available # Populate if project_manager is available
if self.project_manager: if self.project_manager:
self._populate_environment_dropdown() self._populate_environment_dropdown()
# Update indicators after environment selector is shown
self._update_variable_indicators()
def _on_environment_changed(self, dropdown, _param): def _on_environment_changed(self, dropdown, _param):
"""Handle environment selection change.""" """Handle environment selection change."""
# If we're programmatically changing the environment during population, skip this
if self._is_programmatically_changing_environment:
return
if not hasattr(self, 'environment_ids'): if not hasattr(self, 'environment_ids'):
logger.warning("Environment changed but environment_ids not initialized")
return return
selected_index = dropdown.get_selected() selected_index = dropdown.get_selected()
if selected_index < len(self.environment_ids): if selected_index < len(self.environment_ids):
self.selected_environment_id = self.environment_ids[selected_index] self.selected_environment_id = self.environment_ids[selected_index]
# Update visual indicators when environment changes else:
logger.warning(f"Environment index {selected_index} out of range (max {len(self.environment_ids)-1})")
self.selected_environment_id = None
# Always update visual indicators when environment changes
self._update_variable_indicators() self._update_variable_indicators()
def get_selected_environment(self): def get_selected_environment(self):
@ -1303,6 +1329,10 @@ class RequestTabWidget(Gtk.Box):
def _update_variable_indicators(self): def _update_variable_indicators(self):
"""Update visual indicators for undefined variables.""" """Update visual indicators for undefined variables."""
# Only update if we have a project (variables only make sense in project context)
if not self.project_id:
return
# Detect undefined variables # Detect undefined variables
self.undefined_variables = self._detect_undefined_variables() self.undefined_variables = self._detect_undefined_variables()

View File

@ -64,8 +64,11 @@ class VariableSubstitution:
var_name = match.group(1) var_name = match.group(1)
if var_name in variables: if var_name in variables:
value = variables[var_name] value = variables[var_name]
# Replace with value or empty string if value is None/empty # Check if value is empty/None - treat as undefined for warning purposes
return value if value else "" if not value:
undefined_vars.append(var_name)
return ""
return value
else: else:
# Variable not defined - track it and replace with empty string # Variable not defined - track it and replace with empty string
undefined_vars.append(var_name) undefined_vars.append(var_name)

View File

@ -950,7 +950,8 @@ class RosterWindow(Adw.ApplicationWindow):
def _on_manage_environments(self, widget, project): def _on_manage_environments(self, widget, project):
"""Show environments management dialog.""" """Show environments management dialog."""
# Reload project from disk to get latest data (including variables created by scripts) # Get latest project data (includes variables created by scripts)
# Note: load_projects() uses cached data that's kept up-to-date by save operations
projects = self.project_manager.load_projects() projects = self.project_manager.load_projects()
fresh_project = None fresh_project = None
for p in projects: for p in projects: