diff --git a/docs/source/more/customise.rst b/docs/source/more/customise.rst index ef06f156..70905def 100644 --- a/docs/source/more/customise.rst +++ b/docs/source/more/customise.rst @@ -107,6 +107,7 @@ A colour theme ``.conf`` file consists of the following settings: [Syntax] background = base text = default + line = default:32 link = blue headertext = green headertag = green:L135 @@ -175,7 +176,7 @@ There are several ways to enter colour values: .. versionadded:: 2.8 The ``[Syntax]`` section was moved into the main theme file. Previously, these settings were in - their own file. The ``[Icons]`` section was renamed to ``[Base]``. + their own file. The ``[Icons]`` section was renamed to ``[Base]``. Added the ``line`` setting. Icon Themes diff --git a/novelwriter/assets/themes/blue_streak_dark.conf b/novelwriter/assets/themes/blue_streak_dark.conf index ed0f912d..7b4133a5 100644 --- a/novelwriter/assets/themes/blue_streak_dark.conf +++ b/novelwriter/assets/themes/blue_streak_dark.conf @@ -56,6 +56,7 @@ errortext = red [Syntax] background = base text = default +line = blue:48 link = blue headertext = blue headertag = blue:D150 diff --git a/novelwriter/assets/themes/blue_streak_light.conf b/novelwriter/assets/themes/blue_streak_light.conf index 0a3673b1..215cc8e1 100644 --- a/novelwriter/assets/themes/blue_streak_light.conf +++ b/novelwriter/assets/themes/blue_streak_light.conf @@ -56,6 +56,7 @@ errortext = red [Syntax] background = base text = default +line = blue:32 link = blue headertext = blue headertag = blue:D150 diff --git a/novelwriter/assets/themes/cyberpunk_night.conf b/novelwriter/assets/themes/cyberpunk_night.conf index 70996a22..4ba750f2 100644 --- a/novelwriter/assets/themes/cyberpunk_night.conf +++ b/novelwriter/assets/themes/cyberpunk_night.conf @@ -56,6 +56,7 @@ errortext = red [Syntax] background = base text = #969696 +line = #282828 link = blue headertext = #ffffff headertag = purple diff --git a/novelwriter/assets/themes/default_dark.conf b/novelwriter/assets/themes/default_dark.conf index 3d5436d7..0c7bd643 100644 --- a/novelwriter/assets/themes/default_dark.conf +++ b/novelwriter/assets/themes/default_dark.conf @@ -57,6 +57,7 @@ errortext = red [Syntax] background = base text = default +line = default:32 link = blue headertext = green headertag = green:D150 diff --git a/novelwriter/assets/themes/default_light.conf b/novelwriter/assets/themes/default_light.conf index 1fb2533d..fb4e707a 100644 --- a/novelwriter/assets/themes/default_light.conf +++ b/novelwriter/assets/themes/default_light.conf @@ -57,6 +57,7 @@ errortext = red [Syntax] background = base text = default +line = default:32 link = blue headertext = green headertag = green:L135 diff --git a/novelwriter/assets/themes/dracula.conf b/novelwriter/assets/themes/dracula.conf index 8e0d0133..3330f04c 100644 --- a/novelwriter/assets/themes/dracula.conf +++ b/novelwriter/assets/themes/dracula.conf @@ -73,6 +73,7 @@ errortext = red [Syntax] background = base text = #f8f8f2 +line = base:L150 link = #ff79c6 headertext = purple headertag = purple:D150 diff --git a/novelwriter/assets/themes/grey_dark.conf b/novelwriter/assets/themes/grey_dark.conf index 862caaf3..570a84b9 100644 --- a/novelwriter/assets/themes/grey_dark.conf +++ b/novelwriter/assets/themes/grey_dark.conf @@ -56,6 +56,7 @@ errortext = red [Syntax] background = #363636 text = default +line = default:32 link = default headertext = default:L115 headertag = default:D125 diff --git a/novelwriter/assets/themes/grey_light.conf b/novelwriter/assets/themes/grey_light.conf index 2da58171..feccddaa 100644 --- a/novelwriter/assets/themes/grey_light.conf +++ b/novelwriter/assets/themes/grey_light.conf @@ -56,6 +56,7 @@ errortext = red [Syntax] background = base text = default +line = default:32 link = default headertext = default:D200 headertag = default:L400 diff --git a/novelwriter/assets/themes/lcars.conf b/novelwriter/assets/themes/lcars.conf index e0130c59..0d5467db 100644 --- a/novelwriter/assets/themes/lcars.conf +++ b/novelwriter/assets/themes/lcars.conf @@ -57,6 +57,7 @@ errortext = red [Syntax] background = base text = default +line = yellow:48 link = purple headertext = orange headertag = red diff --git a/novelwriter/assets/themes/light_owl.conf b/novelwriter/assets/themes/light_owl.conf index 208cb459..a50c48b2 100644 --- a/novelwriter/assets/themes/light_owl.conf +++ b/novelwriter/assets/themes/light_owl.conf @@ -76,6 +76,7 @@ errortext = red [Syntax] background = base text = default +line = default:32 link = blue headertext = blue headertag = blue:160 diff --git a/novelwriter/assets/themes/night_owl.conf b/novelwriter/assets/themes/night_owl.conf index 79f9a5a5..c5ee6dd0 100644 --- a/novelwriter/assets/themes/night_owl.conf +++ b/novelwriter/assets/themes/night_owl.conf @@ -76,6 +76,7 @@ errortext = red [Syntax] background = base text = default +line = default:48 link = blue headertext = blue headertag = blue:160 diff --git a/novelwriter/assets/themes/primer_dark.conf b/novelwriter/assets/themes/primer_dark.conf index 8c26ce67..20be9548 100644 --- a/novelwriter/assets/themes/primer_dark.conf +++ b/novelwriter/assets/themes/primer_dark.conf @@ -56,6 +56,7 @@ errortext = #f85149 [Syntax] background = base text = faded +line = default:32 link = blue headertext = default headertag = faded diff --git a/novelwriter/assets/themes/primer_light.conf b/novelwriter/assets/themes/primer_light.conf index 93e7a87b..6d1263c5 100644 --- a/novelwriter/assets/themes/primer_light.conf +++ b/novelwriter/assets/themes/primer_light.conf @@ -56,6 +56,7 @@ errortext = #cf222e [Syntax] background = base text = faded +line = default:32 link = blue headertext = default headertag = faded:128 diff --git a/novelwriter/assets/themes/snazzy.conf b/novelwriter/assets/themes/snazzy.conf index 0cec0326..09b18a77 100644 --- a/novelwriter/assets/themes/snazzy.conf +++ b/novelwriter/assets/themes/snazzy.conf @@ -69,6 +69,7 @@ errortext = red [Syntax] background = base text = default +line = default:32 link = blue headertext = green headertag = green:D125 diff --git a/novelwriter/assets/themes/solarized_dark.conf b/novelwriter/assets/themes/solarized_dark.conf index e3f146ba..8bf4e983 100644 --- a/novelwriter/assets/themes/solarized_dark.conf +++ b/novelwriter/assets/themes/solarized_dark.conf @@ -75,6 +75,7 @@ errortext = red [Syntax] background = base text = default +line = default:32 link = blue headertext = default:D125 headertag = cyan diff --git a/novelwriter/assets/themes/solarized_light.conf b/novelwriter/assets/themes/solarized_light.conf index 76c2eec2..5f01abe7 100644 --- a/novelwriter/assets/themes/solarized_light.conf +++ b/novelwriter/assets/themes/solarized_light.conf @@ -75,6 +75,7 @@ errortext = red [Syntax] background = base text = default +line = default:32 link = blue headertext = default:L125 headertag = cyan diff --git a/novelwriter/assets/themes/tango_dark.conf b/novelwriter/assets/themes/tango_dark.conf index 8bd7d23c..aacb5e5f 100644 --- a/novelwriter/assets/themes/tango_dark.conf +++ b/novelwriter/assets/themes/tango_dark.conf @@ -66,6 +66,7 @@ errortext = red [Syntax] background = base text = default +line = default:32 link = blue headertext = blue headertag = blue:160 diff --git a/novelwriter/assets/themes/tango_light.conf b/novelwriter/assets/themes/tango_light.conf index df4b0a1b..70d6f741 100644 --- a/novelwriter/assets/themes/tango_light.conf +++ b/novelwriter/assets/themes/tango_light.conf @@ -66,6 +66,7 @@ errortext = red [Syntax] background = base text = default +line = default:32 link = blue headertext = blue headertag = blue:160 diff --git a/novelwriter/assets/themes/tomorrow.conf b/novelwriter/assets/themes/tomorrow.conf index 3175e68b..e355356a 100644 --- a/novelwriter/assets/themes/tomorrow.conf +++ b/novelwriter/assets/themes/tomorrow.conf @@ -76,6 +76,7 @@ errortext = red [Syntax] background = base text = #4d4d4c +line = default:32 link = blue headertext = blue headertag = blue:L135 diff --git a/novelwriter/assets/themes/tomorrow_night.conf b/novelwriter/assets/themes/tomorrow_night.conf index 671d1a9c..41298008 100644 --- a/novelwriter/assets/themes/tomorrow_night.conf +++ b/novelwriter/assets/themes/tomorrow_night.conf @@ -76,6 +76,7 @@ errortext = red [Syntax] background = base text = default +line = default:48 link = blue headertext = blue headertag = blue:D150 diff --git a/novelwriter/assets/themes/tomorrow_night_blue.conf b/novelwriter/assets/themes/tomorrow_night_blue.conf index a9b48dcc..5f951353 100644 --- a/novelwriter/assets/themes/tomorrow_night_blue.conf +++ b/novelwriter/assets/themes/tomorrow_night_blue.conf @@ -76,6 +76,7 @@ errortext = red [Syntax] background = base text = default +line = default:32 link = blue headertext = blue headertag = blue:D150 diff --git a/novelwriter/assets/themes/tomorrow_night_bright.conf b/novelwriter/assets/themes/tomorrow_night_bright.conf index e2df7d59..9207162f 100644 --- a/novelwriter/assets/themes/tomorrow_night_bright.conf +++ b/novelwriter/assets/themes/tomorrow_night_bright.conf @@ -76,6 +76,7 @@ errortext = red [Syntax] background = base text = default +line = default:48 link = blue headertext = blue headertag = blue:D150 diff --git a/novelwriter/assets/themes/tomorrow_night_eighties.conf b/novelwriter/assets/themes/tomorrow_night_eighties.conf index 367e1968..dcd093fb 100644 --- a/novelwriter/assets/themes/tomorrow_night_eighties.conf +++ b/novelwriter/assets/themes/tomorrow_night_eighties.conf @@ -76,6 +76,7 @@ errortext = red [Syntax] background = base text = default +line = default:32 link = blue headertext = blue headertag = blue:D150 diff --git a/novelwriter/config.py b/novelwriter/config.py index 9258bcca..9f422b4d 100644 --- a/novelwriter/config.py +++ b/novelwriter/config.py @@ -77,15 +77,16 @@ class Config: "fmtSQuoteOpen", "focusWidth", "guiFont", "guiLocale", "hasEnchant", "hideFocusFooter", "hideHScroll", "hideVScroll", "highlightEmph", "hostName", "iconColDocs", "iconColTree", "iconTheme", "incNotesWCount", "isDebug", "kernelVer", "lastNotes", "lightTheme", - "mainPanePos", "mainWinSize", "memInfo", "narratorBreak", "narratorDialog", "nativeFont", - "osDarwin", "osLinux", "osType", "osUnknown", "osWindows", "outlinePanePos", - "prefsWinSize", "scrollPastEnd", "searchCase", "searchLoop", "searchMatchCap", - "searchNextFile", "searchProjCase", "searchProjRegEx", "searchProjWord", "searchRegEx", - "searchWord", "showEditToolBar", "showFullPath", "showLineEndings", "showMultiSpaces", - "showSessionTime", "showTabsNSpaces", "showViewerPanel", "spellLanguage", "stopWhenIdle", - "tabWidth", "textFont", "textMargin", "textWidth", "themeMode", "useCharCount", - "userIdleTime", "verPyQtString", "verPyQtValue", "verPyString", "verQtString", - "verQtValue", "viewComments", "viewPanePos", "viewSynopsis", "welcomeWinSize", + "lineHighlight", "mainPanePos", "mainWinSize", "memInfo", "narratorBreak", + "narratorDialog", "nativeFont", "osDarwin", "osLinux", "osType", "osUnknown", "osWindows", + "outlinePanePos", "prefsWinSize", "scrollPastEnd", "searchCase", "searchLoop", + "searchMatchCap", "searchNextFile", "searchProjCase", "searchProjRegEx", "searchProjWord", + "searchRegEx", "searchWord", "showEditToolBar", "showFullPath", "showLineEndings", + "showMultiSpaces", "showSessionTime", "showTabsNSpaces", "showViewerPanel", + "spellLanguage", "stopWhenIdle", "tabWidth", "textFont", "textMargin", "textWidth", + "themeMode", "useCharCount", "userIdleTime", "verPyQtString", "verPyQtValue", + "verPyString", "verQtString", "verQtValue", "viewComments", "viewPanePos", "viewSynopsis", + "welcomeWinSize", ) LANG_NW = 1 @@ -191,6 +192,7 @@ class Config: self.textMargin = 40 # Editor/viewer text margin self.tabWidth = 40 # Editor tabulator width self.cursorWidth = 1 # Editor cursor width + self.lineHighlight = False # Highlight current line in editor self.focusWidth = 800 # Focus Mode text width self.hideFocusFooter = False # Hide document footer in Focus Mode @@ -667,6 +669,7 @@ class Config: self.textMargin = conf.rdInt(sec, "margin", self.textMargin) self.tabWidth = conf.rdInt(sec, "tabwidth", self.tabWidth) self.cursorWidth = conf.rdInt(sec, "cursorwidth", self.cursorWidth) + self.lineHighlight = conf.rdBool(sec, "linehighlight", self.lineHighlight) self.focusWidth = conf.rdInt(sec, "focuswidth", self.focusWidth) self.hideFocusFooter = conf.rdBool(sec, "hidefocusfooter", self.hideFocusFooter) self.doJustify = conf.rdBool(sec, "justify", self.doJustify) @@ -793,6 +796,7 @@ class Config: "margin": str(self.textMargin), "tabwidth": str(self.tabWidth), "cursorwidth": str(self.cursorWidth), + "lineHighlight": str(self.lineHighlight), "focuswidth": str(self.focusWidth), "hidefocusfooter": str(self.hideFocusFooter), "justify": str(self.doJustify), diff --git a/novelwriter/dialogs/preferences.py b/novelwriter/dialogs/preferences.py index 1a8645b3..23620cef 100644 --- a/novelwriter/dialogs/preferences.py +++ b/novelwriter/dialogs/preferences.py @@ -544,6 +544,13 @@ class GuiPreferences(NDialog): unit=self.tr("px") ) + # Highlight Current Line + self.lineHighlight = NSwitch(self) + self.lineHighlight.setChecked(CONFIG.lineHighlight) + self.mainForm.addRow( + self.tr("Highlight current line"), self.lineHighlight + ) + # Show Tabs and Spaces self.showTabsNSpaces = NSwitch(self) self.showTabsNSpaces.setChecked(CONFIG.showTabsNSpaces) @@ -1032,9 +1039,14 @@ class GuiPreferences(NDialog): CONFIG.tabWidth = self.tabWidth.value() # Text Editing + lineHighlight = self.lineHighlight.isChecked() + + updateSyntax |= CONFIG.lineHighlight != lineHighlight + CONFIG.spellLanguage = self.spellLanguage.currentData() CONFIG.autoSelect = self.autoSelect.isChecked() CONFIG.cursorWidth = self.cursorWidth.value() + CONFIG.lineHighlight = lineHighlight CONFIG.showTabsNSpaces = self.showTabsNSpaces.isChecked() CONFIG.showLineEndings = self.showLineEndings.isChecked() diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py index 846b6317..5e49d67e 100644 --- a/novelwriter/gui/doceditor.py +++ b/novelwriter/gui/doceditor.py @@ -45,11 +45,11 @@ from PyQt6.QtCore import ( from PyQt6.QtGui import ( QAction, QCursor, QDragEnterEvent, QDragMoveEvent, QDropEvent, QKeyEvent, QKeySequence, QMouseEvent, QPalette, QPixmap, QResizeEvent, QShortcut, - QTextBlock, QTextCursor, QTextDocument, QTextOption + QTextBlock, QTextCursor, QTextDocument, QTextFormat, QTextOption ) from PyQt6.QtWidgets import ( QApplication, QFrame, QGridLayout, QHBoxLayout, QLabel, QLineEdit, QMenu, - QPlainTextEdit, QToolBar, QVBoxLayout, QWidget + QPlainTextEdit, QTextEdit, QToolBar, QVBoxLayout, QWidget ) from novelwriter import CONFIG, SHARED @@ -75,7 +75,8 @@ from novelwriter.tools.lipsum import GuiLipsum from novelwriter.types import ( QtAlignCenterTop, QtAlignJustify, QtAlignLeft, QtAlignLeftTop, QtAlignRight, QtKeepAnchor, QtModCtrl, QtModNone, QtModShift, QtMouseLeft, - QtMoveAnchor, QtMoveLeft, QtMoveRight, QtScrollAlwaysOff, QtScrollAsNeeded + QtMoveAnchor, QtMoveLeft, QtMoveRight, QtScrollAlwaysOff, QtScrollAsNeeded, + QtTransparent ) logger = logging.getLogger(__name__) @@ -145,6 +146,8 @@ class GuiDocEditor(QPlainTextEdit): self._lastActive = 0.0 # Timestamp of last activity self._lastFind = None # Position of the last found search word self._doReplace = False # Switch to temporarily disable auto-replace + self._lineColor = QtTransparent + self._selection = QTextEdit.ExtraSelection() # Auto-Replace self._autoReplace = TextAutoReplace() @@ -315,6 +318,10 @@ class GuiDocEditor(QPlainTextEdit): self.docHeader.matchColors() self.docFooter.matchColors() + self._lineColor = syntax.line + self._selection.format.setBackground(self._lineColor) + self._selection.format.setProperty(QTextFormat.Property.FullWidthSelection, True) + return def initEditor(self) -> None: @@ -372,6 +379,8 @@ class GuiDocEditor(QPlainTextEdit): # Refresh sizes self.setTabStopDistance(CONFIG.tabWidth) self.setCursorWidth(CONFIG.cursorWidth) + self.setExtraSelections([]) + self._cursorMoved() # If we have a document open, we should refresh it in case the # font changed, otherwise we just clear the editor entirely, @@ -1110,6 +1119,10 @@ class GuiDocEditor(QPlainTextEdit): def _cursorMoved(self) -> None: """Triggered when the cursor moved in the editor.""" self.docFooter.updateLineCount(self.textCursor()) + if CONFIG.lineHighlight: + self._selection.cursor = self.textCursor() + self._selection.cursor.clearSelection() + self.setExtraSelections([self._selection]) return @pyqtSlot(int, int, str) diff --git a/novelwriter/gui/theme.py b/novelwriter/gui/theme.py index fbde3028..a92c6add 100644 --- a/novelwriter/gui/theme.py +++ b/novelwriter/gui/theme.py @@ -80,6 +80,7 @@ class SyntaxColors: back: QColor = QColor(255, 255, 255) text: QColor = QColor(0, 0, 0) + line: QColor = QColor(0, 0, 0) link: QColor = QColor(0, 0, 0) head: QColor = QColor(0, 0, 0) headH: QColor = QColor(0, 0, 0) @@ -364,6 +365,7 @@ class GuiTheme: if parser.has_section(sec): self.syntaxTheme.back = self._readColor(parser, sec, "background") self.syntaxTheme.text = self._readColor(parser, sec, "text") + self.syntaxTheme.line = self._readColor(parser, sec, "line") self.syntaxTheme.link = self._readColor(parser, sec, "link") self.syntaxTheme.head = self._readColor(parser, sec, "headertext") self.syntaxTheme.headH = self._readColor(parser, sec, "headertag") diff --git a/tests/conftest.py b/tests/conftest.py index 4017bb9f..d8407d28 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -65,7 +65,11 @@ def resetConfigVars(): CONFIG.darkTheme = DEF_GUI_DARK CONFIG.lightTheme = DEF_GUI_LIGHT CONFIG.themeMode = nwTheme.LIGHT - CONFIG.emphLabels = True # Ensures better coverage, off by default + + # Enable a few settings to ensure better coverage + CONFIG.emphLabels = True + CONFIG.lineHighlight = True + return diff --git a/tests/reference/baseConfig_novelwriter.conf b/tests/reference/baseConfig_novelwriter.conf index fa16b190..859cca52 100644 --- a/tests/reference/baseConfig_novelwriter.conf +++ b/tests/reference/baseConfig_novelwriter.conf @@ -1,5 +1,5 @@ [Meta] -timestamp = 2025-06-10 23:18:12 +timestamp = 2025-06-14 17:03:01 [Main] font = @@ -40,6 +40,7 @@ width = 700 margin = 40 tabwidth = 40 cursorwidth = 1 +linehighlight = False focuswidth = 800 hidefocusfooter = False justify = False diff --git a/tests/test_dialogs/test_dlg_preferences.py b/tests/test_dialogs/test_dlg_preferences.py index 66d6183e..da87a629 100644 --- a/tests/test_dialogs/test_dlg_preferences.py +++ b/tests/test_dialogs/test_dlg_preferences.py @@ -250,12 +250,14 @@ def testDlgPreferences_Settings(qtbot, monkeypatch, nwGUI, fncPath, tstPaths): prefs.spellLanguage.setCurrentIndex(prefs.spellLanguage.findData("de")) prefs.autoSelect.setChecked(False) prefs.cursorWidth.setValue(5) + prefs.lineHighlight.setChecked(False) prefs.showTabsNSpaces.setChecked(True) prefs.showLineEndings.setChecked(True) assert CONFIG.spellLanguage != "de" assert CONFIG.autoSelect is True assert CONFIG.cursorWidth == 1 + assert CONFIG.lineHighlight is True assert CONFIG.showTabsNSpaces is False assert CONFIG.showLineEndings is False @@ -390,6 +392,7 @@ def testDlgPreferences_Settings(qtbot, monkeypatch, nwGUI, fncPath, tstPaths): assert CONFIG.spellLanguage == "de" assert CONFIG.autoSelect is False assert CONFIG.cursorWidth == 5 + assert CONFIG.lineHighlight is False assert CONFIG.showTabsNSpaces is True assert CONFIG.showLineEndings is True diff --git a/tests/test_gui/test_gui_theme.py b/tests/test_gui/test_gui_theme.py index 04e4c62b..805f5009 100644 --- a/tests/test_gui/test_gui_theme.py +++ b/tests/test_gui/test_gui_theme.py @@ -632,7 +632,7 @@ def testGuiTheme_CheckTheme(theme): "helptext", "fadedtext", "errortext", ], "Syntax": [ - "background", "text", "link", "headertext", "headertag", + "background", "text", "line", "link", "headertext", "headertag", "emphasis", "dialog", "altdialog", "hidden", "note", "shortcode", "keyword", "tag", "value", "optional", "spellcheckline", "errorline", "replacetag", "modifier", "texthighlight",