diff --git a/CHANGELOG.md b/CHANGELOG.md index 0027e37..eaf83a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ # 0.7.6 * Add .desktop file for Debian + * Fix Pomodoro timer rounding so it rounds up to 0.25, but rounds to closest quarter (up or down) for minutes higher than that, instead of always up to next quarter. + * Allow setting a code block on a line that already has text (it will start a newline for the codeblock) + * Retain indentation when tab is used to indent a line, unless enter is pressed twice or user deletes the indentation # 0.7.5 diff --git a/bouquin/markdown_editor.py b/bouquin/markdown_editor.py index 3d30889..78af734 100644 --- a/bouquin/markdown_editor.py +++ b/bouquin/markdown_editor.py @@ -89,6 +89,12 @@ class MarkdownEditor(QTextEdit): # Track current list type for smart enter handling self._last_enter_was_empty = False + # Track "double-enter" behavior for indentation retention. + # If we auto-insert indentation on a new line, the next Enter on that + # now-empty indented line should remove the indentation and return to + # column 0 (similar to how lists exit on a second Enter). + self._last_enter_was_empty_indent = False + # Track if we're currently updating text programmatically self._updating = False @@ -919,8 +925,10 @@ class MarkdownEditor(QTextEdit): before = line[:pos_in_block] # "before" currently contains whatever's before the *third* backtick. - # We trigger only when the line is (whitespace + "``") before the caret. - if before.endswith("``") and before.strip() == "``": + # Trigger when the user types a *third consecutive* backtick anywhere on the line. + # (We require the run immediately before the caret to be exactly two backticks, + # so we don't trigger on 4+ backticks.) + if before.endswith("``") and (len(before) < 3 or before[-3] != "`"): doc = self.document() if doc is not None: # Remove the two backticks that were already typed @@ -1126,6 +1134,10 @@ class MarkdownEditor(QTextEdit): cursor = self.textCursor() current_line = self._get_current_line() + # Leading indentation (tabs/spaces) on the current line. + m_indent = re.match(r"^([ \t]*)", current_line) + line_indent = m_indent.group(1) if m_indent else "" + # Check if we're in a code block current_block = cursor.block() line_text = current_block.text() @@ -1215,13 +1227,43 @@ class MarkdownEditor(QTextEdit): # Insert newline and continue the list super().keyPressEvent(event) cursor = self.textCursor() - cursor.insertText(prefix) + # Preserve any leading indentation so nested lists keep their level. + cursor.insertText(line_indent + prefix) + self._last_enter_was_empty_indent = False return else: + # Not a list: support indentation retention. If a line starts + # with indentation (tabs/spaces), carry that indentation to the + # next line. A *second* Enter on an empty indented line resets + # back to column 0. + if line_indent: + rest = current_line[len(line_indent) :] + indent_only = rest.strip() == "" + + if indent_only and self._last_enter_was_empty_indent: + # Second Enter on an empty indented line: remove the + # indentation-only line and start a fresh, unindented line. + cursor.select(QTextCursor.SelectionType.LineUnderCursor) + cursor.removeSelectedText() + cursor.insertText("\n") + self._last_enter_was_empty_indent = False + self._last_enter_was_empty = False + return + + # First Enter (or a non-empty indented line): keep the indent. + super().keyPressEvent(event) + cursor = self.textCursor() + cursor.insertText(line_indent) + self._last_enter_was_empty_indent = True + self._last_enter_was_empty = False + return + self._last_enter_was_empty = False + self._last_enter_was_empty_indent = False else: # Any other key resets the empty enter flag self._last_enter_was_empty = False + self._last_enter_was_empty_indent = False # Default handling super().keyPressEvent(event) diff --git a/bouquin/pomodoro_timer.py b/bouquin/pomodoro_timer.py index e66c1f4..bde75fb 100644 --- a/bouquin/pomodoro_timer.py +++ b/bouquin/pomodoro_timer.py @@ -111,6 +111,25 @@ class PomodoroManager: self._parent = parent_window self._active_timer: Optional[PomodoroTimer] = None + @staticmethod + def _seconds_to_logged_hours(elapsed_seconds: int) -> float: + """Convert elapsed seconds to decimal hours for logging. + + Rules: + - For very short runs (< 15 minutes), always round up to 0.25h (15 minutes). + - Otherwise, round to the closest 0.25h (15-minute) increment. + Halfway cases (e.g., 22.5 minutes) round up. + """ + if elapsed_seconds < 0: + elapsed_seconds = 0 + + # 15 minutes = 900 seconds + if elapsed_seconds < 900: + return 0.25 + + quarters = int(math.floor((elapsed_seconds / 900.0) + 0.5)) + return quarters * 0.25 + def start_timer_for_line(self, line_text: str, date_iso: str): """ Start a new timer for the given line of text and embed it into the @@ -156,9 +175,8 @@ class PomodoroManager: def _on_timer_stopped(self, elapsed_seconds: int, task_text: str, date_iso: str): """Handle timer stop - open time log dialog with pre-filled data.""" - # Convert seconds to decimal hours, rounding up to the nearest 0.25 hour (15 minutes) - quarter_hours = math.ceil(elapsed_seconds / 900) - hours = quarter_hours * 0.25 + # Convert seconds to decimal hours, and handle rounding up or down + hours = self._seconds_to_logged_hours(elapsed_seconds) # Ensure minimum of 0.25 hours if hours < 0.25: