Go to an alternate file and more.
- jump between test and implementation files
- open/jump to an alternate file from the Side Bar
.projections.json
file support (including JSON schema validation)- built-in projections for Elixir, Ruby, and Sublime package development
- public API for other packages to use
- work on all platforms
- Install the Sublime Text Package Control package if you don't have it already.
- Open the command palette and start typing
Package Control: Install Package
. - Enter
Projectionist
.
The package implements the logic that was originally introduced by Tim Pope in his vim-projectionist plugin. And the main idea is to:
provide granular project configuration using "projections".
The package exposes the following commands to work with projections:
projectionist_open_alternate
- open an alternate file for the current file.
In Sublime Text 4 this command supports"mode": "side_by_side"
, to open an alternate file in a side-by-side view. This command is also available in the Command Palette (Projectionist: Open alternate
) as well as in the Side Bar. In the Side Bar there is also an option to jump(reveal in Side Bar) an alternate file rather than open it.
Check Default.sublime-commands to see the list of all available commands.
Projections are maps from filenames and globs to sets of properties describing the file.
The simplest way to define them is to create a .projections.json
in the root of the project,
but there are other ways to define projection too (see Sublime Text integration section).
The package ships with a JSON schema for the .projections.json
file to provide validation and auto-completion (requires LSP-json package).
Here's a simple example for a Maven project:
{
"src/main/java/*.java": {
"alternate": "src/test/java/{}.java",
},
"src/test/java/*.java": {
"alternate": "src/main/java/{}.java",
}
}
In property values, {}
will be replaced by the portion of the glob matched by the *
.
You can also chain one or more transformations inside the braces separated by bars, e.g. {dot|hyphenate}
.
The complete list of available transformations is as follows:
Name | Behavior |
---|---|
dot |
/ to . |
underscore |
/ to _ |
backslash |
/ to \ |
colons |
/ to :: |
hyphenate |
_ to - |
blank |
_ and - to space |
uppercase |
uppercase |
camelcase |
foo_bar/baz_quux to fooBar/bazQuux |
snakecase |
FooBar/bazQuux to foo_bar/baz_quux |
capitalize |
capitalize first letter and each letter after a slash |
dirname |
remove last slash separated component |
basename |
remove all but last slash separated component |
singular |
singularize |
plural |
pluralize |
file |
absolute path to file |
project |
absolute path to project |
open |
literal { |
close |
literal } |
nothing |
empty string |
vim |
no-op (include to specify other implementations should ignore) |
From a globbing perspective, *
is actually a stand in for **/*
.
For advanced cases, you can include both globs explicitly: test/**/test_*.rb
.
When expanding with {}
, the **
and *
portions are joined with a slash.
If necessary, the dirname and basename expansions can be used to split the value back apart.
As of now, the package supports only some of the properties from the original implementation, but support to more properties will be added in the future. Here are the properties that are currently supported:
-
alternate
Determines the destination of theprojectionist_open_alternate
command.
If this is a list, the first readable file will be used. Will also be used as a default forrelated
. -
template
Array of lines to use when creating a new file.
These are the properties that are not supported yet:
-
console
Command to run to start a REPL or other interactive shell.
This is useful to set from a*
projection or on a simple file glob like*.js
.
Will also be used as a default forstart
.
Expansions are shell escaped. -
dispatch
Expansions are shell escaped. -
make
Setsmakeprg
.
This is useful to set from a*
projection.
Expansions are shell escaped. -
path
Additional directories to prepend topath
.
Can be relative to the project root or absolute.
This is useful to set on a simple file glob like*.js
. -
related
Indicates one or more files to search when a navigation command is called without an argument, to find a default destination.
Related files are searched recursively. -
start
Command to run to "boot" the project.
Examples includelein run
,rails server
, andforeman start
.
This is useful to set from a*
projection.
Expansions are shell escaped. -
type
Declares the type of file and creates a set of navigation commands for opening files that match the glob.
The package supports projections from the following sources:
local
- projections defined in the project config file (e.g.MyProject.sublime-project
)file
- projections defined in the.projections.json
fileglobal
- heuristic projections defined in the global settingsbuiltin
- built-in heuristic projections that ship with the package
The lookup order is determined by the lookup_order
setting and it is:
{
"lookup_order": [
"local", // first look in the project settings
"file", // then, in the .projections.json file
"global", // then, the `heuristic_projections` from the package settings
"builtin" // and finally, he built-in heuristic projections
]
}
To avoid evaluation projections from some of the sources, remove them from the lookup_order
setting:
{
"lookup_order": ["local", "file"] // evaluate only local and .projections.json projections
}
The order of the items in the lookup_order
setting is important, so switching items in the array will change the lookup order:
{
"lookup_order": [
"file", // first look in the .projections.json file
"local", // then, in the project settings
"builtin", // then, the built-in heuristic projections
"global" // and finally, the `heuristic_projections` from the package settings
]
}
Given the number of projection sources, the package provides the projectionist_output_projections
command
(or Projectionist: Output projections
in the Command Palette)
to output all the projections to the Sublime Text console.
The lookup_order
also determines how projections with the same pattern are handled.
So, for example, if the same pattern is defined in multiple sources,
the properties will be merged obeying the order defined in the lookup_order
setting.
So, for example, if there is a local projection:
{
"lib/*.ex": {
"alternate": "test/{}_test.exs",
"prop2": "value2"
}
}
and a built-in projection:
{
"lib/*.ex": {
"alternate": "spec/{}_spec.exs",
"prop1": "value1"
}
}
the resulting projection will be:
{
"lib/*.ex": {
"alternate": "test/{}_test.exs",
"prop1": "value1",
"prop2": "value2"
}
}
And to avoid overriding projection defined on deeper levels, the append
/prepend
prefix can be used, for example:
{
"lib/*.ex": {
"prepend_alternate": "spec/{}_spec.exs",
"prop1": "value1"
}
}
will result in:
{
"lib/*.ex": {
"alternate": [
"spec/{}_spec.exs",
"test/{}_test.exs"
],
"prop1": "value1",
"prop2": "value2"
}
}
This can be very useful to, say, fine-tune projections defined in the .projections.json
file
since this file can be used by other editors (e.g. VIM, VSCode).
As of now, only the alternate
property supports these prefixes.
Local projections can be defined in the project settings:
{
"folders": [
{
"path": ".",
}
],
"settings": {
"Projectionist": {
"projections": {
"plugin/*.py": {
"alternate": "tests/{dirname}/test_{basename}.py",
},
"tests/**/test_*.py": {
"alternate": "plugin/{}.py",
}
}
}
}
}
Heuristic 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.
The keys of the dictionary are files and directories that can be found in the root of a project, with &
separating multiple requirements and |
separating multiple alternatives.
You can also prefix a file or directory with !
to forbid rather than require its presence.
In the example below, the first key requires a file named mix.exs
and a file named test/test_helper.exs
.
{
"heuristic_projections": {
"mix.exs&test/test_helper.exs": {
"lib/*.ex": {
"alternate": "test/{}_test.exs",
"template": [
"defmodule {camelcase|capitalize|dot} do",
"end"
]
},
"test/*_test.exs": {
"alternate": "lib/{}.ex",
"template": [
"defmodule {camelcase|capitalize|dot}Test do",
" use ExUnit.Case",
"",
" alias {camelcase|capitalize|dot}",
"end",
]
}
}
}
}
The package comes with the following list of built-in projections and they are enabled by default:
To disable some of the built-in projections, remove them from the builtin_heuristic_projections
setting:
{
"builtin_heuristic_projections": ["elixir", "ruby"] // disable the sublime projection
}
For performance reasons, heuristic projections (both global and built-in) are determined once per project and then cached.
The same goes for the .projections.json
file, it is parsed and cached per project.
So it is important to clear the cache after changing heuristic projections or updating the .projections.json
file.
The cache can be cleared with the projectionist_clear_cache
command or via the Projectionist: Clear Cache
command from the Command Palette.
The package supports multiple project folders. It can be very useful when there is a nested folder
that contains a separate project. The package can detect this situation and use this information
for heuristic projections calculation and detecting the .projections.json
file.
Another way to handle nested projects is to use the subprojects
settings (usually in the project config)
{
"folders": [
{
"path": ".",
}
],
"settings": {
"Projectionist": {
"subprojects": [
"subfolder1/subfolder1_1",
["subfolder2", "subfolder2_1"]
]
}
}
}
A subproject can be either a string or an array of strings(the path separator will be added automatically in this case).
To allow other packages to find alternate files and more, the package exposes the projectionist
module that acts as the public API.
Check plugin/api.py
for implementation details and tests/test_api.py
for test cases.
Allows to find an alternate file for a given file and root directory.
Returns 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.
try:
from projectionist import find_alternate_file
root = "~/code/project"
file = "~/code/project/folder1/file1.py"
exists, alternate = find_alternate_file(root, file)
if alternate is None:
print("No alternate file defined")
elif exists:
print("Alternate file exists")
else:
print("Alternate file defined but does not exist")
except ImportError:
print("Projectionist is not installed")
- support more original projectionist features, like
type
,console
,dispatch
Projectionist
is a Sublime Text implementation of the vim-projectionist plugin so all credits go to the authors and maintainers of this awesome Vim plugin.