Restructure project and add user settings persistence

- Move src/clide/ to clide/ (flatten directory structure)
- Add SettingsService for persistent user preferences
- Settings stored in ~/.clide/settings.json
- Persist theme, panel visibility, and compact mode state
- Fix terminal rendering glitch with throttled refresh (~60fps)
- Update pyproject.toml, Makefile, and tests for new structure

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Jeroen Schweitzer
2026-02-01 00:42:46 +01:00
co-authored by Claude Opus 4.5
parent 6b7c3ecf9b
commit 1ed21d2a0f
88 changed files with 378 additions and 226 deletions
+1 -2
View File
@@ -1,8 +1,7 @@
"""Shared pytest fixtures for Clide tests."""
from collections.abc import AsyncGenerator, Generator
from pathlib import Path
from typing import AsyncGenerator, Generator
from unittest.mock import MagicMock
import pytest
from textual.pilot import Pilot
+4 -5
View File
@@ -1,7 +1,6 @@
"""Full application test harness for Clide."""
from pathlib import Path
from typing import Optional
from textual.pilot import Pilot
@@ -26,14 +25,14 @@ class AppHarness:
def __init__(
self,
workdir: Path,
settings: Optional[ClideSettings] = None,
extension_manager: Optional[ExtensionManager] = None,
settings: ClideSettings | None = None,
extension_manager: ExtensionManager | None = None,
) -> None:
self.workdir = workdir
self.settings = settings or ClideSettings()
self.extension_manager = extension_manager or ExtensionManager()
self._app: Optional[ClideApp] = None
self._pilot: Optional[Pilot] = None
self._app: ClideApp | None = None
self._pilot: Pilot | None = None
async def start(self) -> tuple[ClideApp, Pilot]:
"""Start the application and return app and pilot for testing.
+4 -4
View File
@@ -1,6 +1,6 @@
"""Controller isolation test harness for Clide."""
from typing import TYPE_CHECKING, Any, Optional
from typing import TYPE_CHECKING, Any
from unittest.mock import AsyncMock, MagicMock
from textual.message import Message
@@ -19,7 +19,7 @@ class MockApp:
def _capture_message(self, message: Message) -> None:
self.messages.append(message)
def get_messages(self, message_type: Optional[type] = None) -> list[Message]:
def get_messages(self, message_type: type | None = None) -> list[Message]:
"""Get captured messages, optionally filtered by type."""
if message_type is None:
return self.messages.copy()
@@ -46,7 +46,7 @@ class ControllerHarness:
def __init__(self) -> None:
self._mock_app = MockApp()
self._controllers: list["BaseController"] = []
self._controllers: list[BaseController] = []
self._mocks: dict[str, Any] = {}
@property
@@ -68,7 +68,7 @@ class ControllerHarness:
for controller in self._controllers:
await controller.shutdown()
def get_messages(self, message_type: Optional[type] = None) -> list[Message]:
def get_messages(self, message_type: type | None = None) -> list[Message]:
"""Get messages posted to the mock app."""
return self._mock_app.get_messages(message_type)
-1
View File
@@ -4,7 +4,6 @@ from pathlib import Path
import pytest
from textual.app import App, ComposeResult
from textual.widgets import Static
from clide.widgets.components.files_view import FilesView
-3
View File
@@ -1,9 +1,6 @@
"""Tests for ClideApp."""
from pathlib import Path
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
from clide.models.config import ClideSettings
+5 -7
View File
@@ -1,7 +1,7 @@
"""Tests for controller classes."""
from pathlib import Path
from unittest.mock import AsyncMock, MagicMock, patch
from unittest.mock import patch
import pytest
@@ -11,13 +11,11 @@ from clide.controllers.git import GitController
from clide.controllers.jira import JiraController
from clide.controllers.problems import ProblemsController
from clide.controllers.todos import TodosController
from clide.models.diff import ChangeType, DiffContent, DiffHunk, DiffLine
from clide.models.editor import CursorPosition, FileBuffer
from clide.models.diff import DiffContent, DiffHunk
from clide.models.editor import FileBuffer
from clide.models.git import ChangeStatus, GitBranch, GitChange, GitCommit, GitStatus
from clide.models.problems import Problem, ProblemsSummary, Severity
from clide.models.todos import TodoItem, TodosSummary, TodoType
from clide.services.git_service import GitService
from clide.services.process_service import CommandResult
from clide.models.problems import Problem, Severity
from clide.models.todos import TodoItem, TodoType
class TestGitController:
+5 -5
View File
@@ -6,12 +6,12 @@ import pytest
from pydantic import ValidationError
from clide.models.config import ClideSettings, KeybindingsConfig, PanelConfig
from clide.models.diff import ChangeType, DiffContent, DiffHunk, DiffLine, DiffViewState
from clide.models.editor import CursorPosition, EditorState, FileBuffer, Selection
from clide.models.git import ChangeStatus, GitBranch, GitChange, GitCommit, GitGraph, GitStatus
from clide.models.problems import Problem, ProblemsSummary, ProblemsState, Severity
from clide.models.diff import ChangeType, DiffContent, DiffHunk, DiffLine
from clide.models.editor import CursorPosition, EditorState, FileBuffer
from clide.models.git import ChangeStatus, GitBranch, GitChange, GitCommit, GitStatus
from clide.models.problems import Problem, ProblemsSummary, Severity
from clide.models.theme import ThemeColors, ThemeDefinition, ThemeMetadata
from clide.models.todos import TodoItem, TodosSummary, TodosState, TodoType
from clide.models.todos import TodoItem, TodosSummary, TodoType
class TestCursorPosition:
-5
View File
@@ -1,12 +1,7 @@
"""Tests for panel widgets."""
from pathlib import Path
from unittest.mock import MagicMock, patch
import pytest
from clide.models.diff import DiffContent
from clide.models.git import GitBranch, GitChange
from clide.models.problems import Problem, Severity
from clide.models.todos import TodoItem, TodoType
+2 -5
View File
@@ -1,14 +1,11 @@
"""Tests for service classes."""
import asyncio
from pathlib import Path
from unittest.mock import AsyncMock, MagicMock, patch
from unittest.mock import patch
import pytest
from clide.models.git import GitBranch, GitChange, GitCommit, GitStatus
from clide.models.problems import Problem, Severity
from clide.models.todos import TodoItem, TodoType
from clide.models.todos import TodoType
from clide.services.file_service import FileService
from clide.services.git_service import GitService
from clide.services.linter_service import LinterService
+4 -6
View File
@@ -1,13 +1,11 @@
"""Tests for widget components."""
from pathlib import Path
from unittest.mock import MagicMock, patch
from unittest.mock import MagicMock
import pytest
from clide.models.diff import ChangeType, DiffContent, DiffHunk, DiffLine
from clide.models.editor import CursorPosition, FileBuffer
from clide.models.git import ChangeStatus, GitBranch, GitChange, GitCommit
from clide.models.diff import DiffContent
from clide.models.editor import FileBuffer
from clide.models.git import ChangeStatus, GitChange, GitCommit
from clide.models.problems import Problem, Severity
from clide.models.todos import TodoItem, TodoType