Increase line spacing between lines (except for code blocks)

This commit is contained in:
Miguel Jacq 2025-11-14 11:26:09 +11:00
parent 8cd9538a50
commit 0773084ec5
Signed by: mig5
GPG key ID: 59B3F0C24135C6A9
2 changed files with 101 additions and 10 deletions

View file

@ -3,6 +3,7 @@
* Fix a few small matters identified with tests * Fix a few small matters identified with tests
* Make locales dynamically detected from the locales dir rather than hardcoded * Make locales dynamically detected from the locales dir rather than hardcoded
* Add version information in the navigation * Add version information in the navigation
* Increase line spacing between lines (except for code blocks)
# 0.2.1.8 # 0.2.1.8

View file

@ -12,6 +12,7 @@ from PySide6.QtGui import (
QTextCursor, QTextCursor,
QTextDocument, QTextDocument,
QTextFormat, QTextFormat,
QTextBlockFormat,
QTextImageFormat, QTextImageFormat,
) )
from PySide6.QtCore import Qt, QRect, QTimer from PySide6.QtCore import Qt, QRect, QTimer
@ -43,6 +44,8 @@ class MarkdownEditor(QTextEdit):
font.setPointSize(10) font.setPointSize(10)
self.setFont(font) self.setFont(font)
self._apply_line_spacing() # 1.25× initial spacing
# Checkbox characters (Unicode for display, markdown for storage) # Checkbox characters (Unicode for display, markdown for storage)
self._CHECK_UNCHECKED_DISPLAY = "" self._CHECK_UNCHECKED_DISPLAY = ""
self._CHECK_CHECKED_DISPLAY = "" self._CHECK_CHECKED_DISPLAY = ""
@ -73,6 +76,8 @@ class MarkdownEditor(QTextEdit):
# reattach the highlighter to the new document # reattach the highlighter to the new document
if hasattr(self, "highlighter") and self.highlighter: if hasattr(self, "highlighter") and self.highlighter:
self.highlighter.setDocument(self.document()) self.highlighter.setDocument(self.document())
self._apply_line_spacing()
self._apply_code_block_spacing()
QTimer.singleShot(0, self._update_code_block_row_backgrounds) QTimer.singleShot(0, self._update_code_block_row_backgrounds)
def showEvent(self, e): def showEvent(self, e):
@ -140,9 +145,10 @@ class MarkdownEditor(QTextEdit):
def _update_code_block_row_backgrounds(self): def _update_code_block_row_backgrounds(self):
"""Paint a full-width background for each line that is in a fenced code block.""" """Paint a full-width background for each line that is in a fenced code block."""
doc = self.document() doc = self.document()
sels = [] if doc is None:
return
# Use the same bg color as the highlighter's code block sels = []
bg_brush = self.highlighter.code_block_format.background() bg_brush = self.highlighter.code_block_format.background()
inside = False inside = False
@ -158,16 +164,12 @@ class MarkdownEditor(QTextEdit):
fmt = QTextCharFormat() fmt = QTextCharFormat()
fmt.setBackground(bg_brush) fmt.setBackground(bg_brush)
fmt.setProperty(QTextFormat.FullWidthSelection, True) fmt.setProperty(QTextFormat.FullWidthSelection, True)
# mark so we can merge with other selections safely
fmt.setProperty(QTextFormat.UserProperty, "codeblock_bg") fmt.setProperty(QTextFormat.UserProperty, "codeblock_bg")
sel.format = fmt sel.format = fmt
cur = QTextCursor(doc) cur = QTextCursor(doc)
cur.setPosition( cur.setPosition(block.position())
block.position()
) # collapsed cursor = whole line when FullWidthSelection
sel.cursor = cur sel.cursor = cur
sels.append(sel) sels.append(sel)
if is_fence: if is_fence:
@ -182,6 +184,60 @@ class MarkdownEditor(QTextEdit):
] ]
self.setExtraSelections(others + sels) self.setExtraSelections(others + sels)
def _apply_line_spacing(self, height: float = 125.0):
"""Apply proportional line spacing to the whole document."""
doc = self.document()
if doc is None:
return
cursor = QTextCursor(doc)
cursor.beginEditBlock()
cursor.select(QTextCursor.Document)
fmt = QTextBlockFormat()
fmt.setLineHeight(
height, # 125.0 = 1.25×
QTextBlockFormat.LineHeightTypes.ProportionalHeight.value,
)
cursor.mergeBlockFormat(fmt)
cursor.endEditBlock()
def _apply_code_block_spacing(self):
"""
Make all fenced code-block lines (including ``` fences) single-spaced.
Call this AFTER _apply_line_spacing().
"""
doc = self.document()
if doc is None:
return
cursor = QTextCursor(doc)
cursor.beginEditBlock()
inside = False
block = doc.begin()
while block.isValid():
text = block.text()
stripped = text.strip()
is_fence = stripped.startswith("```")
is_code_line = is_fence or inside
if is_code_line:
fmt = block.blockFormat()
fmt.setLineHeight(
0.0,
QTextBlockFormat.LineHeightTypes.SingleHeight.value,
)
cursor.setPosition(block.position())
cursor.setBlockFormat(fmt)
if is_fence:
inside = not inside
block = block.next()
cursor.endEditBlock()
def to_markdown(self) -> str: def to_markdown(self) -> str:
"""Export current content as markdown.""" """Export current content as markdown."""
# First, extract any embedded images and convert to markdown # First, extract any embedded images and convert to markdown
@ -255,6 +311,9 @@ class MarkdownEditor(QTextEdit):
finally: finally:
self._updating = False self._updating = False
self._apply_line_spacing()
self._apply_code_block_spacing()
# Render any embedded images # Render any embedded images
self._render_images() self._render_images()
@ -452,9 +511,33 @@ class MarkdownEditor(QTextEdit):
block_state = current_block.userState() block_state = current_block.userState()
# If current line is opening code fence, or we're inside a code block stripped = current_line.strip()
if current_line.strip().startswith("```") or block_state == 1: is_fence_line = stripped.startswith("```")
# Just insert a regular newline - the highlighter will format it as code
if is_fence_line:
# Work out if this fence is closing (inside block before it)
inside_before = self._is_inside_code_block(current_block.previous())
# Insert the newline as usual
super().keyPressEvent(event)
if inside_before:
# We were on the *closing* fence; the new line is outside the block.
# Give that new block normal 1.25× spacing.
new_block = self.textCursor().block()
fmt = new_block.blockFormat()
fmt.setLineHeight(
125.0,
QTextBlockFormat.LineHeightTypes.ProportionalHeight.value,
)
cur2 = self.textCursor()
cur2.setBlockFormat(fmt)
self.setTextCursor(cur2)
return
# Inside a code block (but not on a fence): newline stays code-style
if block_state == 1:
super().keyPressEvent(event) super().keyPressEvent(event)
return return
@ -646,6 +729,9 @@ class MarkdownEditor(QTextEdit):
c.insertText(f"```\n{selected.rstrip()}\n```\n") c.insertText(f"```\n{selected.rstrip()}\n```\n")
if hasattr(self, "_update_code_block_row_backgrounds"): if hasattr(self, "_update_code_block_row_backgrounds"):
self._update_code_block_row_backgrounds() self._update_code_block_row_backgrounds()
# tighten spacing for the new code block
self._apply_code_block_spacing()
self.setFocus() self.setFocus()
return return
@ -710,6 +796,10 @@ class MarkdownEditor(QTextEdit):
if hasattr(self, "_update_code_block_row_backgrounds"): if hasattr(self, "_update_code_block_row_backgrounds"):
self._update_code_block_row_backgrounds() self._update_code_block_row_backgrounds()
# tighten spacing for the new code block
self._apply_code_block_spacing()
self.setFocus() self.setFocus()
def apply_heading(self, size: int): def apply_heading(self, size: int):