Function: ghostel--detect-urls

ghostel--detect-urls is a natively compiled function defined in ghostel-links.el.

Signature

(ghostel--detect-urls &optional BEGIN END)

Documentation

Scan a buffer region for plain-text URLs and file:line references.

BEGIN and END default to point-min and point-max respectively. Skips regions that already have a help-echo property (e.g. from OSC 8) and the user's active input on the current prompt line. Bounding the scan keeps streaming output from re-scanning the entire materialized scrollback on every redraw. Binds inhibit-read-only and suppresses modification hooks so the scan can attach text properties when called from the deferred-detection timer outside the redraw scope.

Returns the (BEGIN . END) actually covered, which the caller-supplied bounds widened to whole logical lines.

Source Code

;; Defined in /nix/store/1252krff0gb2kisjcpp45m50cpdskfrl-emacs-packages-deps/share/emacs/site-lisp/ghostel-links.el
(defun ghostel--detect-urls (&optional begin end)
  "Scan a buffer region for plain-text URLs and file:line references.
BEGIN and END default to `point-min' and `point-max' respectively.
Skips regions that already have a `help-echo' property (e.g. from OSC 8)
and the user's active input on the current prompt line.
Bounding the scan keeps streaming output from re-scanning the entire
materialized scrollback on every redraw.
Binds `inhibit-read-only' and suppresses modification hooks so the scan
can attach text properties when called from the deferred-detection timer
outside the redraw scope.

Returns the (BEGIN . END) actually covered, which the caller-supplied
bounds widened to whole logical lines."
  (let* (;; Whole logical lines: a value the terminal split across rows is
         ;; only recognisable once the rows are joined, and starting mid-line
         ;; would let the `^' anchor below match where there is no line start.
         (begin (ghostel--soft-wrap-line-beginning
                 (or begin (point-min)) ghostel--soft-wrap-row-limit))
         (end (ghostel--soft-wrap-line-end
               (or end (point-max)) ghostel--soft-wrap-row-limit))
         (inhibit-read-only t)
         (inhibit-modification-hooks t)
         ;; `ghostel--cursor-char-pos' is the live terminal cursor after a redraw;
         ;; its line is the prompt the user is currently editing.  Capture as
         ;; buffer-position bounds so the per-match skip check is O(1).
         (active-bounds
          (unless ghostel--inhibit-active-line-skip
            (let ((active-pos (or ghostel--cursor-char-pos (point))))
              (cons (ghostel--soft-wrap-line-beginning
                     active-pos ghostel--soft-wrap-row-limit)
                    (ghostel--soft-wrap-line-end
                     active-pos ghostel--soft-wrap-row-limit)))))
         (joined (ghostel--wrap-joined-region
                  begin end ghostel--soft-wrap-row-limit))
         (text (car joined))
         (chunks (cdr joined))
         ;; Disable file detection over TRAMP
         (files (and ghostel-enable-file-detection
                     (not (file-remote-p default-directory))))
         ;; Every range a pattern matched, whether or not it was linkified.
         ;; What no pattern covers any more is a leftover to clear.
         (matched nil)
         (url-ranges nil))
    ;; Pass 1: http(s) URLs
    (when ghostel-enable-url-detection
      (let ((offset 0))
        (while (string-match ghostel--url-regex text offset)
          (setq offset (match-end 0))
          (let* ((beg (ghostel--wrap-offset-to-pos (match-beginning 0) chunks))
                 (mend (ghostel--wrap-offset-to-pos (match-end 0) chunks))
                 (url (match-string-no-properties 0 text))
                 (fragments (ghostel--wrap-fragments beg mend))
                 (range (cons beg mend)))
            (push range url-ranges)
            (push range matched)
            (unless (ghostel--skip-match-p fragments url active-bounds)
              (ghostel--linkify fragments url url))))))
    ;; Pass 2: file:line[:col] references (e.g. "./foo.el:42",
    ;; "/tmp/bar.rs:10", or bare relative paths like "src/main.rs:42:4"
    ;; from compiler output).  The full regex is assembled from fixed anchor
    ;; + user-tunable path + fixed `:LINE[:COL]' tail so group 1 (path) and
    ;; group 2 (line[:col]) are always present — no nil-guarding needed in
    ;; the hot loop.  A small hash memoizes `file-exists-p' so repeated paths
    ;; in a redraw (common in multi-line compiler diagnostics) don't re-stat.
    (when files
      (let ((full-regex (concat ghostel--file-detection-leading-anchor
                                "\\(" ghostel-file-detection-path-regex "\\)"
                                "\\(" ghostel--file-detection-tail "\\)"))
            (seen (make-hash-table :test 'equal))
            (offset 0))
        (while (string-match full-regex text offset)
          (setq offset (match-end 2))
          (let* ((beg (ghostel--wrap-offset-to-pos (match-beginning 1) chunks))
                 (mend (ghostel--wrap-offset-to-pos (match-end 2) chunks))
                 (path (match-string-no-properties 1 text))
                 (loc (match-string-no-properties 2 text))
                 (raw (concat path loc))
                 (fragments (ghostel--wrap-fragments beg mend)))
            ;; A URL owns its whole span: its `//host/path' half also looks
            ;; like a path, and re-marking it would replace the link with a
            ;; local file — and stat that file on every scan.
            (unless (or (ghostel--range-overlaps-p beg mend url-ranges)
                        ;; With the URL pass switched off nothing recorded a
                        ;; range, so fall back to the link already there.
                        ;; When it did run its ranges are the whole truth,
                        ;; and a leftover URL link is about to be cleared.
                        (and (not ghostel-enable-url-detection)
                             (ghostel--url-link-p beg)))
              (if (ghostel--skip-match-p fragments raw active-bounds)
                  ;; Whatever is there stays; it must not count as stranded.
                  (push (cons beg mend) matched)
                (let* ((abs-path (expand-file-name path))
                       (cached (gethash abs-path seen 'unset))
                       (exists (if (eq cached 'unset)
                                   (puthash abs-path (file-exists-p abs-path) seen)
                                 cached)))
                  ;; A candidate that names no file leaves the range
                  ;; uncovered, so a link left over from the text it used
                  ;; to hold is cleared.
                  (when exists
                    (push (cons beg mend) matched)
                    (ghostel--linkify
                     fragments
                     (if (> (length loc) 0)
                         (concat "fileref:" abs-path ":" (substring loc 1))
                       (concat "fileref:" abs-path))
                     raw)))))))))
    (ghostel--drop-stranded-links
     begin end matched ghostel-enable-url-detection files)
    (cons begin end)))