@@ -383,31 +383,27 @@ BEGIN-END-POSITIONS is a plist with :begin and :end positions."
383383 (propertize (number-to-string num)
384384 'face 'lsp-sonarlint--step-marker )))
385385
386- (defun lsp-sonarlint--make-full-line-overlay (range )
387- " Create an overlay covering entire line(s) rather than the precise RANGE ."
386+ (defun lsp-sonarlint--make-full-line-overlay (line )
387+ " Create an overlay covering entire LINE ."
388388 (save-excursion
389- (goto-char (plist-get range :begin ))
390- (let ((begin (line-beginning-position )))
391- (goto-char (plist-get range :end ))
392- (let ((end (line-end-position )))
393- (lsp-sonarlint--make-overlay-between `(:begin , begin :end , end ))))))
389+ (goto-char (point-min ))
390+ (forward-line (- line 1 ))
391+ (lsp-sonarlint--make-overlay-between `(:begin ,(line-beginning-position )
392+ :end ,(line-end-position )))))
394393
395394(defun lsp-sonarlint--get-column (pos )
396395 " Get the column of the point position POS."
397396 (save-excursion
398397 (goto-char pos)
399398 (current-column )))
400399
401- (defun lsp-sonarlint--secondary-msg-lens-offset (range )
402- " Compute and return white-space string to align message with RANGE ."
400+ (defun lsp-sonarlint--secondary-msg-lens-offset (position )
401+ " Compute and return number of characters to align message with POSITION ."
403402 (let* ((msg-height (face-attribute 'lsp-sonarlint-embedded-msg-face :height nil 'default ))
404- (default-height (face-attribute 'default :height ))
405- (msg-offset-in-chars
406- (1+ (/ (* (lsp-sonarlint--get-column
407- (plist-get range :begin ))
408- default-height)
409- msg-height))))
410- (make-string msg-offset-in-chars ?\s )))
403+ (default-height (face-attribute 'default :height )))
404+ (1+ (/ (* (lsp-sonarlint--get-column position)
405+ default-height)
406+ msg-height))))
411407
412408(defun lsp-sonarlint--procure-overlays-for-secondary-locations (flows )
413409 " Create overlays for secondary locations in FLOWS.
@@ -428,13 +424,8 @@ Returns a list of plists with the overlay, step number, and message."
428424 (let* ((range-ht (ht-get location " textRange" ))
429425 (range (lsp-sonarlint--get-range-positions range-ht))
430426 (overlay (lsp-sonarlint--make-overlay-between range))
431- (fl-ovl (lsp-sonarlint--make-full-line-overlay range))
432- (message-offset (lsp-sonarlint--secondary-msg-lens-offset range))
433427 (message (ht-get location " message" )))
434428 (overlay-put overlay 'face 'lsp-sonarlint-secondary-location-face )
435- (overlay-put fl-ovl 'before-string
436- (propertize (concat message-offset message " \n " )
437- 'face 'lsp-sonarlint-embedded-msg-face ))
438429 (lsp-sonarlint--add-number-marker overlay step-num)
439430 `(:overlay , overlay :step-num , step-num :message , message )))
440431 locations)))
@@ -516,13 +507,113 @@ pointing to the `:overlay' from LOC-MESSAGE."
516507 (overlay-put overlay 'focus-location (plist-get loc-message :overlay ))
517508 overlay))
518509
510+ (defun lsp-sonarlint--extract-located-messages (locations )
511+ " Group messages from LOCATIONS by their coordinates."
512+ (let ((line-to-msg (make-hash-table :test #'equal )))
513+ (mapc (lambda (location )
514+ (let* ((precise-overlay (plist-get location :overlay ))
515+ (message-offset (lsp-sonarlint--secondary-msg-lens-offset
516+ (overlay-start precise-overlay)))
517+ (message (plist-get location :message ))
518+ (line (line-number-at-pos (overlay-start precise-overlay))))
519+ (push `(:message , message :offset , message-offset )
520+ (gethash line line-to-msg))))
521+ locations)
522+ line-to-msg))
523+
524+ (defun lsp-sonarlint--deduplicate (sorted-list )
525+ " Remove duplicate elements from sorted SORTED-LIST."
526+ (let ((result '())
527+ (last-element nil ))
528+ (dolist (element sorted-list (nreverse result))
529+ (unless (equal element last-element)
530+ (push element result)
531+ (setq last-element element)))))
532+
533+ (defun lsp-sonarlint--combine (messages-with-offsets )
534+ " Combine MESSAGES-WITH-OFFSETS that don't overlap into single line.
535+
536+ MESSAGES-WITH-OFFSETS must be sorted by offset."
537+ (let ((result '())
538+ (reversed (reverse messages-with-offsets)))
539+ (dolist (msg-off messages-with-offsets (nreverse result))
540+ (when-let* ((right-most (car reversed))
541+ (right-offset (plist-get right-most :offset ))
542+ (my-offset (plist-get msg-off :offset ))
543+ (my-message (plist-get msg-off :message ))
544+ (gap (- right-offset (+ my-offset (length my-message)))))
545+ (unless (< right-offset my-offset) ; this element is already combined
546+ (if (<= gap 3 ) ; ; too close or overlap
547+ (push msg-off result)
548+ (setf (plist-get msg-off :message )
549+ (concat my-message
550+ (make-string gap ?\s )
551+ (plist-get right-most :message )))
552+ (pop reversed)
553+ (push msg-off result)))))))
554+
555+ (defun lsp-sonarlint--process-offsets (messages-with-offsets )
556+ " Sort, deduplicate, adjust, and combine MESSAGES-WITH-OFFSETS.
557+
558+ Sort them in increasing order, remove duplicate messages with identical offsets,
559+ adjust offsets to account for the number labels prepended to each location."
560+ (let* ((sorted (sort messages-with-offsets (lambda (msg-off1 msg-off2 )
561+ (< (plist-get msg-off1 :offset )
562+ (plist-get msg-off2 :offset )))))
563+ (deduplicated (lsp-sonarlint--deduplicate sorted))
564+ (accumulated-adjustment 0 )
565+ (adjusted (mapcar (lambda (msg-with-offset )
566+ (setf (plist-get msg-with-offset :offset )
567+ (+ accumulated-adjustment
568+ (plist-get msg-with-offset :offset )))
569+ (setq accumulated-adjustment 1 )
570+ msg-with-offset)
571+ deduplicated)))
572+ (lsp-sonarlint--combine adjusted)))
573+
574+ (defun lsp-sonarlint--concat-msg-lines (msg-offsets )
575+ " Combine the list of MSG-OFFSETS into a single string."
576+ (string-join
577+ (mapcar (lambda (msg-offset )
578+ (concat (make-string (plist-get msg-offset :offset ) ?\s )
579+ (plist-get msg-offset :message )))
580+ msg-offsets)
581+ " \n " ))
582+
583+ (defun lsp-sonarlint--add-inline-messages (locations )
584+ " Add lens-style in-line messages for LOCATIONS."
585+ (maphash (lambda (line messages )
586+ (let* ((adjusted-messages
587+ (lsp-sonarlint--process-offsets messages))
588+ (overlay (lsp-sonarlint--make-full-line-overlay line))
589+ (prefix-count (/ (1+ (length adjusted-messages)) 2 ))
590+ (prefix-msgs (seq-take adjusted-messages prefix-count))
591+ (postfix-msgs (seq-drop adjusted-messages prefix-count)))
592+ (when prefix-msgs
593+ (overlay-put overlay 'before-string
594+ (propertize (concat (lsp-sonarlint--concat-msg-lines prefix-msgs)
595+ " \n " )
596+ 'face 'lsp-sonarlint-embedded-msg-face )))
597+ (when postfix-msgs
598+ (overlay-put overlay 'after-string
599+ (propertize (concat " \n "
600+ (lsp-sonarlint--concat-msg-lines postfix-msgs))
601+ 'face 'lsp-sonarlint-embedded-msg-face )))))
602+ (lsp-sonarlint--extract-located-messages locations)))
603+
604+ (defvar lsp-sonarlint--original-buffer nil
605+ " The buffer with code and SonarLint issues.
606+
607+ Useful when exploring secondary locations, which uses an auxiliary buffer." )
608+
519609(defun lsp-sonarlint--show-all-locations (command )
520610 " Show all secondary locations listed in COMMAND for the focused issue."
521611 (lsp-sonarlint--remove-secondary-loc-highlights)
522612 (let* ((arguments (seq-first (ht-get command " arguments" )))
523613 (flows (ht-get arguments " flows" )))
524614 (let ((locations (lsp-sonarlint--procure-overlays-for-secondary-locations flows))
525615 (primary (lsp-sonarlint--procure-overlay-for-primary-location arguments)))
616+ (setq lsp-sonarlint--original-buffer (current-buffer ))
526617 (switch-to-buffer-other-window lsp-sonarlint--secondary-messages-buffer-name)
527618 (fundamental-mode )
528619 (setq buffer-read-only nil )
@@ -539,6 +630,8 @@ pointing to the `:overlay' from LOC-MESSAGE."
539630 (insert " \n " )
540631 (let ((overlay (lsp-sonarlint--add-message-entry location)))
541632 (lsp-sonarlint--add-number-marker overlay (plist-get location :step-num ))))
633+ (with-current-buffer lsp-sonarlint--original-buffer
634+ (lsp-sonarlint--add-inline-messages (cons primary locations)))
542635 (goto-char (point-min ))
543636 (tabulated-list-mode )
544637 (setq-local cursor-type nil ))))
0 commit comments