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)))