|
14 | 14 | #include <QDragMoveEvent> |
15 | 15 | #include <QDropEvent> |
16 | 16 | #include <QEvent> |
| 17 | +#include <QEventLoop> |
17 | 18 | #include <QFileInfo> |
18 | 19 | #include <QFont> |
19 | 20 | #include <QFontDatabase> |
@@ -2538,11 +2539,6 @@ void QOwnNotesMarkdownTextEdit::onContextMenu(QPoint pos) { |
2538 | 2539 | connect(formatSelectionAction, &QAction::triggered, this, |
2539 | 2540 | [this]() { requestMarkdownLspFormatting(true); }); |
2540 | 2541 |
|
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 | | - |
2546 | 2542 | menu->addSeparator(); |
2547 | 2543 | } |
2548 | 2544 |
|
@@ -2694,6 +2690,8 @@ QMenu *QOwnNotesMarkdownTextEdit::spellCheckContextMenu(QPoint pos) { |
2694 | 2690 | addHarperMenuSection(menu, cursorAtMouse, cursor, hasEntries); |
2695 | 2691 | #endif |
2696 | 2692 |
|
| 2693 | + addMarkdownLspMenuSection(menu, cursorAtMouse, hasEntries); |
| 2694 | + |
2697 | 2695 | if (!spellchecker || !spellchecker->isActive() || _isSpellCheckingDisabled) { |
2698 | 2696 | if (!hasEntries) { |
2699 | 2697 | delete menu; |
@@ -2800,6 +2798,95 @@ QMenu *QOwnNotesMarkdownTextEdit::spellCheckContextMenu(QPoint pos) { |
2800 | 2798 | return menu; |
2801 | 2799 | } |
2802 | 2800 |
|
| 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 | + |
2803 | 2890 | #ifdef LANGUAGETOOL_ENABLED |
2804 | 2891 | void QOwnNotesMarkdownTextEdit::addLanguageToolMenuSection(QMenu *menu, |
2805 | 2892 | const QTextCursor &cursorAtMouse, |
|
0 commit comments