Skip to content

Commit c8010ea

Browse files
committed
#3467 lsp: add context menu and fix markings
Signed-off-by: Patrizio Bekerle <patrizio@bekerle.com>
1 parent 3585785 commit c8010ea

5 files changed

Lines changed: 278 additions & 109 deletions

File tree

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,20 @@
22

33
## 26.4.23
44

5+
- Fixed a URI percent-encoding mismatch that prevented Markdown LSP diagnostics
6+
from being applied when a note filename contains spaces; the incoming LSP URI
7+
(e.g. `Top%20heading.md`) is now decoded with `QUrl::fromPercentEncoding`
8+
before being compared to the stored document URI so wave-underlines appear
9+
correctly for notes with spaces in their name
10+
(for [#3467](https://github.com/pbek/QOwnNotes/issues/3467))
11+
- Added an inline **Markdown LSP diagnostic context menu** section that appears
12+
when right-clicking on a wave-underlined region in the note editor; it shows
13+
the diagnostic message as a header and fetches available LSP code actions
14+
(fixes) via a short synchronous wait, then lists each fix as a clickable menu
15+
item — matching the same UX pattern used by the LanguageTool and Harper
16+
context menus; the old standalone **Code actions** menu item in the
17+
**Markdown LSP** submenu has been removed in favour of this inline approach
18+
(for [#3467](https://github.com/pbek/QOwnNotes/issues/3467))
519
- Fixed shortcuts not being saved or restored in the **Shortcuts** settings;
620
`storeShortcutSettings` now iterates the actual menu actions (mirroring how
721
`initShortcuts` reads them) and looks up the corresponding widgets in the

src/helpers/qownnotesmarkdownhighlighter.cpp

Lines changed: 114 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/*
1+
/*
22
* Copyright (c) 2014-2026 Patrizio Bekerle -- <patrizio@bekerle.com>
33
*
44
* This program is free software; you can redistribute it and/or modify
@@ -123,6 +123,11 @@ void QOwnNotesMarkdownHighlighter::highlightBlock(const QString &text) {
123123
// Call the per-block highlightingHook in scripts (only invoked if at
124124
// least one script provides the hook, checked via cached flag)
125125
highlightScriptingHook(text);
126+
127+
// Apply LSP diagnostic underlines last so they overlay all other formatting
128+
if (!text.isEmpty()) {
129+
highlightMarkdownLsp(text);
130+
}
126131
}
127132

128133
// Only check for encryption end marker if we're highlighting encrypted text
@@ -614,3 +619,111 @@ void QOwnNotesMarkdownHighlighter::highlightSpellChecking(const QString &text) {
614619
}
615620
}
616621
}
622+
623+
/**
624+
* Stores LSP diagnostics in the block-keyed cache and triggers a rehighlight
625+
* of every affected block so the underlines appear via QSyntaxHighlighter::setFormat(),
626+
* exactly like the LanguageTool and Harper integrations.
627+
*/
628+
void QOwnNotesMarkdownHighlighter::setMarkdownLspDiagnostics(
629+
const QVector<MarkdownLspClient::Diagnostic> &diagnostics) {
630+
_lspDiagnosticsCache.clear();
631+
632+
for (const MarkdownLspClient::Diagnostic &diag : diagnostics) {
633+
if (diag.message.isEmpty()) {
634+
continue;
635+
}
636+
637+
// Choose underline color based on LSP severity:
638+
// 1 = Error, 2 = Warning, 3 = Information, 4 = Hint
639+
QColor color;
640+
switch (diag.severity) {
641+
case 1:
642+
color = QColor(214, 68, 68); // Red for errors
643+
break;
644+
case 2:
645+
color = QColor(210, 140, 30); // Orange for warnings
646+
break;
647+
case 3:
648+
color = QColor(80, 140, 210); // Blue for information
649+
break;
650+
case 4:
651+
color = QColor(100, 170, 100); // Green for hints
652+
break;
653+
default:
654+
color = QColor(214, 68, 68);
655+
break;
656+
}
657+
658+
// A diagnostic may span multiple lines; add an entry for every covered line
659+
for (int line = diag.range.startLine; line <= diag.range.endLine; ++line) {
660+
LspBlockDiagnostic entry;
661+
entry.color = color;
662+
entry.toolTip = diag.message;
663+
664+
if (line == diag.range.startLine && line == diag.range.endLine) {
665+
entry.startCharacter = diag.range.startCharacter;
666+
entry.endCharacter = diag.range.endCharacter;
667+
} else if (line == diag.range.startLine) {
668+
entry.startCharacter = diag.range.startCharacter;
669+
entry.endCharacter = INT_MAX; // Highlight to end of line
670+
} else if (line == diag.range.endLine) {
671+
entry.startCharacter = 0;
672+
entry.endCharacter = diag.range.endCharacter;
673+
} else {
674+
entry.startCharacter = 0;
675+
entry.endCharacter = INT_MAX; // Highlight entire intermediate line
676+
}
677+
678+
_lspDiagnosticsCache[line].append(entry);
679+
}
680+
}
681+
}
682+
683+
/**
684+
* Clears all cached LSP diagnostics.
685+
*/
686+
void QOwnNotesMarkdownHighlighter::clearMarkdownLspDiagnostics() { _lspDiagnosticsCache.clear(); }
687+
688+
/**
689+
* Applies wave underlines for LSP diagnostics on the current block via
690+
* QSyntaxHighlighter::setFormat(), so they behave identically to spell-check
691+
* and grammar-checker underlines (document-level, not view-level).
692+
*/
693+
void QOwnNotesMarkdownHighlighter::highlightMarkdownLsp(const QString &text) {
694+
const int blockNumber = currentBlock().blockNumber();
695+
const auto it = _lspDiagnosticsCache.constFind(blockNumber);
696+
if (it == _lspDiagnosticsCache.constEnd()) {
697+
return;
698+
}
699+
700+
qDebug() << "LSP highlightMarkdownLsp: block" << blockNumber << "entries:" << it->size()
701+
<< "text:" << text.left(40);
702+
for (const LspBlockDiagnostic &entry : *it) {
703+
const int start = entry.startCharacter;
704+
const int end = (entry.endCharacter == INT_MAX) ? text.length() : entry.endCharacter;
705+
const int count = end - start;
706+
if (count <= 0 || start >= text.length()) {
707+
continue;
708+
}
709+
setMarkdownLspUnderline(start, qMin(count, text.length() - start), entry.color,
710+
entry.toolTip);
711+
}
712+
}
713+
714+
/**
715+
* Applies a wave underline to a character range in the current block, merging
716+
* with the existing per-character format to preserve syntax highlighting colors.
717+
*/
718+
void QOwnNotesMarkdownHighlighter::setMarkdownLspUnderline(int start, int count,
719+
const QColor &color,
720+
const QString &toolTip) {
721+
for (int i = start; i < start + count; ++i) {
722+
QTextCharFormat format = QSyntaxHighlighter::format(i);
723+
format.setFontUnderline(true);
724+
format.setUnderlineStyle(QTextCharFormat::SpellCheckUnderline);
725+
format.setUnderlineColor(color);
726+
format.setToolTip(toolTip);
727+
setFormat(i, 1, format);
728+
}
729+
}

src/helpers/qownnotesmarkdownhighlighter.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
#include <entities/note.h>
1919
#include <libraries/qmarkdowntextedit/markdownhighlighter.h>
20+
#include <services/markdownlspclient.h>
2021

2122
#include <QHash>
2223

@@ -41,6 +42,9 @@ class QOwnNotesMarkdownHighlighter : public MarkdownHighlighter {
4142

4243
void updateCurrentNote(Note *note);
4344

45+
void setMarkdownLspDiagnostics(const QVector<MarkdownLspClient::Diagnostic> &diagnostics);
46+
void clearMarkdownLspDiagnostics();
47+
4448
struct ScriptingHighlightingRule {
4549
explicit ScriptingHighlightingRule(const HighlighterState state_) : state(state_) {}
4650
ScriptingHighlightingRule() = default;
@@ -69,6 +73,9 @@ class QOwnNotesMarkdownHighlighter : public MarkdownHighlighter {
6973
void highlightWikiLinks(const QString &text);
7074
void clearWikiLinkCache();
7175

76+
void highlightMarkdownLsp(const QString &text);
77+
void setMarkdownLspUnderline(int start, int count, const QColor &color, const QString &toolTip);
78+
7279
// Set the format of a word as misspelled i.e., red wavy underline
7380
void setMisspelled(const int start, const int count);
7481
void highlightSpellChecking(const QString &text);
@@ -93,6 +100,16 @@ class QOwnNotesMarkdownHighlighter : public MarkdownHighlighter {
93100
QRegularExpression _regexTagStyleLink;
94101
QRegularExpression _regexBracketLink;
95102
QHash<QString, bool> _wikiLinkCache;
103+
104+
// Cache of LSP diagnostics keyed by block (line) number.
105+
// Each entry holds one or more diagnostics that touch that block.
106+
struct LspBlockDiagnostic {
107+
int startCharacter = 0;
108+
int endCharacter = 0;
109+
QColor color;
110+
QString toolTip;
111+
};
112+
QHash<int, QVector<LspBlockDiagnostic>> _lspDiagnosticsCache;
96113
void highlightScriptingRules(const QVector<ScriptingHighlightingRule> &rules,
97114
const QString &text);
98115
void highlightScriptingHook(const QString &text);

0 commit comments

Comments
 (0)