# HG changeset patch
# User Peter Demcak <
majs...@gmail.com>
# Date 1772737074 -3600
# Thu Mar 05 19:57:54 2026 +0100
# Node ID d4ed04c8da7120737437170dea6c3aed83978ae8
# Parent 02f645a75099443fb46fdec53801adeed4a0b90d
fileview: dark annotate palette and inverted chunk selection markers
- Add line numbers to annotate margin text in dark theme and clear
extra Scintilla lines to prevent stale margin content
- Use _make_simple_annotate_palette() generating age-based HSL colors
derived from THEME.background (newer=lighter, older=darker)
- Use _updateMarkerDark() with inverted include/exclude semantics:
included chunks are visually marked instead of excluded ones,
matching dark theme conventions
diff -r 02f645a75099 -r d4ed04c8da71 tortoisehg/hgqt/fileview.py
--- a/tortoisehg/hgqt/fileview.py Thu Mar 05 19:57:32 2026 +0100
+++ b/tortoisehg/hgqt/fileview.py Thu Mar 05 19:57:54 2026 +0100
@@ -1279,9 +1279,27 @@
s.font().family().encode('utf-8'))
self._sci.SendScintilla(qsci.SCI_STYLESETSIZE,
s.style(), s.font().pointSize())
- for i, (fctx, _origline) in enumerate(self._links):
- self._sci.setMarginText(i, revtexts[fctx.rev()], s)
+
+ if THEME.enabled:
+ max_line = len(str(len(self._links)))
+
+ n = len(self._links)
+ # fill only real annotate lines
+ for i in range(n):
+ fctx, _origline = self._links[i]
+ lineno = f"{i + 1:>{max_line}}"
+ rev = revtexts.get(fctx.rev(), "")
+ self._sci.setMarginText(i, f"{lineno} {rev}", s)
+
+ # clear any extra Scintilla lines (usually the last blank line)
+ for i in range(n, self._sci.lines()):
+ self._sci.setMarginText(i, "", s)
+
+ else:
+ for i, (fctx, _origline) in enumerate(self._links):
+ self._sci.setMarginText(i, revtexts[fctx.rev()], s)
+
def _updatemarkers(self):
"""Update markers which colorizes each line"""
self._redefinemarkers()
@@ -1290,6 +1308,51 @@
if m is not None:
self._sci.markerAdd(i, m)
+ def _make_simple_annotate_palette(self, filectxs, now, maxcolors, background):
+ """
+ Generate a simple age-based annotate palette.
+
+ Newer revisions are lighter, older are darker.
+ Colors are derived from THEME.background.
+ Returns dict {QColor: [fctxs]} compatible with colormap.makeannotatepalette.
+ """
+ uniq = sorted(set(filectxs), key=lambda fctx: -fctx.date()[0])
+
+ if not uniq:
+ return {}
+
+ maxcolors = max(1, min(maxcolors, len(uniq)))
+
+ newest = uniq[0].date()[0]
+ oldest = uniq[-1].date()[0]
+ span = max(1.0, float(newest - oldest))
+
+ h, s, l, a = background.getHslF()
+
+ buckets = {}
+ for fctx in uniq:
+ age = newest - fctx.date()[0]
+ t = age / span
+ idx = int(t * (maxcolors - 1))
+ buckets.setdefault(idx, []).append(fctx)
+
+ L_MIN = 0.08
+ L_MAX = 0.22
+
+ h0, _, _, a = background.getHslF()
+
+ palette = {}
+ for idx, fctxs in buckets.items():
+ t = idx / max(1, maxcolors - 1)
+ l2 = L_MAX * (1.0 - t) + L_MIN * t
+
+ h = (h0 + t * 300.0) % 360.0 / 360.0
+ color = QColor.fromHslF(h, 0.35, l2, a)
+
+ palette[
color.name()] = fctxs
+
+ return palette
+
def _redefinemarkers(self):
"""Redefine line markers according to the current revs"""
curdate = self._fd.rawContext().date()[0]
@@ -1300,11 +1363,21 @@
self._revmarkers.clear()
filectxs = iter(fctx for fctx, _origline in self._links)
maxcolors = 32 - _FirstAnnotateLineMarker
- palette = colormap.makeannotatepalette(filectxs, curdate,
+
+ if THEME.enabled:
+ palette = self._make_simple_annotate_palette(
+ filectxs,
+ curdate,
+ maxcolors,
+ THEME.background
+ )
+ else:
+ palette = colormap.makeannotatepalette(filectxs, curdate,
maxcolors=maxcolors, maxhues=8,
maxsaturations=16,
mindate=mindate,
isdarktheme=self._isdarktheme)
+
for i, (color, fctxs) in enumerate(palette.items()):
m = _FirstAnnotateLineMarker + i
self._sci.markerDefine(qsci.MarkerSymbol.Background, m)
@@ -1510,7 +1583,10 @@
return
for chunk in fd.changes.hunks:
self._chunkatline[chunk.lineno] = chunk
- self._updateMarker(chunk)
+ if THEME.enabled:
+ self._updateMarkerDark(chunk)
+ else:
+ self._updateMarker(chunk)
def _updateMarker(self, chunk):
excludemsg = ' ' + _('(excluded from the next commit)')
@@ -1553,6 +1629,46 @@
self._sci.clearIndicatorRange(chunk.lineno + 1, 0,
chunk.lineno + chunk.linecount, 0,
self._excludeindicator)
+ def _updateMarkerDark(self, chunk):
+ # In dark theme, invert visual meaning of included/excluded chunks
+ includemsg = ' ' + _('(included in the next commit)')
+ m = self._sci.markersAtLine(chunk.lineno)
+ inclmarked = m & (1 << _IncludedChunkStartMarker)
+ exclmarked = m & (1 << _ExcludedChunkStartMarker)
+
+ if not chunk.excluded and not inclmarked:
+ self._sci.setReadOnly(False)
+ llen = self._sci.lineLength(chunk.lineno)
+ self._sci.insertAt(includemsg, chunk.lineno, llen - 1)
+ self._sci.setReadOnly(True)
+
+ self._sci.markerDelete(chunk.lineno, _ExcludedChunkStartMarker)
+ self._sci.markerAdd(chunk.lineno, _IncludedChunkStartMarker)
+ for i in pycompat.xrange(chunk.linecount - 1):
+ self._sci.markerAdd(chunk.lineno + i + 1, _ExcludedLineMarker)
+ self._sci.fillIndicatorRange(chunk.lineno + 1, 0,
+ chunk.lineno + chunk.linecount, 0,
+ self._excludeindicator)
+
+ if chunk.excluded and inclmarked:
+ self._sci.setReadOnly(False)
+ llen = self._sci.lineLength(chunk.lineno)
+ mlen = len(includemsg.encode('utf-8'))
+ pos = self._sci.positionFromLineIndex(chunk.lineno, llen - mlen - 1)
+ self._sci.SendScintilla(qsci.SCI_SETTARGETSTART, pos)
+ self._sci.SendScintilla(qsci.SCI_SETTARGETEND, pos + mlen)
+ self._sci.SendScintilla(qsci.SCI_REPLACETARGET, 0, b'')
+ self._sci.setReadOnly(True)
+
+ if chunk.excluded and not exclmarked:
+ self._sci.markerDelete(chunk.lineno, _IncludedChunkStartMarker)
+ self._sci.markerAdd(chunk.lineno, _ExcludedChunkStartMarker)
+ for i in pycompat.xrange(chunk.linecount - 1):
+ self._sci.markerDelete(chunk.lineno + i + 1,
+ _ExcludedLineMarker)
+ self._sci.clearIndicatorRange(chunk.lineno + 1, 0,
+ chunk.lineno + chunk.linecount, 0,
+ self._excludeindicator)
#@pyqtSlot(int, int, Qt.KeyboardModifier)
def _onMarginClicked(self, margin, line, state):
@@ -1584,7 +1700,10 @@
for l in lines:
chunk = self._chunkatline[l]
self._fd.setChunkExcluded(chunk, excluded)
- self._updateMarker(chunk)
+ if THEME.enabled:
+ self._updateMarkerDark(chunk)
+ else:
+ self._updateMarker(chunk)
self.chunkSelectionChanged.emit()
def _toggleChunkAtLine(self, line):