This plugin contains various tools to work with ember.js projects. After installing it, just editing files in an ember.js project should be enough to activate them. It's similar to (and inspired by) rails.vim.
The tools work both for javascript and coffeescript, and they support both the handlebars and emblem templating languages.
It's recommended to also use the vim-projectionist plugin for easier navigation through the project. Here's a sample json file you might use with it: https://gist.github.com/AndrewRadev/3524ee46bca8ab349329. It sets up the major file types you might want to navigate to, and it connects routes, controllers, and templates, so that the :A
command jumps from route to controller, to template, and then back to the route.
A list of the different tools that the plugin provides follows.
This plugin sets a special includeexpr
that does a good job of locating files based on contextual information. This includes not only gf
, but the entire family of mappings that depends on includeexpr
, like <c-w>f
. From now on, for clarity, only "gf" will be used.
Pressing gf
on a route in the app/routes.js
file will open the relevant route file. This attempts to respect nesting as well, so as long as your routes don't do anything too fancy, the plugin will probably manage to find out where it's supposed to go. For example:
Router.map(function() {
this.route('foo', function() {
this.route('bar-baz');
})
});
Pressing gf
on "bar-baz" will jump to "app/routes/foo/bar-baz.js", provided that file exists.
You can also use it with transitionTo
and transitionToRoute
calls:
beforeModel() {
this.transitionTo('foo.bar-baz');
}
Pressing gf
on a component name in a template files will jump to that component's template. For example:
Pressing gf
while on "header-navigation" will jump to "app/components/header-navigation/template.hbs", or "app/templates/components/header-navigation.hbs", if any of those files exists.
Pressing gf
on an action name in a template files will jump to the current template's controller or component, and jump to the particular action. So, having a file like this:
Pressing gf
while on "showTooltip" will jump to the current template's controller/component file and find the "showTooltip" action.
If you have a service defined in the file "app/services/cookie-settings.js", then you can jump to that file while hitting gf
on the point of injection of the service:
import Ember from 'ember';
export default Ember.Service.extend({
cookieSettings: Ember.inject.service()
// ...
});
A gf
on "cookieSettings" will jump to the right file, if it exists. If you invoke the service with this.get
, you can also gf
there as well:
this.get('cookieSettings.someProperty');
A gf
on "cookieSettings" in the get
will also work, as long as there's an injection line in the file.
The same thing works for injection of controllers:
export default Ember.Controller.extend({
exampleController: Ember.inject.controller('example')
});
With an explicit layoutName
, templateName
, or controllerName
, you can gf
on the declaration to jump directly to it:
export default Ember.Controller.extend({
layoutName: 'some/template/name'
});
If you have a method call that is related to a model, then a gf
on it will jump to that model. For instance,
export default Ember.Model.extend({
user: DS.belongsTo("user")
});
A gf
on the "user" in the belongsTo
call will jump to the user model, if it exists. The method calls that work this way are:
createRecord
modelFor
belongsTo
hasMany
With a partial
call in the template, you can gf
directly to the referred template:
With a render
call in a logic file, you can gf
to the template as well:
this.render('partials/foo-bar/baz');
If you have import lines, like this:
import Ember from 'ember';
import ControllerCommonMixin from '../../mixins/controller-common';
import OtherMixin from 'app-name/mixins/other';
export default Ember.Controller.extend(ControllerCommonMixin, OtherMixin)
Using gf
on "../../mixins/controller-common" will send you to the right file, relative to the current one. Using gf
on "app-name/mixins/other" will also send you to the right file, provided there's a package.json
file in the app root that defines the app name and that Vim has the json_decode
function (available for version 8 and some late patchlevels of 7.4).
The :Extract
command is invoked on a range of lines, usually in visual mode. It's only defined in templating languages (handlebars or emblem). It takes the selected lines and moves them to a separate component's template. It also creates a placeholder component file for them.
So, if you have a template that looks like this:
You can mark everything within the <header>
tag and execute this command:
:Extract header-navigation
This will create the following files:
app/components/header-navigation/component.js
app/components/header-navigation/template.hbs
The original template will now look like this:
And the header-navigation template file will be opened in a split window and will contain:
If the original template is an emblem one, the component will also have an emblem template, but if you'd like to specify explicitly what templates you prefer, set the g:ember_tools_default_logic_filetype
and/or g:ember_tools_default_template_filetype
configuration variables.
The :Unpack
command helps you unpack an imported variable into its component pieces. An example might looks something like this:
import Ember from 'ember';
export default Ember.Controller.extend({
foo: Ember.computed.equal('bar', 'baz')
});
Running the :Unpack
command with the cursor on Ember.Controller
would lead to the following result:
import Ember from 'ember';
const { Controller } = Ember;
export default Controller.extend({
foo: Ember.computed.equal('bar', 'baz')
});
The command creates a const { ... } =
line that unpacks the Ember
variable's Controller
component into its own variable.
You can continue to run :Unpack
on, for instance, Ember.computed.equal
, and then once again on the remaining computed.equal
(if you have repeat.vim installed, you can just trigger the .
mapping) to get:
import Ember from 'ember';
const { Controller, computed } = Ember;
const { equal } = computed;
export default Controller.extend({
foo: equal('bar', 'baz')
});
The command adds new entries to the end of the list. If you'd like to sort them in some way afterwards, you can try using a different plugin of mine, sideways.
The :Inline
command inlines an "unpacked" variable. If you have code like this:
import Ember from 'ember';
const { computed, Controller } = Ember;
export default Controller.extend({
foo: computed.equal('bar', 'baz')
bar: computed.equal('baz', 'qux')
});
Running :Inline
on computed
within the const { ... }
definition will remove it from that list and replace it across the file (ignoring strings and comments) with Ember.computed
.
import Ember from 'ember';
const { Controller } = Ember;
export default Controller.extend({
foo: Ember.computed.equal('bar', 'baz')
bar: Ember.computed.equal('baz', 'qux')
});
For now, this is simply a reversal of the :Unpack
command. In the future, the :Inline
command might also inline other kinds of constructs, like local variables or properties.
The :Inject
command provides an easy way to inject services in your Ember objects. Calling :Inject <some-service>
will add a line with the service injection. If you don't provide an argument, the plugin will look under the cursor for a get('<service-name>')
:
export default Ember.Object({
init() {
this.get('someService').someMethod();
},
});
With the cursor on someService
, running :Inject
will add the injection to the code:
export default Ember.Object({
someService: Ember.inject.service(),
init() {
this.get('someService').someMethod();
},
});
When calling :Inject
with an argument, it tab-completes all available services. If an injection is already present, the plugin simply notifies you. The cursor is kept where it was, so the injection might be offscreen -- you'll be notified it was successful.
The plugin attempts to highlight actions in javascript files in a special way. Any function definition that's in an action: { }
block would get the special syntax group emberAction
. By default, this underlines the action. So, in this example:
As you can see, both "someAction" and "anotherAction" are underscored to help see that they're actions, and not just methods.
To disable this behaviour, you'd need to set g:ember_tools_highlight_actions
to 0.
To control when the syntax highlighting is recomputed, change the g:ember_tools_highlight_actions_on
setting. See the documentation below for the possible values.
If you want to customize the highlighting, define the highlight group emberAction
to whatever you like. As an example:
highlight emberAction ctermfg=red gui=red
This will highlight actions in red in both color terminals and the gui.
let g:ember_tools_default_logic_filetype = 'coffee'
Default value: javascript
This variable controls the default logic filetype the plugin will use. In general, it'll try to use the same filetype as the current file (javascript or coffeescript), but in situations when it can't guess, it'll read this variable to find the "default" preference.
let g:ember_tools_default_template_filetype = 'emblem'
Default value: handlebars
This variable controls the default template filetype the plugin will use. In general, it'll try to use the same filetype as the current file (handlebars or emblem), but in situations when it can't guess, it'll read this variable to find the "default" preference.
let g:ember_tools_custom_gf_callbacks = ['SomeFunctionName']
Default value: []
This variable allows the user to set up custom callbacks for the gf
mapping. It should be a list of function names. The plugin will call those without any arguments, in order, and if any of them return anything that's not an empty string, it'll stop execution and use that as the result.
The current directory for the duration of the callback will be the ember root. Also, at this time, the iskeyword
parameter will be set to include the "." and "/" characters, in order to make it easier to match some ember identifiers. Feel free to change it in your callbacks, it will be reset once the callback is done.
An example of what you could potentially do can be found in this gist: https://gist.github.com/AndrewRadev/c62132f96deca165b8969eba7bc1dc13
There's quite a few project-specific things, which is why it's not a general-purpose callback. There's also a few invocations of the plugin's public API, which, unfortunately, you would have to read the source code to understand.
let g:ember_tools_extract_behaviour = 'component-dir'
Default value: "separate-template"
This setting controls the behaviour of :Extract
regarding what kind of files to generate and where. The options are:
- "separate-template" (the default):
- Component file:
app/components/<component-name>.js
- Template file:
app/templates/components/<component-name>.hbs
- Component file:
- "component-dir"
- Component file:
app/components/<component-name>/component.js
- Template file:
app/components/<component-name>/template.hbs
- Component file:
The file extensions might not be "js" and "hbs", depending on what the current filetype is and what other settings are set to.
let g:ember_tools_highlight_actions = 0
Default value: 1
When set to 1 (the default), the plugin will attempt to highlight function definitions in actions: { }
blocks with the emberAction
highlight group (that you can customize however you like). Set to 0 to disable this behaviour completely.
let g:ember_tools_highlight_actions_on = ['init', 'write']
Default value: ["init", "insert-leave", "normal-text-changed"]
This setting controls when the plugin will try to highlight ember actions. Unfortunately, I haven't found a way to plug this feature into the built-in syntax highlighting mechanism, so updating requires a manual search through the buffer for the right area. (If you have an idea how you can do this, a pull request, or even an issue with a suggestion would be very welcome).
What this means in practice is that the plugin needs to decide when to try to update the limits of the area. The setting is a list with all events that trigger the process. The available events are:
init
: When the buffer is initially loaded. You probably want this.write
: When the buffer is written to disk.insert-leave
: When you leave insert mode.normal-text-changed
: When you change text in normal mode, for instance, when pasting or using |r| to replace.cursor-hold
: When you don't move the cursor fortimeoutlen
milliseconds.
As an example, setting the value to ["init", "write", "cursor-hold"]
would only update the highlighting when writing and not moving the cursor, which might be more efficient than the default. (The default is not slow at all on my machine, but, as with everything, your mileage may vary).
Note that the "normal-text-changed" setting will only work if your Vim has the TextChanged
autocommand.
Pull requests are welcome, but take a look at CONTRIBUTING.md first for some guidelines.