|
1 | | -/* |
| 1 | +/* |
2 | 2 | * Copyright (c) 2014-2026 Patrizio Bekerle -- <patrizio@bekerle.com> |
3 | 3 | * |
4 | 4 | * This program is free software; you can redistribute it and/or modify |
@@ -123,6 +123,11 @@ void QOwnNotesMarkdownHighlighter::highlightBlock(const QString &text) { |
123 | 123 | // Call the per-block highlightingHook in scripts (only invoked if at |
124 | 124 | // least one script provides the hook, checked via cached flag) |
125 | 125 | highlightScriptingHook(text); |
| 126 | + |
| 127 | + // Apply LSP diagnostic underlines last so they overlay all other formatting |
| 128 | + if (!text.isEmpty()) { |
| 129 | + highlightMarkdownLsp(text); |
| 130 | + } |
126 | 131 | } |
127 | 132 |
|
128 | 133 | // Only check for encryption end marker if we're highlighting encrypted text |
@@ -614,3 +619,111 @@ void QOwnNotesMarkdownHighlighter::highlightSpellChecking(const QString &text) { |
614 | 619 | } |
615 | 620 | } |
616 | 621 | } |
| 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 | +} |
0 commit comments