Fix variable indicators and add project caching

This commit is contained in:
vesp 2026-01-06 01:45:36 +01:00
parent 79c2f3c944
commit 51263816f3
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._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):
"""Create data directory if it doesn't exist."""
self.data_dir.mkdir(parents=True, exist_ok=True)
def load_projects(self) -> List[Project]:
"""Load projects from JSON file."""
def load_projects(self, force_reload: bool = False) -> List[Project]:
"""
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():
self._projects_cache = []
return []
try:
with open(self.projects_file, 'r') as 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:
logger.error(f"Error loading projects: {e}")
self._projects_cache = []
return []
def save_projects(self, projects: List[Project]):
"""Save projects to JSON file."""
"""Save projects to JSON file and update cache."""
try:
data = {
'version': 1,
@ -68,6 +87,9 @@ class ProjectManager:
}
with open(self.projects_file, 'w') as f:
json.dump(data, f, indent=2)
# Update cache with the saved data
self._projects_cache = projects
except Exception as 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.undefined_variables: Set[str] = set()
self._update_indicators_timeout_id: Optional[int] = None
self._is_programmatically_changing_environment: bool = False
# Build the UI
self._build_ui()
@ -819,6 +820,10 @@ class RequestTabWidget(Gtk.Box):
if request.syntax in syntax_options:
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:
"""Build and return HttpRequest from current UI state."""
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_tooltip_text("Select environment for variable substitution")
# Connect signal
self.environment_dropdown.connect("notify::selected", self._on_environment_changed)
# Connect signal handler
self._env_change_handler_id = self.environment_dropdown.connect("notify::selected", self._on_environment_changed)
# Add to container at the beginning (left side)
container.prepend(self.environment_dropdown)
@ -1178,31 +1183,40 @@ class RequestTabWidget(Gtk.Box):
if not project:
return
# Build string list with "None" + environment names
string_list = Gtk.StringList()
string_list.append("None")
# 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
# Track environment IDs (index 0 is None)
self.environment_ids = [None]
try:
# Build string list with "None" + environment names
string_list = Gtk.StringList()
string_list.append("None")
for env in project.environments:
string_list.append(env.name)
self.environment_ids.append(env.id)
# Track environment IDs (index 0 is None)
self.environment_ids = [None]
self.environment_dropdown.set_model(string_list)
for env in project.environments:
string_list.append(env.name)
self.environment_ids.append(env.id)
# Select current environment
if self.selected_environment_id:
try:
index = self.environment_ids.index(self.selected_environment_id)
self.environment_dropdown.set_selected(index)
except ValueError:
self.environment_dropdown.set_model(string_list)
# Select current environment
if self.selected_environment_id:
try:
index = self.environment_ids.index(self.selected_environment_id)
self.environment_dropdown.set_selected(index)
except ValueError:
self.environment_dropdown.set_selected(0) # Default to "None"
else:
self.environment_dropdown.set_selected(0) # Default to "None"
else:
self.environment_dropdown.set_selected(0) # Default to "None"
# Update indicators after environment is set
self._update_variable_indicators()
finally:
# 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):
"""Show environment selector (create if it doesn't exist)."""
@ -1226,17 +1240,29 @@ class RequestTabWidget(Gtk.Box):
# Populate if project_manager is available
if self.project_manager:
self._populate_environment_dropdown()
# Update indicators after environment selector is shown
self._update_variable_indicators()
def _on_environment_changed(self, dropdown, _param):
"""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'):
logger.warning("Environment changed but environment_ids not initialized")
return
selected_index = dropdown.get_selected()
if selected_index < len(self.environment_ids):
self.selected_environment_id = self.environment_ids[selected_index]
# Update visual indicators when environment changes
self._update_variable_indicators()
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()
def get_selected_environment(self):
"""Get the currently selected environment object."""
@ -1303,6 +1329,10 @@ class RequestTabWidget(Gtk.Box):
def _update_variable_indicators(self):
"""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
self.undefined_variables = self._detect_undefined_variables()

View File

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

View File

@ -950,7 +950,8 @@ class RosterWindow(Adw.ApplicationWindow):
def _on_manage_environments(self, widget, project):
"""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()
fresh_project = None
for p in projects: