Skip to content

Commit adb9961

Browse files
committed
Release v0.3.0
2 parents 3510d99 + 2fcf878 commit adb9961

9 files changed

Lines changed: 113 additions & 51 deletions

File tree

.github/workflows/ci.yml

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,33 @@ on:
99
jobs:
1010
test:
1111
runs-on: ubuntu-latest
12+
strategy:
13+
matrix:
14+
python-version: ["3.12", "3.13", "3.14"]
1215

1316
steps:
1417
- uses: actions/checkout@v4
1518

16-
- name: Set up Python 3.14
19+
- name: Set up Python ${{ matrix.python-version }}
1720
uses: actions/setup-python@v5
1821
with:
19-
python-version: '3.14'
22+
python-version: ${{ matrix.python-version }}
2023

2124
- name: Install uv
2225
run: pip install uv
2326

2427
- name: Install dependencies
2528
run: uv pip install --system -r requirements.txt
2629

30+
- name: Install Qt runtime dependencies
31+
run: |
32+
sudo apt-get update -q
33+
sudo apt-get install -y libgl1 libegl1 libxkbcommon-x11-0
34+
2735
- name: Run tests
28-
run: pytest tests/ -v --cov=src --cov-report=xml
36+
env:
37+
QT_QPA_PLATFORM: offscreen
38+
run: pytest tests/ -v --cov=src --cov-report=xml --cov-fail-under=80
2939

3040
- name: Upload coverage to Codecov
3141
uses: codecov/codecov-action@v4

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
A PySide6 desktop application for text processing on Linux. Provides encoding conversion (to UTF-8), text formatting/cleaning, find/replace, and file management through a split-panel interface.
44

5-
**Status**: v0.2.0 — core features implemented and tested. Encoding conversion is stubbed; find/replace, text cleaning, and file I/O are fully functional. See `DESIGN.md` for the full specification.
5+
**Status**: v0.3.0 — core features implemented and tested. Encoding conversion, find/replace, text cleaning, and file I/O are fully functional. See `DESIGN.md` for the full specification.
66

77
## Stack
88

src/utils/constants.py

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,26 @@
99
# File extensions shown in the QFileSystemModel tree.
1010
# Covers common text, config, markup, and script formats.
1111
TEXT_FILE_EXTENSIONS = [
12-
"*.txt", "*.md", "*.rst", "*.csv", "*.log",
13-
"*.json", "*.yaml", "*.yml", "*.toml", "*.xml", "*.html", "*.htm",
14-
"*.css", "*.js", "*.ts", "*.py", "*.sh", "*.bash",
15-
"*.conf", "*.cfg", "*.ini", "*.env",
12+
"*.txt",
13+
"*.md",
14+
"*.rst",
15+
"*.csv",
16+
"*.log",
17+
"*.json",
18+
"*.yaml",
19+
"*.yml",
20+
"*.toml",
21+
"*.xml",
22+
"*.html",
23+
"*.htm",
24+
"*.css",
25+
"*.js",
26+
"*.ts",
27+
"*.py",
28+
"*.sh",
29+
"*.bash",
30+
"*.conf",
31+
"*.cfg",
32+
"*.ini",
33+
"*.env",
1634
]

src/views/main_window.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,9 @@ def _setup_file_tree(self) -> None:
235235
self._fs_model = QFileSystemModel(self.ui)
236236
self._fs_model.setRootPath(QDir.homePath())
237237
self._fs_model.setNameFilters(TEXT_FILE_EXTENSIONS)
238-
self._fs_model.setNameFilterDisables(False) # hide non-matches (not just grey them)
238+
self._fs_model.setNameFilterDisables(
239+
False
240+
) # hide non-matches (not just grey them)
239241
self._file_tree_view.setModel(self._fs_model)
240242
self._file_tree_view.setRootIndex(self._fs_model.index(QDir.homePath()))
241243
# Hide size/type/date columns — name column only
@@ -324,7 +326,9 @@ def _connect_signals(self) -> None:
324326
self._plain_text_edit.cursorPositionChanged.connect(self._update_cursor_label)
325327
# contentsChanged fires on Delete/Backspace where cursor position does not
326328
# change — without this the char count goes stale after in-place deletions.
327-
self._plain_text_edit.document().contentsChanged.connect(self._update_cursor_label)
329+
self._plain_text_edit.document().contentsChanged.connect(
330+
self._update_cursor_label
331+
)
328332

329333
# Keyboard shortcuts not present in the .ui file.
330334
# (Ctrl+S/O/Q/Shift+S are already wired via QAction shortcuts in main_window.ui.)
@@ -592,7 +596,7 @@ def _apply_preferences(self) -> None:
592596

593597
# Font
594598
family = str(settings.value(KEY_FONT_FAMILY, DEFAULTS[KEY_FONT_FAMILY]))
595-
size = int(settings.value(KEY_FONT_SIZE, DEFAULTS[KEY_FONT_SIZE]))
599+
size = int(str(settings.value(KEY_FONT_SIZE, DEFAULTS[KEY_FONT_SIZE])))
596600
self._plain_text_edit.setFont(QFont(family, size))
597601

598602
# Word wrap
@@ -605,7 +609,7 @@ def _apply_preferences(self) -> None:
605609

606610
# Theme — Fusion style with a dark palette; restore system default for light.
607611
theme = str(settings.value(KEY_THEME, DEFAULTS[KEY_THEME]))
608-
app = QApplication.instance()
612+
app = cast(QApplication, QApplication.instance())
609613
if app is not None:
610614
if theme == "dark":
611615
app.setStyle("Fusion")

src/views/preferences_dialog.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
"""
99

1010
import os
11+
from typing import TypeVar
1112

1213
from PySide6.QtCore import QDir, QFile, QObject, QSettings, Signal
1314
from PySide6.QtUiTools import QUiLoader
@@ -23,14 +24,14 @@
2324
QWidget,
2425
)
2526

26-
from typing import TypeVar
27-
2827
_W = TypeVar("_W")
2928

3029

3130
def _require(widget: "_W | None", name: str) -> "_W":
3231
if widget is None:
33-
raise RuntimeError(f"Required widget '{name}' not found in preferences_dialog.ui")
32+
raise RuntimeError(
33+
f"Required widget '{name}' not found in preferences_dialog.ui"
34+
)
3435
return widget
3536

3637

@@ -98,7 +99,8 @@ def _load_ui(self, parent: QWidget | None) -> None:
9899
self.dialog.findChild(QCheckBox, "wordWrapCheckBox"), "wordWrapCheckBox"
99100
)
100101
self._line_numbers_cb = _require(
101-
self.dialog.findChild(QCheckBox, "lineNumbersCheckBox"), "lineNumbersCheckBox"
102+
self.dialog.findChild(QCheckBox, "lineNumbersCheckBox"),
103+
"lineNumbersCheckBox",
102104
)
103105
self._theme_light_radio = _require(
104106
self.dialog.findChild(QRadioButton, "themeLightRadio"), "themeLightRadio"
@@ -139,13 +141,15 @@ def _load_from_settings(self) -> None:
139141
str(settings.value(KEY_FONT_FAMILY, DEFAULTS[KEY_FONT_FAMILY]))
140142
)
141143
self._font_size_spin.setValue(
142-
int(settings.value(KEY_FONT_SIZE, DEFAULTS[KEY_FONT_SIZE]))
144+
int(str(settings.value(KEY_FONT_SIZE, DEFAULTS[KEY_FONT_SIZE])))
143145
)
144146
self._word_wrap_cb.setChecked(
145-
settings.value(KEY_WORD_WRAP, DEFAULTS[KEY_WORD_WRAP], type=bool)
147+
bool(settings.value(KEY_WORD_WRAP, DEFAULTS[KEY_WORD_WRAP], type=bool))
146148
)
147149
self._line_numbers_cb.setChecked(
148-
settings.value(KEY_LINE_NUMBERS, DEFAULTS[KEY_LINE_NUMBERS], type=bool)
150+
bool(
151+
settings.value(KEY_LINE_NUMBERS, DEFAULTS[KEY_LINE_NUMBERS], type=bool)
152+
)
149153
)
150154
theme = settings.value(KEY_THEME, DEFAULTS[KEY_THEME])
151155
self._theme_dark_radio.setChecked(theme == "dark")
@@ -161,7 +165,9 @@ def _write_to_settings(self) -> None:
161165
settings.setValue(KEY_FONT_SIZE, self._font_size_spin.value())
162166
settings.setValue(KEY_WORD_WRAP, self._word_wrap_cb.isChecked())
163167
settings.setValue(KEY_LINE_NUMBERS, self._line_numbers_cb.isChecked())
164-
settings.setValue(KEY_THEME, "dark" if self._theme_dark_radio.isChecked() else "light")
168+
settings.setValue(
169+
KEY_THEME, "dark" if self._theme_dark_radio.isChecked() else "light"
170+
)
165171
settings.setValue(KEY_DEFAULT_DIR, self._default_dir_edit.text())
166172

167173
def _on_apply_clicked(self) -> None:

tests/integration/test_live_scenarios.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,7 @@ def test_title_loses_star_after_save(self, app, tmp_path, qtbot):
8787

8888

8989
class TestCleaningWithRealService:
90-
def test_trim_removes_trailing_spaces_and_blank_lines(
91-
self, app, tmp_path, qtbot
92-
):
90+
def test_trim_removes_trailing_spaces_and_blank_lines(self, app, tmp_path, qtbot):
9391
# trim_whitespace strips trailing spaces per line and removes trailing
9492
# blank lines, but does NOT strip leading spaces.
9593
f = tmp_path / "trim.txt"
@@ -148,9 +146,7 @@ def test_find_wraps_from_end_of_document(self, app, tmp_path, qtbot):
148146
app._on_find_clicked() # wraps to start, then finds
149147
assert app._plain_text_edit.textCursor().selectedText() == "quick"
150148

151-
def test_replace_all_updates_editor_and_emits_signal(
152-
self, app, tmp_path, qtbot
153-
):
149+
def test_replace_all_updates_editor_and_emits_signal(self, app, tmp_path, qtbot):
154150
f = tmp_path / "rep.txt"
155151
f.write_text("cat cat cat", encoding="utf-8")
156152
app._file_name_edit.setText(str(f))

0 commit comments

Comments
 (0)