Skip to content

Commit 65dfff3

Browse files
committed
#3467 lsp: add context menu
Signed-off-by: Patrizio Bekerle <patrizio@bekerle.com>
1 parent da3cbf7 commit 65dfff3

3 files changed

Lines changed: 107 additions & 5 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/widgets/qownnotesmarkdowntextedit.cpp

Lines changed: 92 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include <QDragMoveEvent>
1515
#include <QDropEvent>
1616
#include <QEvent>
17+
#include <QEventLoop>
1718
#include <QFileInfo>
1819
#include <QFont>
1920
#include <QFontDatabase>
@@ -2538,11 +2539,6 @@ void QOwnNotesMarkdownTextEdit::onContextMenu(QPoint pos) {
25382539
connect(formatSelectionAction, &QAction::triggered, this,
25392540
[this]() { requestMarkdownLspFormatting(true); });
25402541

2541-
QAction *codeActionsAction = lspMenu->addAction(tr("Code actions"));
2542-
codeActionsAction->setEnabled(isAllowNoteEditing && !_markdownLspDiagnostics.isEmpty());
2543-
connect(codeActionsAction, &QAction::triggered, this,
2544-
[this]() { requestMarkdownLspCodeActions(textCursor()); });
2545-
25462542
menu->addSeparator();
25472543
}
25482544

@@ -2694,6 +2690,8 @@ QMenu *QOwnNotesMarkdownTextEdit::spellCheckContextMenu(QPoint pos) {
26942690
addHarperMenuSection(menu, cursorAtMouse, cursor, hasEntries);
26952691
#endif
26962692

2693+
addMarkdownLspMenuSection(menu, cursorAtMouse, hasEntries);
2694+
26972695
if (!spellchecker || !spellchecker->isActive() || _isSpellCheckingDisabled) {
26982696
if (!hasEntries) {
26992697
delete menu;
@@ -2800,6 +2798,95 @@ QMenu *QOwnNotesMarkdownTextEdit::spellCheckContextMenu(QPoint pos) {
28002798
return menu;
28012799
}
28022800

2801+
void QOwnNotesMarkdownTextEdit::addMarkdownLspMenuSection(QMenu *menu,
2802+
const QTextCursor &cursorAtMouse,
2803+
bool &hasEntries) {
2804+
if (!_markdownLspEnabled || !_markdownLspClient || _markdownLspDiagnostics.isEmpty()) {
2805+
return;
2806+
}
2807+
2808+
// Find a diagnostic whose range covers the cursor position
2809+
const int cursorLine = cursorAtMouse.blockNumber();
2810+
const int cursorCol = cursorAtMouse.positionInBlock();
2811+
const MarkdownLspClient::Diagnostic *match = nullptr;
2812+
for (const auto &diag : std::as_const(_markdownLspDiagnostics)) {
2813+
if (cursorLine >= diag.range.startLine && cursorLine <= diag.range.endLine) {
2814+
// For single-line diagnostics also check column range
2815+
if (diag.range.startLine == diag.range.endLine) {
2816+
if (cursorCol >= diag.range.startCharacter &&
2817+
cursorCol <= diag.range.endCharacter) {
2818+
match = &diag;
2819+
break;
2820+
}
2821+
} else {
2822+
match = &diag;
2823+
break;
2824+
}
2825+
}
2826+
}
2827+
2828+
if (!match) {
2829+
return;
2830+
}
2831+
2832+
// Add separator before LSP section if other entries already exist
2833+
if (hasEntries) {
2834+
menu->addSeparator();
2835+
}
2836+
2837+
// Show the diagnostic message as a disabled header
2838+
const QString header = tr("Markdown LSP: %1").arg(match->message);
2839+
QAction *headerAction = menu->addAction(header);
2840+
headerAction->setEnabled(false);
2841+
2842+
// Request code actions for this diagnostic and wait briefly for the response
2843+
// using a local event loop (same pattern as QDialog::exec)
2844+
MarkdownLspClient::DiagnosticRange range = match->range;
2845+
_markdownLspLastCodeActions.clear();
2846+
_markdownLspCodeActionRequestId =
2847+
_markdownLspClient->requestCodeActions(_markdownLspUri, range, {*match});
2848+
2849+
if (_markdownLspCodeActionRequestId >= 0) {
2850+
QVector<MarkdownLspClient::CodeAction> receivedActions;
2851+
int receivedId = -1;
2852+
2853+
// Wait up to 2 seconds for the response
2854+
QEventLoop loop;
2855+
QTimer timeout;
2856+
timeout.setSingleShot(true);
2857+
timeout.setInterval(2000);
2858+
connect(&timeout, &QTimer::timeout, &loop, &QEventLoop::quit);
2859+
connect(_markdownLspClient, &MarkdownLspClient::codeActionsReceived, &loop,
2860+
[&](int id, const QVector<MarkdownLspClient::CodeAction> &actions) {
2861+
receivedId = id;
2862+
receivedActions = actions;
2863+
loop.quit();
2864+
});
2865+
timeout.start();
2866+
loop.exec();
2867+
2868+
if (receivedId == _markdownLspCodeActionRequestId) {
2869+
_markdownLspCodeActionRequestId = -1;
2870+
if (receivedActions.isEmpty()) {
2871+
QAction *noFixAction = menu->addAction(tr("No fixes available"));
2872+
noFixAction->setEnabled(false);
2873+
} else {
2874+
for (const auto &action : std::as_const(receivedActions)) {
2875+
menu->addAction(action.title, this, [this, action]() {
2876+
if (action.hasEdits()) {
2877+
applyMarkdownLspTextEdits(action.edits);
2878+
} else if (action.hasCommand()) {
2879+
_markdownLspClient->executeCommand(action.command);
2880+
}
2881+
});
2882+
}
2883+
}
2884+
}
2885+
}
2886+
2887+
hasEntries = true;
2888+
}
2889+
28032890
#ifdef LANGUAGETOOL_ENABLED
28042891
void QOwnNotesMarkdownTextEdit::addLanguageToolMenuSection(QMenu *menu,
28052892
const QTextCursor &cursorAtMouse,

src/widgets/qownnotesmarkdowntextedit.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ class QOwnNotesMarkdownTextEdit : public QMarkdownTextEdit {
178178
void overrideFontSizeStyle(int fontSize);
179179

180180
QMenu *spellCheckContextMenu(QPoint pos);
181+
void addMarkdownLspMenuSection(QMenu *menu, const QTextCursor &cursorAtMouse, bool &hasEntries);
181182
#ifdef LANGUAGETOOL_ENABLED
182183
void addLanguageToolMenuSection(QMenu *menu, const QTextCursor &cursorAtMouse,
183184
const QTextCursor &selectedCursor, bool &hasEntries);

0 commit comments

Comments
 (0)