Why is it so difficult to map snippets to keymaps?


#1

So I love atom. However I’ve been totally baffled by how insane is to map a snippet to a keymap.
If the whole pruprose of the snippet file is to define short snippets and expand them with a prefix, is it possible to open with a keymap?
I have, for example a snippet for inserting inline code for JavaScript in markdown:

'.source.gfm':
  'javascript':
    'prefix': '$js'
    'body': '```javascript\n$1\n```'

Works perfectly. However i would like to press ctrl+j and be able to use the snippet Something like:

'atom-text-editor[data-grammar="source gfm"]':
  'ctrl-j': 'snippets:gfm.javascript',

However I haven’t been able to make anything like that work. So the only solution seems to make a command in the init.coffee that writes the snippet directly on the editor and then assign that command to the keymap.cson… that is rather silly, considering you can do that kind of stuff really easily in Sublime.

Am I doing this wrong? I see all the questions related always end up pointing to the init.coffee custom command solution which makes a bit useless a consolidate of my snippets file (I end up rewriting the snippet into coffeescript code so i can have both behaviors, tab autocomplete and keymapped).
Thank you all!


#2

There is no official way to bind a snippet to a keybinding, but there is a way to access the snippets package and insert a snippet from code:

atom.commands.add 'atom-text-editor[data-grammar="source coffee"]',
  'custom:insert-fun': ->
    return unless editor = atom.workspace.getActiveTextEditor()
    return unless mod = atom.packages.getActivePackage('snippets')?.mainModule
    return unless snippet = mod.getSnippets(editor)['fun']
    mod.insert(snippet, editor, editor.getLastCursor())

#3

Thank you @deprint, it is apossible solution, but it is again exactly what I would like to avoid. Modifying the init.coffee for something like this is precisely where Sublime is much more flexible.

The disadvantages of this method:

  • Cannot assign snippets to keymaps directly.
  • Requires debugging of the command. If i mess up the command, I would need to restart atom after making a correction (!)
  • If I am going to get a hold of the snippets package… I might as well jsut set my own templates on the commands and just forego the snippet altogether.

Also: i was trying your method for assigning a command to use a snippet i created, it did not work like this:

atom.commands.add 'atom-text-editor[data-grammar="source gfm"]',
  'custom:insert-something': ->
    return unless editor = atom.workspace.getActiveTextEditor()
    return unless mod = atom.packages.getActivePackage('snippets')?.mainModule
    return unless snippet = mod.getSnippets(editor)['.source.gfm.italic']
    mod.insert(snippet, editor, editor.getLastCursor())

#4

To bind an action to a key binding, you need a command (code). Maybe the snippets package maintainers already have this feature on their agenda (if not, consider opening an issue on github).

Same problem when you mess up your snippets file.

Coding your own templates is easy if you’re dealing with simple “insert this text and move cursor to end” snippets, but the package also supports tab-stops and default values. Re-implementing these features is unnecessary work.

About your version of my solution: The key of the object returned by getSnippets(editor) is (usually) the prefix of the snippet (For markdown italic it’s i).

With a bit more coding you could also generate these commands dynamically:

atom.packages.activatePackage('snippets').then ({mainModule}) ->
  if mainModule.loaded
    loadSnippets(mainModule)
  else
    mainModule.onDidLoadSnippets -> loadSnippets(mainModule)

loadSnippets = (mainModule) ->
  for set in mainModule.scopedPropertyStore.propertySets
    if (m = /^\.([^.]+)\./.exec set.selector.toString())?
      map = {}
      for k in Object.keys(set.properties.snippets)
        name = set.properties.snippets[k]?.name
        continue unless name?
        map['snippets:insert-' + name.replace /\s/, '-'] = ((key) -> ->
          return unless editor = atom.workspace.getActiveTextEditor()
          return unless snippet = mainModule.getSnippets(editor)[key]
          mainModule.insert(snippet, editor, editor.getLastCursor())
        )(k)
      continue unless Object.keys(map).length isnt 0
      atom.commands.add "atom-text-editor[data-grammar$=\"#{m[1]}\"]", map
      console.log "#{m[1]}: #{Object.keys(map)}"

I don’t expect it to support every grammar though. snippets:insert-italic-text in a markdown file would add **.


#5

@deprint:
Same problem when you mess up your snippets file.

I can change snippets and not have to reload atom, for commands, I always need to restart for them to take. Am I doing something wrong here? I thought the commands were loaded on initialization and that’s why they cannot be loaded dynamically.

My point is how extremely difficult is to do something like set snippet -> assing keymap to it -> surround selection with snippet using keymap when comparing to any other editor to the level of Atom.


#6

If you’re building commands in the init.coffee, then no, you’re not doing anything wrong. But yes, you need to restart or reload the window for the init.coffee to be evaluated again.

Commands are updated whenever the code to update them is executed. It is the init.coffee that is only executed when the window is first loaded. There is nothing about commands themselves that prevent them from being updated at any time. (They often are updated at various times … like when you install or update package from the Settings View, for instance.)

To answer your initial question though … it is hard to assign keymaps to snippets because they weren’t designed to be mapped that way. Key combinations were intended to be mapped to commands and commands have nothing to do with snippets. If this is something that you want to do often, then what you probably want is for someone to create a package that makes binding key combinations to snippets easier.