aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--modules/org-config.el32
-rw-r--r--tests/test-org-config--org-follow-link-same-window.el72
2 files changed, 103 insertions, 1 deletions
diff --git a/modules/org-config.el b/modules/org-config.el
index c0c6bb82..90a0a5d2 100644
--- a/modules/org-config.el
+++ b/modules/org-config.el
@@ -286,6 +286,28 @@ edge, less the tag width.")
(org-backward-heading-same-level 1)
(org-narrow-to-subtree))
+ (defun cj/--org-follow-link-same-window ()
+ "Follow the Org link at point, opening file links in the current window.
+Org's default for file links is `find-file-other-window' (via
+`org-link-frame-setup'); this overrides it so the file replaces the buffer
+the link sits in. Off a link this does nothing, so a stray click is a silent
+no-op rather than a \"No link found\" error."
+ (when (eq (org-element-type (org-element-context)) 'link)
+ (let ((org-link-frame-setup (cons '(file . find-file) org-link-frame-setup)))
+ (org-open-at-point))))
+
+ (defun cj/org-follow-link-at-mouse-same-window (event)
+ "Follow the Org link clicked in EVENT, opening file links in the same window.
+Bound to S-mouse-1 and mouse-3 in `org-mouse-map' -- the keymap org attaches
+to each link as a `keymap' text property. That layer outranks both
+`org-mode-map' and the `mouse-trap-mode' emulation keymap, so the gesture
+lands even where mouse-trap otherwise disables clicks. A shift-click or
+right-click on a link opens it in place; org's other-window default
+(mouse-2 / plain click) is left alone."
+ (interactive "e")
+ (mouse-set-point event)
+ (cj/--org-follow-link-same-window))
+
:hook
(org-mode . turn-on-visual-line-mode)
(org-mode . (lambda () (setq-local tab-width 8)))
@@ -300,7 +322,15 @@ edge, less the tag width.")
(cj/org-general-settings)
(cj/org-appearance-settings)
- (cj/org-todo-settings))
+ (cj/org-todo-settings)
+
+ ;; Open a file link in the current window on shift-left-click or right-click.
+ ;; These bind into `org-mouse-map' (the per-link `keymap' text property)
+ ;; rather than `org-mode-map' so they outrank the `mouse-trap-mode' emulation
+ ;; keymap, which otherwise swallows clicks in org buffers. mouse-2 / plain
+ ;; click keep org's other-window default.
+ (keymap-set org-mouse-map "S-<mouse-1>" #'cj/org-follow-link-at-mouse-same-window)
+ (keymap-set org-mouse-map "<mouse-3>" #'cj/org-follow-link-at-mouse-same-window))
;; ------------------------------- Org Superstar -------------------------------
diff --git a/tests/test-org-config--org-follow-link-same-window.el b/tests/test-org-config--org-follow-link-same-window.el
new file mode 100644
index 00000000..57caf691
--- /dev/null
+++ b/tests/test-org-config--org-follow-link-same-window.el
@@ -0,0 +1,72 @@
+;;; test-org-config--org-follow-link-same-window.el --- same-window link follow -*- lexical-binding: t; -*-
+
+;;; Commentary:
+;; `cj/--org-follow-link-same-window' follows the Org link at point, opening
+;; file: links in the *current* window instead of org's default other-window
+;; (`org-link-frame-setup' file entry). It is the kernel behind the S-mouse-1
+;; and mouse-3 bindings (`cj/org-follow-link-at-mouse-same-window'). Off a
+;; link it does nothing, so a right-click in empty space is a silent no-op
+;; rather than org's "No link found" user-error.
+;;
+;; `org-open-at-point' (the visit boundary) is stubbed to record whether it
+;; was called and what the file entry of `org-link-frame-setup' resolved to at
+;; call time; real temp Org buffers place point on / off a link.
+
+;;; Code:
+
+(require 'ert)
+(require 'cl-lib)
+(require 'org)
+
+(add-to-list 'load-path (expand-file-name "modules" user-emacs-directory))
+(require 'org-config)
+
+(ert-deftest test-org-config-follow-link-same-window-file-link ()
+ "Normal: on a file link, follow it with the file frame-setup as find-file."
+ (with-temp-buffer
+ (let ((org-mode-hook nil)
+ (frame-file nil)
+ (called nil))
+ (insert "see [[file:/tmp/notes.org][notes]] here")
+ (org-mode)
+ (goto-char (point-min))
+ (search-forward "notes]")
+ (backward-char 2) ; land inside the link's description
+ (cl-letf (((symbol-function 'org-open-at-point)
+ (lambda (&rest _)
+ (setq called t
+ frame-file (cdr (assq 'file org-link-frame-setup))))))
+ (cj/--org-follow-link-same-window))
+ (should called)
+ (should (eq frame-file 'find-file)))))
+
+(ert-deftest test-org-config-follow-link-same-window-off-link-noop ()
+ "Boundary: off any link, do nothing (no error, no visit)."
+ (with-temp-buffer
+ (let ((org-mode-hook nil)
+ (called nil))
+ (insert "plain text, no link at all")
+ (org-mode)
+ (goto-char (point-min))
+ (cl-letf (((symbol-function 'org-open-at-point)
+ (lambda (&rest _) (setq called t))))
+ (cj/--org-follow-link-same-window))
+ (should-not called))))
+
+(ert-deftest test-org-config-follow-link-same-window-http-link ()
+ "Boundary: on a non-file link, still follow it (frame-setup is harmless)."
+ (with-temp-buffer
+ (let ((org-mode-hook nil)
+ (called nil))
+ (insert "site [[https://example.com][example]] end")
+ (org-mode)
+ (goto-char (point-min))
+ (search-forward "example]")
+ (backward-char 2)
+ (cl-letf (((symbol-function 'org-open-at-point)
+ (lambda (&rest _) (setq called t))))
+ (cj/--org-follow-link-same-window))
+ (should called))))
+
+(provide 'test-org-config--org-follow-link-same-window)
+;;; test-org-config--org-follow-link-same-window.el ends here