Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/mclear-tools/tabspaces


https://github.com/mclear-tools/tabspaces

Last synced: 4 days ago
JSON representation

Awesome Lists containing this project

README

        

#+title: Tabspaces
#+author: Colin McLear
#+language: en
#+export_file_name: tabspaces.texi
#+texinfo_filename: tabspaces.info
#+texinfo_dir_category: Emacs
#+texinfo_dir_title: Tabspaces: (tabspaces).
#+texinfo_dir_desc: Tabbed workspaces using tab-bar and project.el

#+html: GNU Emacs
#+html: MELPA
#+html: Buy Me A Coffee

Tabspaces leverages [[https://github.com/emacs-mirror/emacs/blob/master/lisp/tab-bar.el][tab-bar.el]] and [[https://github.com/emacs-mirror/emacs/blob/master/lisp/progmodes/project.el][project.el]] (both built into emacs 27+) to
create buffer-isolated workspaces (or "tabspaces") that also integrate with your
version-controlled projects. It should work with emacs 27+. It is tested to work
with a single frame workflow, but should work with multiple frames as well.

While other great packages exist for managing workspaces, such as [[https://github.com/alphapapa/bufler.el][bufler]],
[[https://github.com/nex3/perspective-el][perspective]] and [[https://github.com/Bad-ptr/persp-mode.el][persp-mode]], this package is less complex than those alternatives, and works
entirely based on the built-in (to emacs 27+) tab-bar and project packages. If
you like simple, this may be the workspace package for you. That said, bufler,
perspective or persp-mode, etc. may better fit your needs.

*NOTE*: version 1.2 renames several functions and streamlines tab and project
creation. Apologies if this breaks your workflow. Please update your configuration accordingly.

** Basic Usage

Calling the minor-mode =tabspaces-mode= sets up newly created tabs as
buffer-isolated workspaces using =tab.el= in the background. Calling
=tabspaces-mode= does not itself create a new tabbed workspace.

Switch or create workspace via =tabspaces-switch-or-create-workspace=. Close a
workspace by invoking =tabspaces-close-workspace=. Note that these two functions
are simply wrappers around native =tab-bar= commands. You can close a workspace
and /kill/ all buffers associated with it using
=tabspaces-kill-buffers-close-workspace=.

Open an existing version-controlled project in its own workspace using
=tabspaces-open-or-create-project-and-workspace=. If no such project exists it
will then create one in its own workspace for you.

See workspace buffers using =tabspaces-switch-buffer= (for =consult= integration see
below), which will only show buffers in the workspace (but list-buffers,
ibuffer, etc. will show all buffers). Setting
=tabspaces-use-filtered-buffers-as-default= to =t= remaps =switch-to-buffer= to
=tabspaces-switch-to-buffer=.

Adding buffers to a workspace is as simple as opening the buffer in
the workspace. Delete buffers from a workspace either by killing them or using
one of either =tabspaces-remove-selected-buffer= or
=tabspaces-remove-current-buffer=. Removed buffers are still available from the
default tabspace unless the variable =tabspaces-remove-to-default= is set to =nil=.

*NOTE* that other than tabbed buffer isolation for all created window tabs this
package does not modify =tab-bar=, =tab-line=, or =project= in any way. It simply adds
convenience functions for use with those packages. So it is still up to the user
to configure tabs, etc., however they like.

Here are some screenshots of tabspaces (with my [[https://github.com/Lambda-Emacs/lambda-themes][lambda-themes]]) and using =consult-buffer= (see below for instructions on that setup). You can see the workspace isolated buffers in each and the tabs at top:

#+ATTR_HTML: :width 85%
[[file:screenshots/tab-notes.png]]
#+ATTR_HTML: :width 85%
[[file:screenshots/tab-emacsd.png]]

** Installation

You may install this package either from Melpa (=M-x package-install tabspaces
RET=) or by cloning this repo and adding it to your load-path.

** Setup

Here's one possible way of setting up the package using [[https://github.com/jwiegley/use-package][use-package]] (and
[[https://github.com/raxod502/straight.el][straight]], if you use that).

#+begin_src emacs-lisp
(use-package tabspaces
;; use this next line only if you also use straight, otherwise ignore it.
:straight (:type git :host github :repo "mclear-tools/tabspaces")
:hook (after-init . tabspaces-mode) ;; use this only if you want the minor-mode loaded at startup.
:commands (tabspaces-switch-or-create-workspace
tabspaces-open-or-create-project-and-workspace)
:custom
(tabspaces-use-filtered-buffers-as-default t)
(tabspaces-default-tab "Default")
(tabspaces-remove-to-default t)
(tabspaces-include-buffers '("*scratch*"))
(tabspaces-initialize-project-with-todo t)
(tabspaces-todo-file-name "project-todo.org")
;; sessions
(tabspaces-session t)
(tabspaces-session-auto-restore t)
(tab-bar-new-tab-choice "*scratch*"))
#+end_src

Note the inclusion of the `tab-bar` setting, which is built-in to Emacs and allows a number of different options for what buffer to set for a newly created tab.

*** Keybindings
Workspace Keybindings are defined in the following variable:

#+begin_src emacs-lisp
(defvar tabspaces-command-map
(let ((map (make-sparse-keymap)))
(define-key map (kbd "C") 'tabspaces-clear-buffers)
(define-key map (kbd "b") 'tabspaces-switch-to-buffer)
(define-key map (kbd "d") 'tabspaces-close-workspace)
(define-key map (kbd "k") 'tabspaces-kill-buffers-close-workspace)
(define-key map (kbd "o") 'tabspaces-open-or-create-project-and-workspace)
(define-key map (kbd "r") 'tabspaces-remove-current-buffer)
(define-key map (kbd "R") 'tabspaces-remove-selected-buffer)
(define-key map (kbd "s") 'tabspaces-switch-or-create-workspace)
(define-key map (kbd "t") 'tabspaces-switch-buffer-and-tab)
map)
"Keymap for tabspace/workspace commands after `tabspaces-keymap-prefix'.")
#+end_src

The variable =tabspaces-keymap-prefix= sets a key prefix (default is =C-c TAB=) for
the keymap, but this can be changed to anything the user prefers.

*** Buffer Filtering

When =tabspaces-mode= is enabled use =tabspaces-switch-to-buffer= to choose from a
filtered list of only those buffers in the current tab/workspace. Though =nil= by
default, when =tabspaces-use-filtered-buffers-as-default= is set to =t= and
=tabspaces-mode= is enabled, =switch-to-buffer= is globally remapped to
=tabspaces-switch-to-buffer=, and thus only shows those buffers in the current
workspace. For use with =consult-buffer=, see below.

*** Switch Tabs via Buffer

Sometimes the user may wish to switch to some open buffer in a tabspace and switch to that tab as well. Use =(=tabspaces-switch-buffer-and-tab=) to achieve this. If the buffer is open in more than one tabspace the user will be prompted to choose which tab to switch to. If there is no such buffer user will be prompted on whether to create it in a new tabspace or the current one.

*** Tabs & Projects

The =tabspaces-open-or-create-project-and-workspace= function provides a
versatile way to manage projects and their associated workspaces in
Emacs. Here's what you can do with it:

1. *Open Existing Projects*: Open an existing version-controlled project
in its own workspace. The function will switch to the project's tab
if it already exists.

2. *Create New Projects*: If no such project exists at the specified
path, it will create one in its own workspace for you, initializing
version control (git or other VCS) in the process.

3. *Descriptive Tab Naming*:

- Tabs are named descriptively based on the project structure.
- In case of naming conflicts, it intelligently renames tabs to avoid
confusion.

4. *Multiple Tabs for the Same Project*:

- By using a universal argument (C-u) before calling the function,
you can force the creation of a new tab even for already open project tabs.
- The first tab will have the original project name.
- Subsequent tabs will be automatically named with incrementing
numbers (e.g., "ProjectName<2>", "ProjectName<3>").
- This is useful when you want to work on different aspects of the
same project in separate workspaces.

*** Persistent Tabspaces

Rudimentary support for saving tabspaces across sessions has been implemented.
Setting =tabspaces-session= to =t= ensures that all open tabspaces and file-visiting
buffers are saved. These may either be restored interactively via
=(tabspaces-restore-session)=, non-interactively via
=(tabspaces--restore-session-on-startup)=, or they can be automatically opened
when =(tabspaces-mode)= is activated if =tabspaces-session-auto-restore= is set to
=t=. In addition, a particular project tabspace may be saved via
=(tabspaces-save-current-project-session)=, and restored when the project is opened via =(tabspaces-open-or-create-project-and-workspace)=.

*** Additional Customization

**** Consult

If you have [[https://github.com/minad/consult][consult]] installed you might want to implement the following in your
config to have workspace buffers in =consult-buffer=:

#+begin_src emacs-lisp
;; Filter Buffers for Consult-Buffer

(with-eval-after-load 'consult
;; hide full buffer list (still available with "b" prefix)
(consult-customize consult--source-buffer :hidden t :default nil)
;; set consult-workspace buffer list
(defvar consult--source-workspace
(list :name "Workspace Buffers"
:narrow ?w
:history 'buffer-name-history
:category 'buffer
:state #'consult--buffer-state
:default t
:items (lambda () (consult--buffer-query
:predicate #'tabspaces--local-buffer-p
:sort 'visibility
:as #'buffer-name)))

"Set workspace buffer list for consult-buffer.")
(add-to-list 'consult-buffer-sources 'consult--source-workspace))
#+end_src

This should seamlessly integrate workspace buffers into =consult-buffer=,
displaying workspace buffers by default and all buffers when narrowing using
"b". Note that you can also see all project related buffers and files just by
narrowing with "p" in [[https://github.com/minad/consult#configuration][a default consult setup]].

*NOTE*: If you typically toggle between having =tabspaces-mode= active and inactive,
you may want to also include a hook function to turn off the
=consult--source-workspace= above and modify the visibility of
=consult--source-buffer=. You can do that with something like the following:

#+begin_src emacs-lisp
(defun my--consult-tabspaces ()
"Deactivate isolated buffers when not using tabspaces."
(require 'consult)
(cond (tabspaces-mode
;; hide full buffer list (still available with "b")
(consult-customize consult--source-buffer :hidden t :default nil)
(add-to-list 'consult-buffer-sources 'consult--source-workspace))
(t
;; reset consult-buffer to show all buffers
(consult-customize consult--source-buffer :hidden nil :default t)
(setq consult-buffer-sources (remove #'consult--source-workspace consult-buffer-sources)))))

(add-hook 'tabspaces-mode-hook #'my--consult-tabspaces)
#+end_src

**** Ivy

If you use ivy you can use this function to limit your buffer search to only
those in the tabspace.

#+begin_src emacs-lisp
(defun tabspaces-ivy-switch-buffer (buffer)
"Display the local buffer BUFFER in the selected window.
This is the frame/tab-local equivilant to `switch-to-buffer'."
(interactive
(list
(let ((blst (mapcar #'buffer-name (tabspaces-buffer-list))))
(read-buffer
"Switch to local buffer: " blst nil
(lambda (b) (member (if (stringp b) b (car b)) blst))))))
(ivy-switch-buffer buffer))
#+end_src

Alternatively, you may use the following function, which is basically a clone of =ivy-switch-buffer= (and thus uses ivy's own implementation framework), but with an additional predicate that only allows showing buffers from the current tabspace.

#+begin_src emacs-lisp
(defun tabspaces-ivy-switch-buffer ()
"Switch to another buffer in the current tabspace."
(interactive)
(ivy-read "Switch to buffer: " #'internal-complete-buffer
:predicate (when (tabspaces--current-tab-name)
(let ((local-buffers (tabspaces--buffer-list)))
(lambda (name-and-buffer)
(member (cdr name-and-buffer) local-buffers))))
:keymap ivy-switch-buffer-map
:preselect (buffer-name (other-buffer (current-buffer)))
:action #'ivy--switch-buffer-action
:matcher #'ivy--switch-buffer-matcher
:caller 'ivy-switch-buffer))
#+end_src

**** Included Buffers

By default the =*scratch*= buffer is included in all workspaces. You can modify
which buffers are included by default by changing the value of
=tabspaces-include-buffers=.

If you want emacs to startup with a set of initial buffers in a workspace
(something I find works well) you could do something like the following:

#+begin_src emacs-lisp
(defun my--tabspace-setup ()
"Set up tabspace at startup."
;; Add *Messages* and *splash* to Tab \`Home\'
(tabspaces-mode 1)
(progn
(tab-bar-rename-tab "Home")
(when (get-buffer "*Messages*")
(set-frame-parameter nil
'buffer-list
(cons (get-buffer "*Messages*")
(frame-parameter nil 'buffer-list))))
(when (get-buffer "*splash*")
(set-frame-parameter nil
'buffer-list
(cons (get-buffer "*splash*")
(frame-parameter nil 'buffer-list))))))

(add-hook 'after-init-hook #'my--tabspace-setup)
#+end_src

**** =TODO= file per project

By default Tabspaces will create a =project-todo.org= file at the root of the project
when creating a new workspace using =tabspaces-open-or-create-project-and-workspace=.

Use =tabspaces-todo-file-name= to change the name of that file, or =tabspaces-initialize-project-with-todo=
to disable this feature completely.

** Acknowledgments
Code for this package is derived from, or inspired by, a variety of sources.
These include:

- The original buffer filter function
+ https://www.rousette.org.uk/archives/using-the-tab-bar-in-emacs/
+ https://github.com/wamei/elscreen-separate-buffer-list/issues/8
+ https://github.com/kaz-yos/emacs
- Buffer filtering and removal
+ https://github.com/florommel/bufferlo
- Consult integration
+ https://github.com/minad/consult#multiple-sources