Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## master (unreleased)

### New features

* Add other-tab variants (`helm-projectile-find-file-other-tab`, `helm-projectile-find-dir-other-tab`, `helm-projectile-switch-project-other-tab`), matching Helm 4.x's tab-bar support alongside the existing other-window and other-frame variants. "Open in other tab" is also available as an action (bound to `C-c t`) in the project and directory sources.

### Changes

* Require Emacs 28.1 and Projectile 3.1.0. Projectile 3.1.0 raised its own minimum to Emacs 28.1 (and pulls in `compat`), so helm-projectile can no longer be installed on Emacs 27. The CI matrix drops the 27.2 job accordingly.
Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,14 @@ so it doesn't fold in submodule files or drop deleted-but-unstaged ones.
The `helm-projectile-find-file-streaming` command streams regardless of
this setting.

Finding files, finding directories and switching projects each come in
`-other-window`, `-other-frame` and `-other-tab` variants
(e.g. `helm-projectile-find-file-other-tab`), matching Emacs' own
`C-x 4` / `C-x 5` / `C-x t` families. Inside a Helm session the same
targets are available as actions (and on `C-c o`, `C-c C-o` and `C-c t`
in the project and directory sources). The `-other-tab` commands need a
recent Helm and Emacs' `tab-bar-mode`.

If you want to use these commands, you have to activate it to replace
the normal Projectile commands:

Expand Down
100 changes: 98 additions & 2 deletions helm-projectile.el
Original file line number Diff line number Diff line change
Expand Up @@ -243,13 +243,20 @@ When `projectile-switch-project-action' is `projectile-find-file' a
(projectile-switch-project-action #'helm-projectile-find-file-other-frame))
(projectile-switch-project-by-name project)))

