{"id":26151932,"url":"https://github.com/abougouffa/one-tab-per-project","last_synced_at":"2025-03-11T06:55:12.198Z","repository":{"id":247493053,"uuid":"825996827","full_name":"abougouffa/one-tab-per-project","owner":"abougouffa","description":"Automatically create a tab per project, providing a light tab-bar based workspace management for Emacs","archived":false,"fork":false,"pushed_at":"2024-12-12T23:14:55.000Z","size":237,"stargazers_count":34,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"main","last_synced_at":"2024-12-13T00:20:14.972Z","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/abougouffa.png","metadata":{"files":{"readme":"README.md","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":"2024-07-08T23:06:09.000Z","updated_at":"2024-12-12T23:14:58.000Z","dependencies_parsed_at":"2024-07-09T03:40:15.946Z","dependency_job_id":"47a08741-d066-446e-a20a-acdd2ecb60ce","html_url":"https://github.com/abougouffa/one-tab-per-project","commit_stats":null,"previous_names":["abougouffa/one-tab-per-project"],"tags_count":16,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/abougouffa%2Fone-tab-per-project","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/abougouffa%2Fone-tab-per-project/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/abougouffa%2Fone-tab-per-project/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/abougouffa%2Fone-tab-per-project/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/abougouffa","download_url":"https://codeload.github.com/abougouffa/one-tab-per-project/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":242988047,"owners_count":20217538,"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":"2025-03-11T06:55:11.500Z","updated_at":"2025-03-11T06:55:12.191Z","avatar_url":"https://github.com/abougouffa.png","language":"Emacs Lisp","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003ca href=\"https://github.com/abougouffa/one-tab-per-project\"\u003e\u003cimg src=\"https://www.gnu.org/software/emacs/images/emacs.png\" alt=\"Emacs Logo\" width=\"80\" height=\"80\" align=\"right\"\u003e\u003c/a\u003e\n## otpp.el\n*One tab per project, with unique names*\n\n---\n[![MELPA](http://melpa.org/packages/otpp-badge.svg)](http://melpa.org/#/otpp)\n[![MELPA Stable](http://stable.melpa.org/packages/otpp-badge.svg)](http://stable.melpa.org/#/otpp)\n\nThis is a lightweight workspace management package that provides a thin layer\nbetween builtin packages `project` and `tab-bar`. The whole idea consists of\ncreating a _tab per opened project_ while ensuring unique names for the\ncreated tabs (when multiple opened projects have the same name).\n\nThis package is inspired by `project-tab-groups` which creates a \"tab group\"\nper project.\n\n### Installation\n\n\nThis package is available on MELPA.\n\n```emacs-lisp\n(use-package otpp\n  :straight t\n  :after project\n  :init\n  ;; If you like to define some aliases for better user experience\n  (defalias 'one-tab-per-project-mode 'otpp-mode)\n  (defalias 'one-tab-per-project-override-mode 'otpp-override-mode)\n  ;; Enable `otpp-mode` globally\n  (otpp-mode 1)\n  ;; If you want to advice the commands in `otpp-override-commands`\n  ;; to be run in the current's tab (so, current project's) root directory\n  (otpp-override-mode 1))\n```\n\n### Basic usage\n\n\nThe usage is quite straightforward, there is no extra commands to learn to be\nable to use it. When `otpp-mode` global minor mode is enabled, you will have\nthis:\n\n- When you switch to a project `project-switch-project` (bound by default to\n  `C-x p p`), `otpp` will create a tab with the project name.\n\n- When you kill a project with all its buffers with `project-kill-buffers`, the\n  tab is closed.\n\n- Lets say you've switched to the project under\n  `/home/user/project1/backend/`, `otpp` will create a tab named `backend`\n  for this particular project. Now, you opened a second project under\n  `/home/user/project2/backend/`, `otpp` will detect that the name of the\n  project `backend` is the same as the previously opened one, but it have a\n  different path. In this case, `otpp` will create a tab named\n  `backend[project2]` and renames the previously opened tab to\n  `backend[project1]`. This conflict resolution is provided by the\n  `otpp-uniq-*` routines.\n\n- For some cases, you might need to attach a manually created tab (by\n  `tab-bar-new-tab`) to an opened project so you have two tabs dedicated to\n  the same project (with different windows layouts for example). In this\n  case, you can call the command `otpp-change-tab-root-dir` and select the\n  path of the project to attach to.\n\n- When you use some commands to jump to a file (`find-file`,\n  `xref-find-definitions`, etc.), you can end up with a buffer belonging to a\n  _different project (lets say `B`)_ but displayed in the current project's\n  tab _(`A`)_. In this case, you can call `otpp-detach-buffer-to-tab` to\n  create a new tab dedicated to the buffer's project `B`. When the opened\n  buffer is project-less (not part of a project), the command will signal a\n  user error unless `otpp-allow-detach-projectless-buffer` is non-nil, in\n  this case, `otpp` creates a new project-less tab for the buffer.\n\n### Advanced usage\n\n\nConsider this use case: supposing you are using `otpp-mode` and you've run\n`project-switch-project` to open the `X` project in a new `X` tab. Now you\n`M-x find-file` then you open the `test.cpp` file outside the current `X`\nproject. Now, if you run `project-find-file`, you will be in one of these two\nsituations:\n\n1. If `test.cpp` is part of another project `Y`, the `project-find-file` will\n   prompt you with a list of `Y`s files even though we are in the `X` tab.\n\n2. If `test.cpp` isn't part of any project, `project-find-file` will prompt\nyou to select a project first, then to select a file.\n\nFor this, `otpp` provides `otpp-prefix` (we recommend to bind it to some key,\nlike `C-x t P`, using `otpp-prefix` from `M-x` can have some limitations).\nWhen you run `otpp-prefix` followed by `C-x p f` for example, you will be\nprompted for files in the current's tab project files even if you are\nvisiting a file outside of the current project.\n\nIn my workflow, I would like to always restrict the commands like\n`project-find-file` and `project-kill-buffers` to the project bound to the\ncurrent tab, even if I'm visiting a file which is not part of this project.\nIf you like this behavior, you can enable the `otpp-override-mode`. This mode\nwill advice all the commands defined in `otpp-override-commands` to be ran in\nthe current's tab root directory (_a.k.a._, in the project bound to the\ncurrent tab).\n\nWhen `otpp-override-mode` is enabled, the `otpp-prefix` acts inversely. While\nall `otpp-override-commands` are restricted to the current's tab project by\ndefault, running a command with `otpp-prefix` will disable this behavior,\nwhich results of the next command to be run in the `default-directory`\ndepending on the visited buffer.\n\n### Similar packages\n\n\nThis section is not exhaustive, it includes only the packages that I used\nbefore.\n\n- [`project-tab-groups`](https://github.com/fritzgrabo/project-tab-groups):\n  This package provides a mode that enhances the Emacs built-in `project` to\n  support keeping projects isolated in named tab groups. `otpp` is inspired\n  by this package, but instead of setting the tab groups, `otpp` introduces a\n  new attribute in the tab named `otpp-root-dir` where it stores the root\n  directory of the project bound to the tab. This allows keeping the tabs\n  updated in case another project with the same name (but a different path)\n  is opened.\n\n- [`tabspaces`](https://github.com/mclear-tools/tabspaces): This package\n  provide workspace management with `tab-bar` and with an integration with\n  `project`. Contrary to `otpp` and `project-tab-groups`, `tabspaces` don't\n  create tabs automatically, you need to call specific commands like\n  `tabspaces-open-or-create-project-and-workspace`. Also, `tabspaces`\n  behavior isn't predictable when you open several projects with the same\n  directory name.\n\n\n\n### Customization Documentation\n\n#### `otpp-preserve-non-otpp-tabs`\n\nWhen non-nil, preserve the current rootless tab when switching projects.\n\n#### `otpp-bury-on-kill-buffer-when-multiple-tabs`\n\nBury the current buffer when killed but it is opened in another tab.\n\nWhen non-nil, this modifies the behavior of `kill-buffer` when killing\nthe current buffer. If the current buffer is opened in another tab, we\nbury it instead of killing it. This only affects the current buffer,\nwhen we explicitly select another buffer to kill, `otpp` assumes that we\nhave a good reason to kill it.\n\n#### `otpp-reconnect-tab`\n\nWhether to reconnect a disconnected tab when switching to it.\n\nWhen set to a function's symbol, that function will be called\nwith the switched-to project's root directory as its single\nargument.\n\nWhen non-nil, show the project dispatch menu instead.\n\n#### `otpp-strictly-obey-dir-locals`\n\nWhether to strictly obey local variables.\n\nSet a nil (default value) to only respect the local variables when they\nare defined in the project's root (the `dir-locals-file` is located in\nthe project's root).\n\nSet to a function that takes DIR, PROJECT-ROOT and DIR-LOCALS-ROOT as\narguments in this order, see the function `otpp-project-name`. The\nfunction should return non-nil to take the local variables into account.\n\nThis can be useful when the project include sub-projects (a Git\nrepository with sub-modules, a Git repository with other Git repos\ninside, a Repo workspace, etc).\n\n#### `otpp-post-change-tab-root-functions`\n\nList of functions to call after changing the `otpp-root-dir` of a tab.\nThis hook is run at the end of the function `otpp-change-tab-root-dir`.\nThe current tab is supplied as an argument.\n\n#### `otpp-project-name-function`\n\nDerive project name from a directory.\n\nThis function receives a directory and return the project name\nfor the project that includes this path.\n\n#### `otpp-allow-detach-projectless-buffer`\n\nAllow detaching a buffer to a new tab even if it is not part of a project.\nThis can also be set to a function that receives the buffer, and return\nnon-nil if we should allow the tab creation.\n\n#### `otpp-override-commands`\n\nA list of commands to be advised in `otpp-override-mode`.\nThese commands will be run with `default-directory` set the to current's\ntab directory.\n\n#### `otpp-default-tab-name`\n\nThe default tab name to use when the last otpp tab is killed.\n\n### Function and Macro Documentation\n\n#### `(otpp-get-tab-root-dir \u0026optional TAB)`\n\nGet the root directory set to the TAB, default to the current tab.\n\n#### `(otpp-project-name DIR)`\n\nGet the project name from DIR.\nThis function extracts the project root. Then, it tries to find a\n`dir-locals-file` file that can be applied to files inside the directory\nDIR. When found, the local variables are read if any of these conditions\nis correct:\n- `otpp-strictly-obey-dir-locals` is set to a function, and calling it\n  returns non-nil (we pass to this function the DIR, the project root\n  and the directory containing the `dir-locals-file`).\n- `otpp-strictly-obey-dir-locals` is a *not* a function and it is\n  non-nil.\n- The `dir-locals-file` file is stored in the project root, a.k.a.,\n  the project root is the same as the `dir-locals-file` directory.\nThen, this function checks in this order:\n1. If the local variable `otpp-project-name` is set locally in the\n`dir-locals-file`, use it as project name.\n2. Same with the local variable `project-vc-name`.\n3. Return the directory name of the project's root.\nWhen DIR isn't part of any project, returns nil.\n\n#### `(otpp-detach-buffer-to-tab BUFFER)`\n\nCreate or switch to the tab corresponding to the project of BUFFER.\nWhen called with the a prefix, it asks for the buffer.\n\n#### `(otpp-change-tab-root-dir DIR \u0026optional TAB-NUMBER)`\n\nChange the `otpp-root-dir` attribute to DIR.\nIf if the absolute TAB-NUMBER is provided, set it, otherwise, set the\ncurrent tab.\nWhen DIR is empty or nil, delete it from the tab.\n\n#### `(otpp-find-tabs-by-root-dir DIR)`\n\nReturn a list of tabs that have DIR as `otpp-root-dir` attribute.\n\n#### `(otpp-select-or-create-tab-root-dir DIR)`\n\nSelect or create the tab with root directory DIR.\nReturns non-nil if a new tab was created, and nil otherwise.\n\n#### `(otpp-prefix)`\n\nRun the next command in the tab's root directory (or not!).\nThe actual behavior depends on `otpp-override-mode`. For\ninstance, when you execute M-x otpp-prefix followed by\nC-x p f, if the `otpp-override-mode` is\nenabled, this will run the `project-find-file` command in the\n`default-directory`, otherwise, it will bind the `default-directory` to\nthe current's tab directory before executing `project-find-file`.\n\n-----\n\u003cdiv style=\"padding-top:15px;color: #d0d0d0;\"\u003e\nMarkdown README file generated by\n\u003ca href=\"https://github.com/mgalgs/make-readme-markdown\"\u003emake-readme-markdown.el\u003c/a\u003e\n\u003c/div\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fabougouffa%2Fone-tab-per-project","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fabougouffa%2Fone-tab-per-project","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fabougouffa%2Fone-tab-per-project/lists"}