{"id":16987856,"url":"https://github.com/timfjord/projectionist","last_synced_at":"2025-04-05T17:45:17.238Z","repository":{"id":164065537,"uuid":"618352353","full_name":"timfjord/Projectionist","owner":"timfjord","description":"Granular project configuration using \"projections\"","archived":false,"fork":false,"pushed_at":"2023-12-21T11:14:04.000Z","size":11142,"stargazers_count":2,"open_issues_count":2,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-11T14:57:45.810Z","etag":null,"topics":["project","sublime-text"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/timfjord.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":"2023-03-24T09:33:22.000Z","updated_at":"2023-11-01T13:59:50.000Z","dependencies_parsed_at":"2023-12-21T13:08:48.605Z","dependency_job_id":null,"html_url":"https://github.com/timfjord/Projectionist","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/timfjord%2FProjectionist","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/timfjord%2FProjectionist/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/timfjord%2FProjectionist/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/timfjord%2FProjectionist/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/timfjord","download_url":"https://codeload.github.com/timfjord/Projectionist/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247378092,"owners_count":20929293,"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":["project","sublime-text"],"created_at":"2024-10-14T02:50:56.212Z","updated_at":"2025-04-05T17:45:17.213Z","avatar_url":"https://github.com/timfjord.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003c!-- markdownlint-disable --\u003e\n[![Stand With Ukraine](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner2-direct.svg)](https://stand-with-ukraine.pp.ua)\n\n# Projectionist [![Lint](https://github.com/timfjord/Projectionist/actions/workflows/lint.yml/badge.svg)](https://github.com/timfjord/Projectionist/actions/workflows/lint.yml) [![Test](https://github.com/timfjord/Projectionist/actions/workflows/test.yml/badge.svg)](https://github.com/timfjord/Projectionist/actions/workflows/test.yml)\n\u003c!-- markdownlint-enable --\u003e\nGo to an alternate file and more.\n\n## Features\n\n- jump between test and implementation files\n- open/jump to an alternate file from the Side Bar\n- `.projections.json` file support (including JSON schema validation)\n- built-in projections for Elixir, Ruby, and Sublime package development\n- public API for other packages to use\n- work on all platforms\n\n## Installation\n\n1. Install the [Sublime Text Package Control](https://packagecontrol.io/) package if you don't have it already.\n2. Open the command palette and start typing `Package Control: Install Package`.\n3. Enter `Projectionist`.\n\n## Usage\n\nThe package implements the logic that was originally introduced by [Tim Pope](https://github.com/tpope) in his [vim-projectionist](https://github.com/tpope/vim-projectionist) plugin.\nAnd the main idea is to:\n\n\u003e provide granular project configuration using \"projections\".\n\nThe package exposes the following commands to work with projections:\n\n- `projectionist_open_alternate` - open an alternate file for the current file.  \nIn Sublime Text 4 this command supports `\"mode\": \"side_by_side\"`, to open an alternate file in a side-by-side view.\nThis command is also available in the Command Palette (`Projectionist: Open alternate`) as well as in the Side Bar.\nIn the Side Bar there is also an option to jump(reveal in Side Bar) an alternate file rather than open it.\n\nCheck [Default.sublime-commands](https://github.com/timfjord/Projectionist/blob/main/Default.sublime-commands) to see the list of all available commands.\n\n### What are projections?\n\nProjections are maps from filenames and globs to sets of properties describing the file.\nThe simplest way to define them is to create a `.projections.json` in the root of the project,\nbut there are other ways to define projection too (see [Sublime Text integration section](#sublime-text-integration)).  \nThe package ships with a JSON schema for the `.projections.json` file to provide validation and auto-completion (requires [LSP-json](https://github.com/sublimelsp/LSP-json) package).\n\nHere's a simple example for a Maven project:\n\n```json\n{\n  \"src/main/java/*.java\": {\n    \"alternate\": \"src/test/java/{}.java\",\n  },\n  \"src/test/java/*.java\": {\n    \"alternate\": \"src/main/java/{}.java\",\n  }\n}\n```\n\nIn property values, `{}` will be replaced by the portion of the glob matched by the `*`.\nYou can also chain one or more transformations inside the braces separated by bars, e.g. `{dot|hyphenate}`.\nThe complete list of available transformations is as follows:\n\n|         Name | Behavior                                                       |\n|-------------:|:---------------------------------------------------------------|\n|        `dot` | `/` to `.`                                                     |\n| `underscore` | `/` to `_`                                                     |\n|  `backslash` | `/` to `\\`                                                     |\n|     `colons` | `/` to `::`                                                    |\n|  `hyphenate` | `_` to `-`                                                     |\n|      `blank` | `_` and `-` to space                                           |\n|  `uppercase` | uppercase                                                      |\n|  `camelcase` | `foo_bar/baz_quux` to `fooBar/bazQuux`                         |\n|  `snakecase` | `FooBar/bazQuux` to `foo_bar/baz_quux`                         |\n| `capitalize` | capitalize first letter and each letter after a slash          |\n|    `dirname` | remove last slash separated component                          |\n|   `basename` | remove all but last slash separated component                  |\n|   `singular` | singularize                                                    |\n|     `plural` | pluralize                                                      |\n|       `file` | absolute path to file                                          |\n|    `project` | absolute path to project                                       |\n|       `open` | literal `{`                                                    |\n|      `close` | literal `}`                                                    |\n|    `nothing` | empty string                                                   |\n|        `vim` | no-op (include to specify other implementations should ignore) |\n\nFrom a globbing perspective, `*` is actually a stand in for `**/*`.\nFor advanced cases, you can include both globs explicitly: `test/**/test_*.rb`.\nWhen expanding with `{}`, the `**` and `*` portions are joined with a slash.\nIf necessary, the dirname and basename expansions can be used to split the value back apart.\n\nAs of now, the package supports only some of the properties from the original implementation,\nbut support to more properties will be added in the future.\nHere are the properties that are currently supported:\n\n- `alternate`  \nDetermines the destination of the `projectionist_open_alternate` command.  \nIf this is a list, the first readable file will be used.  Will also be used as a default for `related`.\n\n- `template`  \nArray of lines to use when creating a new file.\n\nThese are the properties that are not supported yet:\n\n- `console`  \nCommand to run to start a REPL or other interactive shell.  \nThis is useful to set from a `*` projection or on a simple file glob like `*.js`.  \nWill also be used as a default for `start`.  \nExpansions are shell escaped.\n\n- `dispatch`  \nExpansions are shell escaped.\n\n- `make`  \nSets `makeprg`.  \nThis is useful to set from a `*` projection.  \nExpansions are shell escaped.\n\n- `path`  \nAdditional directories to prepend to `path`.  \nCan be relative to the project root or absolute.  \nThis is useful to set on a simple file glob like `*.js`.\n\n- `related`  \nIndicates one or more files to search when a navigation command is called without an argument, to find a default destination.  \nRelated files are searched recursively.\n\n- `start`  \nCommand to run to \"boot\" the project.  \nExamples include `lein run`, `rails server`, and `foreman start`.  \nThis is useful to set from a `*` projection.  \nExpansions are shell escaped.\n\n- `type`  \nDeclares the type of file and creates a set of navigation commands for opening files that match the glob.\n\n### Sublime Text integration\n\nThe package supports projections from the following sources:\n\n- `local` - projections defined in the project config file (e.g. `MyProject.sublime-project`)\n- `file` - projections defined in the `.projections.json` file\n- `global` - heuristic projections defined in the global settings\n- `builtin` - built-in heuristic projections that ship with the package\n\nThe lookup order is determined by the `lookup_order` setting and it is:\n\n```jsonc\n{\n  \"lookup_order\": [\n    \"local\", // first look in the project settings\n    \"file\", // then, in the .projections.json file\n    \"global\", // then, the `heuristic_projections` from the package settings\n    \"builtin\" // and finally, he built-in heuristic projections\n  ]\n}\n```\n\nTo avoid evaluation projections from some of the sources, remove them from the `lookup_order` setting:\n\n```jsonc\n{\n  \"lookup_order\": [\"local\", \"file\"] // evaluate only local and .projections.json projections\n}\n```\n\nThe order of the items in the `lookup_order` setting is important, so switching items in the array will change the lookup order:\n\n```jsonc\n{\n  \"lookup_order\": [\n    \"file\", // first look in the .projections.json file\n    \"local\", // then, in the project settings\n    \"builtin\", // then, the built-in heuristic projections\n    \"global\" // and finally, the `heuristic_projections` from the package settings\n  ]\n}\n```\n\nGiven the number of projection sources, the package provides the `projectionist_output_projections` command\n(or `Projectionist: Output projections` in the Command Palette)\nto output all the projections to the Sublime Text console.\n\nThe `lookup_order` also determines how projections with the same pattern are handled.\nSo, for example, if the same pattern is defined in multiple sources,\nthe properties will be merged obeying the order defined in the `lookup_order` setting.  \nSo, for example, if there is a local projection:\n\n```json\n{\n  \"lib/*.ex\": {\n    \"alternate\": \"test/{}_test.exs\",\n    \"prop2\": \"value2\"\n  }\n}\n```\n\nand a built-in projection:\n\n```json\n{\n  \"lib/*.ex\": {\n    \"alternate\": \"spec/{}_spec.exs\",\n    \"prop1\": \"value1\"\n  }\n}\n```\n\nthe resulting projection will be:\n\n```json\n{\n  \"lib/*.ex\": {\n    \"alternate\": \"test/{}_test.exs\",\n    \"prop1\": \"value1\",\n    \"prop2\": \"value2\"\n  }\n}\n```\n\nAnd to avoid overriding projection defined on deeper levels, the `append`/`prepend` prefix can be used, for example:\n\n```json\n{\n  \"lib/*.ex\": {\n    \"prepend_alternate\": \"spec/{}_spec.exs\",\n    \"prop1\": \"value1\"\n  }\n}\n```\n\nwill result in:\n\n```json\n{\n  \"lib/*.ex\": {\n    \"alternate\": [\n      \"spec/{}_spec.exs\",\n      \"test/{}_test.exs\"\n    ],\n    \"prop1\": \"value1\",\n    \"prop2\": \"value2\"\n  }\n}\n```\n\nThis can be very useful to, say, fine-tune projections defined in the `.projections.json` file\nsince this file can be used by other editors (e.g. VIM, VSCode).\n\nAs of now, only the `alternate` property supports these prefixes.\n\n#### Local projections\n\nLocal projections can be defined in the project settings:\n\n```json\n{\n  \"folders\": [\n    {\n      \"path\": \".\",\n    }\n  ],\n  \"settings\": {\n    \"Projectionist\": {\n      \"projections\": {\n        \"plugin/*.py\": {\n          \"alternate\": \"tests/{dirname}/test_{basename}.py\",\n        },\n        \"tests/**/test_*.py\": {\n          \"alternate\": \"plugin/{}.py\",\n        }\n      }\n    }\n  }\n}\n```\n\n#### Heuristic projections\n\nHeuristic projections can be defined through the variable `heuristic_projections` in the global setting and they behave as a dictionary mapping between a string describing the root of the project and a set of projections.\nThe keys of the dictionary are files and directories that can be found in the root of a project, with `\u0026` separating multiple requirements and `|` separating multiple alternatives.  \nYou can also prefix a file or directory with `!` to forbid rather than require its presence.\n\nIn the example below, the first key requires a file named `mix.exs` and a file named `test/test_helper.exs`.\n\n```json\n{\n  \"heuristic_projections\": {\n    \"mix.exs\u0026test/test_helper.exs\": {\n      \"lib/*.ex\": {\n        \"alternate\": \"test/{}_test.exs\",\n        \"template\": [\n          \"defmodule {camelcase|capitalize|dot} do\",\n          \"end\"\n        ]\n      },\n      \"test/*_test.exs\": {\n        \"alternate\": \"lib/{}.ex\",\n        \"template\": [\n          \"defmodule {camelcase|capitalize|dot}Test do\",\n          \"  use ExUnit.Case\",\n          \"\",\n          \"  alias {camelcase|capitalize|dot}\",\n          \"end\",\n        ]\n      }\n    }\n  }\n}\n```\n\n#### Built-in projections\n\nThe package comes with the following list of built-in projections and they are enabled by default:\n\n- [`elixir`](https://github.com/timfjord/Projectionist/blob/main/plugin/builtin_projections/elixir.py)\n- [`ruby`](https://github.com/timfjord/Projectionist/blob/main/plugin/builtin_projections/ruby.py)\n- [`sublime`](https://github.com/timfjord/Projectionist/blob/main/plugin/builtin_projections/sublime.py)\n\nTo disable some of the built-in projections, remove them from the `builtin_heuristic_projections` setting:\n\n```jsonc\n{\n  \"builtin_heuristic_projections\": [\"elixir\", \"ruby\"] // disable the sublime projection\n}\n```\n\n#### Caching\n\nFor performance reasons, heuristic projections (both global and built-in) are determined once per project and then cached.\nThe same goes for the `.projections.json` file, it is parsed and cached per project.\nSo it is important to clear the cache after changing heuristic projections or updating the `.projections.json` file.  \nThe cache can be cleared with the `projectionist_clear_cache` command or via the `Projectionist: Clear Cache` command from the Command Palette.\n\n#### Project folders and subprojects\n\nThe package supports multiple project folders. It can be very useful when there is a nested folder\nthat contains a separate project. The package can detect this situation and use this information\nfor heuristic projections calculation and detecting the `.projections.json` file.\n\nAnother way to handle nested projects is to use the `subprojects` settings (usually in the project config)\n\n```json\n{\n  \"folders\": [\n    {\n      \"path\": \".\",\n    }\n  ],\n  \"settings\": {\n    \"Projectionist\": {\n      \"subprojects\": [\n        \"subfolder1/subfolder1_1\",\n        [\"subfolder2\", \"subfolder2_1\"]\n      ]\n    }\n  }\n}\n```\n\nA subproject can be either a string or an array of strings(the path separator will be added automatically in this case).\n\n## Public API\n\nTo allow other packages to find alternate files and more, the package exposes the `projectionist` module that acts as the public API.  \nCheck [`plugin/api.py`](https://github.com/timfjord/Projectionist/blob/main/plugin/api.py) for implementation details and [`tests/test_api.py`](https://github.com/timfjord/Projectionist/blob/main/tests/test_api.py) for test cases.\n\n### `find_alternate_file`\n\nAllows to find an alternate file for a given file and root directory.  \nReturns a tuple of `(exists, alternate)` where `exists` is a boolean indicating whether the alternate file exists and `alternate` is the path to the alternate file or `None` if no alternate file is defined.\n\n```python\ntry:\n    from projectionist import find_alternate_file\n\n    root = \"~/code/project\"\n    file = \"~/code/project/folder1/file1.py\"\n    exists, alternate = find_alternate_file(root, file)\n\n    if alternate is None:\n        print(\"No alternate file defined\")\n    elif exists:\n        print(\"Alternate file exists\")\n    else:\n        print(\"Alternate file defined but does not exist\")\nexcept ImportError:\n    print(\"Projectionist is not installed\")\n```\n\n## Roadmap\n\n- support more original projectionist features, like `type`, `console`, `dispatch`\n\n## Credits\n\n`Projectionist` is a Sublime Text implementation of the [vim-projectionist](https://github.com/tpope/vim-projectionist) plugin so all credits go to the authors and maintainers of this awesome Vim plugin.\n\n## Demo\n\n![Demo](docs/media/demo.gif)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftimfjord%2Fprojectionist","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftimfjord%2Fprojectionist","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftimfjord%2Fprojectionist/lists"}