(defun helm-projectile-switch-project-by-name-other-tab (project)
"Switch to PROJECT and find file in it in other tab."
(let ((projectile-completion-system 'helm)
(projectile-switch-project-action #'helm-projectile-find-file-other-tab))
(projectile-switch-project-by-name project)))

(defvar helm-projectile-projects-map
(let ((map (make-sparse-keymap)))
(set-keymap-parent map helm-map)
(helm-projectile-define-key map
(kbd "C-d") #'dired
(kbd "C-c o") #'helm-projectile-switch-project-by-name-other-window
(kbd "C-c C-o") #'helm-projectile-switch-project-by-name-other-frame
(kbd "C-c t") #'helm-projectile-switch-project-by-name-other-tab
(kbd "M-g") #'helm-projectile-vc
(kbd "M-e") #'helm-projectile-switch-to-shell
(kbd "C-s") #'helm-projectile-grep
Expand All @@ -267,6 +274,7 @@ When `projectile-switch-project-action' is `projectile-find-file' a
"Switch to project" #'helm-projectile-switch-project-by-name
"Switch to project other window `C-c o'" #'helm-projectile-switch-project-by-name-other-window
"Switch to project other frame `C-c C-o'" #'helm-projectile-switch-project-by-name-other-frame
"Switch to project other tab `C-c t'" #'helm-projectile-switch-project-by-name-other-tab
"Open Dired in project's directory `C-d'" #'dired
"Open project root in vc-dir or magit `M-g'" #'helm-projectile-vc
"Switch to Eshell `M-e'" #'helm-projectile-switch-to-shell
Expand Down Expand Up @@ -335,6 +343,16 @@ to be specific to `helm-projectile-projects-source'."
helm-source-projectile-projects-actions
'(helm-projectile-switch-project-by-name-other-frame . :make-first))))

(defclass helm-projectile-projects-other-tab-source (helm-projectile-projects-source)
())

(cl-defmethod helm-setup-user-source :after ((source helm-projectile-projects-other-tab-source))
"Set `helm-projectile-switch-project-by-name-other-tab' as the first action."
(setf (slot-value source 'action)
(helm-projectile-hack-actions
helm-source-projectile-projects-actions
'(helm-projectile-switch-project-by-name-other-tab . :make-first))))

(define-key helm-etags-map (kbd "C-c p f")
(lambda ()
(interactive)
Expand Down Expand Up @@ -670,6 +688,20 @@ Meant to be added to `helm-cleanup-hook', from which it removes
(helm-make-source "Projectile files" 'helm-source-projectile-file-other-frame)
"Helm source definition for Projectile files.")

(defclass helm-source-projectile-file-other-tab (helm-source-projectile-file)
())

(cl-defmethod helm-setup-user-source ((source helm-source-projectile-file-other-tab))
"Make `helm-ff-find-file-other-tab' the first action in SOURCE."
(setf (slot-value source 'action)
(helm-projectile-hack-actions
helm-projectile-file-actions
'(helm-ff-find-file-other-tab . :make-first))))

(defvar helm-source-projectile-files-other-tab-list
(helm-make-source "Projectile files" 'helm-source-projectile-file-other-tab)
"Helm source definition for Projectile files.")

(defvar helm-source-projectile-files-in-all-projects-list
(helm-build-sync-source "Projectile files in all Projects"
:candidates (lambda ()
Expand Down Expand Up @@ -753,6 +785,21 @@ Meant to be added to `helm-cleanup-hook', from which it removes
'helm-source-projectile-dired-file-other-frame)
"Helm source definition for Projectile delete files.")

(defclass helm-source-projectile-dired-file-other-tab (helm-source-projectile-dired-file)
())

(cl-defmethod helm-setup-user-source ((source helm-source-projectile-dired-file-other-tab))
"Make `helm-ff-find-file-other-tab' the first action in SOURCE."
(setf (slot-value source 'action)
(helm-projectile-hack-actions
helm-projectile-dired-file-actions
'(helm-ff-find-file-other-tab . :make-first))))

(defvar helm-source-projectile-dired-files-other-tab-list
(helm-make-source "Projectile files in current Dired buffer"
'helm-source-projectile-dired-file-other-tab)
"Helm source definition for Projectile delete files.")

(defun helm-projectile-dired-find-dir (dir)
"Jump to a selected directory DIR from `helm-projectile'."
(dired (expand-file-name dir (projectile-project-root)))
Expand All @@ -768,10 +815,16 @@ Meant to be added to `helm-cleanup-hook', from which it removes
(dired-other-frame (expand-file-name dir (projectile-project-root)))
(run-hooks 'projectile-find-dir-hook))

(defun helm-projectile-dired-find-dir-other-tab (dir)
"Jump to a selected directory DIR from `helm-projectile' (in other tab)."
(dired-other-tab (expand-file-name dir (projectile-project-root)))
(run-hooks 'projectile-find-dir-hook))

(defvar helm-source-projectile-directory-actions
'(("Open Dired" . helm-projectile-dired-find-dir)
("Open Dired in other window `C-c o'" . helm-projectile-dired-find-dir-other-window)
("Open Dired in other frame `C-c C-o'" . helm-projectile-dired-find-dir-other-frame)
("Open Dired in other tab `C-c t'" . helm-projectile-dired-find-dir-other-tab)
("Switch to Eshell `M-e'" . helm-projectile-switch-to-shell)
("Grep in projects `C-s'" . helm-projectile-grep)
("Create Dired buffer from files `C-c f'" . helm-projectile-dired-files-new-action)
Expand All @@ -797,6 +850,7 @@ Meant to be added to `helm-cleanup-hook', from which it removes
(kbd "<right>") #'helm-next-source
(kbd "C-c o") #'helm-projectile-dired-find-dir-other-window
(kbd "C-c C-o") #'helm-projectile-dired-find-dir-other-frame
(kbd "C-c t") #'helm-projectile-dired-find-dir-other-tab
(kbd "M-e") #'helm-projectile-switch-to-shell
(kbd "C-c f") #'helm-projectile-dired-files-new-action
(kbd "C-c a") #'helm-projectile-dired-files-add-action
Expand Down Expand Up @@ -840,6 +894,20 @@ Meant to be added to `helm-cleanup-hook', from which it removes
(helm-make-source "Projectile directories" 'helm-source-projectile-directory-other-frame)
"Helm source for listing project directories.")

(defclass helm-source-projectile-directory-other-tab (helm-source-projectile-directory)
())

(cl-defmethod helm-setup-user-source ((source helm-source-projectile-directory-other-tab))
"Make `helm-projectile-dired-find-dir-other-tab' the first action in SOURCE."
(setf (slot-value source 'action)
(helm-projectile-hack-actions
helm-source-projectile-directory-actions
'(helm-projectile-dired-find-dir-other-tab . :make-first))))

(defvar helm-source-projectile-directories-other-tab-list
(helm-make-source "Projectile directories" 'helm-source-projectile-directory-other-tab)
"Helm source for listing project directories.")

(defvar helm-projectile-buffers-list-cache nil)

(defclass helm-source-projectile-buffer (helm-source-sync helm-type-buffer)
Expand Down Expand Up @@ -1015,6 +1083,13 @@ Defaults is `helm-projectile-truncate-lines'."
'helm-projectile-projects-other-frame-source)
"Switch to project: " t)

;;;###autoload(autoload 'helm-projectile-switch-project-other-tab "helm-projectile" nil t)
(helm-projectile-command "switch-project-other-tab"
(helm-make-source
"Projectile projects"
'helm-projectile-projects-other-tab-source)
"Switch to project: " t)

(defcustom helm-projectile-find-file-strategy 'sync
"How `helm-projectile-find-file' lists a project's files.

Expand All @@ -1030,8 +1105,8 @@ indexing command prints, so - like `helm-projectile-find-file-streaming'
\(which see) - on Git projects it doesn't fold in submodule files or drop
deleted-but-unstaged ones.

This governs `helm-projectile-find-file' and its other-window and
other-frame variants. The dwim commands and
This governs `helm-projectile-find-file' and its other-window,
other-frame and other-tab variants. The dwim commands and
`helm-projectile-find-file-in-known-projects' always build the full file
list, because they need it (to match the file at point, or to span every
known project)."
Expand Down Expand Up @@ -1072,6 +1147,14 @@ effect immediately."
'helm-source-projectile-files-other-frame-list)
"Find file (other frame): ")

;;;###autoload(autoload 'helm-projectile-find-file-other-tab "helm-projectile" nil t)
(helm-projectile-command "find-file-other-tab"
(helm-projectile--file-sources
'helm-source-projectile-files-streaming-other-tab
'helm-source-projectile-dired-files-other-tab-list
'helm-source-projectile-files-other-tab-list)
"Find file (other tab): ")

;;;###autoload(autoload 'helm-projectile-find-file-in-known-projects "helm-projectile" nil t)
(helm-projectile-command "find-file-in-known-projects" 'helm-source-projectile-files-in-all-projects-list "Find file in projects: " t)

Expand All @@ -1093,6 +1176,12 @@ effect immediately."
helm-source-projectile-directories-other-frame-list)
"Find dir (other frame): ")

;;;###autoload(autoload 'helm-projectile-find-dir-other-tab "helm-projectile" nil t)
(helm-projectile-command "find-dir-other-tab"
'(helm-source-projectile-dired-files-other-tab-list
helm-source-projectile-directories-other-tab-list)
"Find dir (other tab): ")

;;;###autoload(autoload 'helm-projectile-recentf "helm-projectile" nil t)
(helm-projectile-command "recentf" 'helm-source-projectile-recentf-list "Recently visited file: ")

Expand Down Expand Up @@ -1199,6 +1288,13 @@ or drop deleted-but-unstaged files.")
'(find-file-other-frame . :make-first)))
"Like `helm-source-projectile-files-streaming', but opening in another frame.")

(defvar helm-source-projectile-files-streaming-other-tab
(helm-projectile--make-streaming-source
(helm-projectile-hack-actions
helm-projectile-file-actions
'(helm-ff-find-file-other-tab . :make-first)))
"Like `helm-source-projectile-files-streaming', but opening in another tab.")

;;;###autoload(autoload 'helm-projectile-find-file-streaming "helm-projectile" nil t)
(helm-projectile-command "find-file-streaming"
'helm-source-projectile-files-streaming
Expand Down
40 changes: 40 additions & 0 deletions test/helm-projectile-test.el
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,46 @@
(when (get-buffer "hp-test-dired")
(kill-buffer "hp-test-dired")))))))

(describe "helm-projectile other-tab support"
;; Helm 4.x gained tab-bar actions; helm-projectile exposes them as
;; other-tab command/source variants alongside other-window/other-frame.
(it "defines the other-tab commands"
(expect (commandp 'helm-projectile-find-file-other-tab) :to-be-truthy)
(expect (commandp 'helm-projectile-find-dir-other-tab) :to-be-truthy)
(expect (commandp 'helm-projectile-switch-project-other-tab) :to-be-truthy))

(it "promotes the other-tab action as the default in each source"
(cl-flet ((first-action (src) (cdar (helm-get-attr 'action src))))
(expect (first-action helm-source-projectile-files-other-tab-list)
:to-be 'helm-ff-find-file-other-tab)
(expect (first-action helm-source-projectile-files-streaming-other-tab)
:to-be 'helm-ff-find-file-other-tab)
(expect (first-action helm-source-projectile-directories-other-tab-list)
:to-be 'helm-projectile-dired-find-dir-other-tab)))

(it "installs the other-tab finder as the project switch action"
(let (captured)
(cl-letf (((symbol-function 'projectile-switch-project-by-name)
(lambda (&rest _) (setq captured projectile-switch-project-action))))
(helm-projectile-switch-project-by-name-other-tab "/proj/"))
(expect captured :to-be 'helm-projectile-find-file-other-tab)))

(it "dispatches find-file-other-tab sync/streaming like the other variants"
(spy-on 'helm)
(spy-on 'projectile-project-p :and-return-value t)
(spy-on 'projectile-maybe-invalidate-cache)
(spy-on 'projectile-project-name :and-return-value "demo")
(spy-on 'projectile-prepend-project-name :and-call-fake #'identity)
(helm-projectile-find-file-other-tab)
(expect (helm-projectile-test--sources)
:to-equal '(helm-source-projectile-dired-files-other-tab-list
helm-source-projectile-files-other-tab-list))
(spy-calls-reset 'helm)
(let ((helm-projectile-find-file-strategy 'streaming))
(helm-projectile-find-file-other-tab))
(expect (helm-projectile-test--sources)
:to-be 'helm-source-projectile-files-streaming-other-tab)))

(describe "user-facing error conditions"
;; Normal situations (no project, no other file, ...) should signal
;; `user-error', not `error', so they don't trip the debugger when a user
Expand Down
Loading