{"id":13686649,"url":"https://github.com/mclear-tools/tabspaces","last_synced_at":"2025-05-01T09:32:29.163Z","repository":{"id":41882912,"uuid":"459300605","full_name":"mclear-tools/tabspaces","owner":"mclear-tools","description":null,"archived":false,"fork":false,"pushed_at":"2024-04-15T18:43:40.000Z","size":731,"stargazers_count":195,"open_issues_count":3,"forks_count":13,"subscribers_count":7,"default_branch":"main","last_synced_at":"2024-04-17T23:03:36.185Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Emacs Lisp","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mclear-tools.png","metadata":{"files":{"readme":"README.org","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-02-14T19:38:26.000Z","updated_at":"2024-05-30T01:27:29.759Z","dependencies_parsed_at":"2023-10-23T13:35:25.635Z","dependency_job_id":"3e75ceb0-6ed8-4cc1-b780-02086d319b47","html_url":"https://github.com/mclear-tools/tabspaces","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mclear-tools%2Ftabspaces","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mclear-tools%2Ftabspaces/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mclear-tools%2Ftabspaces/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mclear-tools%2Ftabspaces/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mclear-tools","download_url":"https://codeload.github.com/mclear-tools/tabspaces/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251852894,"owners_count":21654480,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-02T15:00:36.747Z","updated_at":"2025-05-01T09:32:29.155Z","avatar_url":"https://github.com/mclear-tools.png","language":"Emacs Lisp","funding_links":["https://www.buymeacoffee.com/fxpy8fzgyxg"],"categories":["Emacs Lisp"],"sub_categories":[],"readme":"#+title: Tabspaces\n#+author: Colin McLear\n#+language: en\n#+export_file_name: tabspaces.texi\n#+texinfo_filename: tabspaces.info\n#+texinfo_dir_category: Emacs\n#+texinfo_dir_title: Tabspaces: (tabspaces).\n#+texinfo_dir_desc: Tabbed workspaces using tab-bar and project.el \n\n#+html: \u003ca href=\"https://www.gnu.org/software/emacs/\"\u003e\u003cimg alt=\"GNU Emacs\" src=\"https://github.com/minad/corfu/blob/screenshots/emacs.svg?raw=true\"/\u003e\u003c/a\u003e\n#+html: \u003ca href=\"https://melpa.org/#/tabspaces\"\u003e\u003cimg alt=\"MELPA\" src=\"https://melpa.org/packages/tabspaces-badge.svg\"/\u003e\u003c/a\u003e\n#+html: \u003ca href=\"https://www.buymeacoffee.com/fxpy8fzgyxg\" target=\"_blank\"\u003e\u003cimg src=\"https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png\" alt=\"Buy Me A Coffee\" style=\"height: 23px !important;width: 120px !important;box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;-webkit-box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;\" \u003e\u003c/a\u003e\n\n\nTabspaces 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\ncreate buffer-isolated workspaces (or \"tabspaces\") that also integrate with your\nversion-controlled projects. It should work with emacs 27+. It is tested to work\nwith a single frame workflow, but should work with multiple frames as well. \n\nWhile other great packages exist for managing workspaces, such as [[https://github.com/alphapapa/bufler.el][bufler]],\n[[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\nentirely based on the built-in (to emacs 27+) tab-bar and project packages. If\nyou like simple, this may be the workspace package for you. That said, bufler,\nperspective or persp-mode, etc. may better fit your needs.\n\n*NOTE*: version 1.2 renames several functions and streamlines tab and project\ncreation. Apologies if this breaks your workflow. Please update your configuration accordingly. \n\n** Basic Usage\n\nCalling the minor-mode =tabspaces-mode= sets up newly created tabs as\nbuffer-isolated workspaces using =tab.el= in the background. Calling\n=tabspaces-mode= does not itself create a new tabbed workspace. \n\nSwitch or create workspace via =tabspaces-switch-or-create-workspace=. Close a\nworkspace by invoking =tabspaces-close-workspace=. Note that these two functions\nare simply wrappers around native =tab-bar= commands. You can close a workspace\nand /kill/ all buffers associated with it using\n=tabspaces-kill-buffers-close-workspace=.\n\nOpen an existing version-controlled project in its own workspace using\n=tabspaces-open-or-create-project-and-workspace=. If no such project exists it\nwill then create one in its own workspace for you.\n\nSee workspace buffers using =tabspaces-switch-buffer= (for =consult= integration see\nbelow), which will only show buffers in the workspace (but list-buffers,\nibuffer, etc. will show all buffers). Setting\n=tabspaces-use-filtered-buffers-as-default= to =t= remaps =switch-to-buffer= to\n=tabspaces-switch-to-buffer=.\n\nAdding buffers to a workspace is as simple as opening the buffer in\nthe workspace. Delete buffers from a workspace either by killing them or using\none of either =tabspaces-remove-selected-buffer= or\n=tabspaces-remove-current-buffer=. Removed buffers are still available from the\ndefault tabspace unless the variable =tabspaces-remove-to-default= is set to =nil=.\n\n*NOTE* that other than tabbed buffer isolation for all created window tabs this\npackage does not modify =tab-bar=, =tab-line=, or =project= in any way. It simply adds\nconvenience functions for use with those packages. So it is still up to the user\nto configure tabs, etc., however they like.\n\nHere 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:\n\n#+ATTR_HTML: :width 85%\n[[file:screenshots/tab-notes.png]]\n#+ATTR_HTML: :width 85%\n[[file:screenshots/tab-emacsd.png]]\n\n** Installation\n\nYou may install this package either from Melpa (=M-x package-install tabspaces\nRET=) or by cloning this repo and adding it to your load-path. \n\n** Setup\n\nHere's one possible way of setting up the package using [[https://github.com/jwiegley/use-package][use-package]] (and\n[[https://github.com/raxod502/straight.el][straight]], if you use that).\n\n#+begin_src emacs-lisp\n(use-package tabspaces\n  ;; use this next line only if you also use straight, otherwise ignore it. \n  :straight (:type git :host github :repo \"mclear-tools/tabspaces\")\n  :hook (after-init . tabspaces-mode) ;; use this only if you want the minor-mode loaded at startup. \n  :commands (tabspaces-switch-or-create-workspace\n             tabspaces-open-or-create-project-and-workspace)\n  :custom\n  (tabspaces-use-filtered-buffers-as-default t)\n  (tabspaces-default-tab \"Default\")\n  (tabspaces-remove-to-default t)\n  (tabspaces-include-buffers '(\"*scratch*\"))\n  (tabspaces-initialize-project-with-todo t)\n  (tabspaces-todo-file-name \"project-todo.org\")\n  ;; sessions\n  (tabspaces-session t)\n  (tabspaces-session-auto-restore t)\n  (tab-bar-new-tab-choice \"*scratch*\"))\n#+end_src\n\nNote 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. \n\n*** Keybindings \nWorkspace Keybindings are defined in the following variable:\n\n#+begin_src emacs-lisp\n(defvar tabspaces-command-map\n  (let ((map (make-sparse-keymap)))\n    (define-key map (kbd \"C\") 'tabspaces-clear-buffers)\n    (define-key map (kbd \"b\") 'tabspaces-switch-to-buffer)\n    (define-key map (kbd \"d\") 'tabspaces-close-workspace)\n    (define-key map (kbd \"k\") 'tabspaces-kill-buffers-close-workspace)\n    (define-key map (kbd \"o\") 'tabspaces-open-or-create-project-and-workspace)\n    (define-key map (kbd \"r\") 'tabspaces-remove-current-buffer)\n    (define-key map (kbd \"R\") 'tabspaces-remove-selected-buffer)\n    (define-key map (kbd \"s\") 'tabspaces-switch-or-create-workspace)\n    (define-key map (kbd \"t\") 'tabspaces-switch-buffer-and-tab)\n    map)\n  \"Keymap for tabspace/workspace commands after `tabspaces-keymap-prefix'.\")\n#+end_src\n\nThe variable =tabspaces-keymap-prefix= sets a key prefix (default is =C-c TAB=) for\nthe keymap, but this can be changed to anything the user prefers.\n\n*** Buffer Filtering\n\nWhen =tabspaces-mode= is enabled use =tabspaces-switch-to-buffer= to choose from a\nfiltered list of only those buffers in the current tab/workspace. Though =nil= by\ndefault, when =tabspaces-use-filtered-buffers-as-default= is set to =t= and\n=tabspaces-mode= is enabled, =switch-to-buffer= is globally remapped to\n=tabspaces-switch-to-buffer=, and thus only shows those buffers in the current\nworkspace. For use with =consult-buffer=, see below.\n\n*** Switch Tabs via Buffer\n\nSometimes 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.\n\n*** Tabs \u0026 Projects\n\nThe =tabspaces-open-or-create-project-and-workspace= function provides a\nversatile way to manage projects and their associated workspaces in\nEmacs. Here's what you can do with it:\n\n1. *Open Existing Projects*: Open an existing version-controlled project\n   in its own workspace. The function will switch to the project's tab\n   if it already exists.\n\n2. *Create New Projects*: If no such project exists at the specified\n   path, it will create one in its own workspace for you, initializing\n   version control (git or other VCS) in the process.\n\n3. *Descriptive Tab Naming*:\n\n    - Tabs are named descriptively based on the project structure.\n    - In case of naming conflicts, it intelligently renames tabs to avoid\n      confusion.\n     \n4. *Multiple Tabs for the Same Project*:\n\n    - By using a universal argument (C-u) before calling the function,\n      you can force the creation of a new tab even for already open project tabs.\n    - The first tab will have the original project name.\n    - Subsequent tabs will be automatically named with incrementing\n      numbers (e.g., \"ProjectName\u003c2\u003e\", \"ProjectName\u003c3\u003e\").     \n    - This is useful when you want to work on different aspects of the\n      same project in separate workspaces.\n\n\n*** Persistent Tabspaces\n\nTabspaces provides basic functionality to save and restore both global (all\ntabspaces) and project-specific tabspace sessions. These sessions store:\n\n- Open file-visiting buffers in each tab\n- Window configurations (splits, sizes, buffer positions)\n\n**** Configuration\n\nBy default, project sessions are stored in their respective project root\ndirectories as hidden files (e.g. =.project-name-tabspaces-session.el=).\nYou can configure where project sessions are stored using\n=tabspaces-session-project-session-store=:\n\n#+begin_src elisp\n;; Store in project directories (default)\n(setq tabspaces-session-project-session-store 'project)\n\n;; Store all project sessions in a specific directory\n(setq tabspaces-session-project-session-store \"~/.emacs.d/tabspaces-sessions/\")\n\n;; Use a custom function to determine location\n(setq tabspaces-session-project-session-store\n      (lambda (project-root)\n        (expand-file-name \n         (concat \"sessions/\" (file-name-nondirectory project-root) \"-tabspaces-session.el\")\n         project-root)))\n#+end_src\n\nThe /global/ session file location is controlled by\n=tabspaces-session-file= (defaults to =~/.emacs.d/tabsession.el=).\n\n**** Usage\n:PROPERTIES:\n:CUSTOM_ID: usage\n:END:\n***** Global Sessions\n:PROPERTIES:\n:CUSTOM_ID: global-sessions\n:END:\nSave all tabs and their configurations:\n\n#+begin_src elisp\nM-x tabspaces-save-session\n#+end_src\n\nRestore saved global session:\n\n#+begin_src elisp\nM-x tabspaces-restore-session\n#+end_src\n\n***** Project Sessions\n:PROPERTIES:\n:CUSTOM_ID: project-sessions\n:END:\nSave current project tab and its configuration:\n\n#+begin_src elisp\nM-x tabspaces-save-current-project-session\n#+end_src\n\nRestore a project session:\n\n#+begin_src elisp\nM-x tabspaces-restore-session\n;; Then select the project directory when prompted\n#+end_src\n\n***** Automatic Session Handling\n:PROPERTIES:\n:CUSTOM_ID: automatic-session-handling\n:END:\nEnable automatic session saving on Emacs exit:\n\n#+begin_src elisp\n(setq tabspaces-session t)  ; Save sessions automatically\n#+end_src\n\nEnable automatic session restoration on startup:\n\n#+begin_src elisp\n(setq tabspaces-session-auto-restore t)  ; Restore last session on startup\n#+end_src\n\nProject sessions can also be automatically restored when switching\nprojects using the provided project commands.\n\n\nRudimentary 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)=.\n\n*** Additional Customization\n\n**** Consult\n\nIf you have [[https://github.com/minad/consult][consult]] installed you might want to implement the following in your\nconfig to have workspace buffers in =consult-buffer=:\n\n#+begin_src emacs-lisp\n  ;; Filter Buffers for Consult-Buffer\n\n  (with-eval-after-load 'consult\n  ;; hide full buffer list (still available with \"b\" prefix)\n  (consult-customize consult--source-buffer :hidden t :default nil)\n  ;; set consult-workspace buffer list\n  (defvar consult--source-workspace\n    (list :name     \"Workspace Buffers\"\n          :narrow   ?w\n          :history  'buffer-name-history\n          :category 'buffer\n          :state    #'consult--buffer-state\n          :default  t\n          :items    (lambda () (consult--buffer-query\n                           :predicate #'tabspaces--local-buffer-p\n                           :sort 'visibility\n                           :as #'buffer-name)))\n\n    \"Set workspace buffer list for consult-buffer.\")\n  (add-to-list 'consult-buffer-sources 'consult--source-workspace))\n#+end_src\n\nThis should seamlessly integrate workspace buffers into =consult-buffer=,\ndisplaying workspace buffers by default and all buffers when narrowing using\n\"b\". Note that you can also see all project related buffers and files just by\nnarrowing with \"p\" in [[https://github.com/minad/consult#configuration][a default consult setup]].\n\n*NOTE*: If you typically toggle between having =tabspaces-mode= active and inactive,\nyou may want to also include a hook function to turn off the\n=consult--source-workspace= above and modify the visibility of\n=consult--source-buffer=. You can do that with something like the following:\n\n#+begin_src emacs-lisp\n  (defun my--consult-tabspaces ()\n    \"Deactivate isolated buffers when not using tabspaces.\"\n    (require 'consult)\n    (cond (tabspaces-mode\n           ;; hide full buffer list (still available with \"b\")\n           (consult-customize consult--source-buffer :hidden t :default nil)\n           (add-to-list 'consult-buffer-sources 'consult--source-workspace))\n          (t\n           ;; reset consult-buffer to show all buffers \n           (consult-customize consult--source-buffer :hidden nil :default t)\n           (setq consult-buffer-sources (remove #'consult--source-workspace consult-buffer-sources)))))\n\n  (add-hook 'tabspaces-mode-hook #'my--consult-tabspaces)           \n#+end_src\n\n**** Ivy\n\nIf you use ivy you can use this function to limit your buffer search to only\nthose in the tabspace.\n\n#+begin_src emacs-lisp\n(defun tabspaces-ivy-switch-buffer (buffer)\n  \"Display the local buffer BUFFER in the selected window.\nThis is the frame/tab-local equivilant to `switch-to-buffer'.\"\n  (interactive\n   (list\n    (let ((blst (mapcar #'buffer-name (tabspaces-buffer-list))))\n      (read-buffer\n       \"Switch to local buffer: \" blst nil\n       (lambda (b) (member (if (stringp b) b (car b)) blst))))))\n  (ivy-switch-buffer buffer))\n#+end_src\n\nAlternatively, 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.\n\n#+begin_src emacs-lisp\n(defun tabspaces-ivy-switch-buffer ()\n  \"Switch to another buffer in the current tabspace.\"\n  (interactive)\n  (ivy-read \"Switch to buffer: \" #'internal-complete-buffer\n            :predicate (when (tabspaces--current-tab-name)\n                         (let ((local-buffers (tabspaces--buffer-list)))\n                           (lambda (name-and-buffer)\n                             (member (cdr name-and-buffer) local-buffers))))\n            :keymap ivy-switch-buffer-map\n            :preselect (buffer-name (other-buffer (current-buffer)))\n            :action #'ivy--switch-buffer-action\n            :matcher #'ivy--switch-buffer-matcher\n            :caller 'ivy-switch-buffer))\n#+end_src\n\n**** Included Buffers\n\nBy default the =*scratch*= buffer is included in all workspaces. You can modify\nwhich buffers are included by default by changing the value of\n=tabspaces-include-buffers=.\n\nIf you want emacs to startup with a set of initial buffers in a workspace\n(something I find works well) you could do something like the following:\n\n#+begin_src emacs-lisp\n  (defun my--tabspace-setup ()\n    \"Set up tabspace at startup.\"\n    ;; Add *Messages* and *splash* to Tab \\`Home\\'\n    (tabspaces-mode 1)\n    (progn\n      (tab-bar-rename-tab \"Home\")\n      (when (get-buffer \"*Messages*\")\n        (set-frame-parameter nil\n                             'buffer-list\n                             (cons (get-buffer \"*Messages*\")\n                                   (frame-parameter nil 'buffer-list))))\n      (when (get-buffer \"*splash*\")\n        (set-frame-parameter nil\n                             'buffer-list\n                             (cons (get-buffer \"*splash*\")\n                                   (frame-parameter nil 'buffer-list))))))\n\n  (add-hook 'after-init-hook #'my--tabspace-setup)\n#+end_src\n\n**** File Per Project\n\nBy default Tabspaces will create a =project-todo.org= file at the root of the project\nwhen creating a new workspace using =tabspaces-open-or-create-project-and-workspace=.\n\nUse =tabspaces-todo-file-name= to change the name of that file, or =tabspaces-initialize-project-with-todo=\nto disable this feature completely.\n\n\n** Acknowledgments\nCode for this package is derived from, or inspired by, a variety of sources.\nThese include:\n\n- The original buffer filter function\n   + https://www.rousette.org.uk/archives/using-the-tab-bar-in-emacs/\n   + https://github.com/wamei/elscreen-separate-buffer-list/issues/8\n   + https://github.com/kaz-yos/emacs\n- Buffer filtering and removal\n   + https://github.com/florommel/bufferlo\n- Consult integration\n   + https://github.com/minad/consult#multiple-sources\n     \n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmclear-tools%2Ftabspaces","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmclear-tools%2Ftabspaces","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmclear-tools%2Ftabspaces/lists"}