Sublime Forum

Key binding only when final entry in scope matches

#1

I want to create a key binding that only fires when the final entry in the scope list is “meta.jsx.js”.

The keybinding listens to the > key and is used to autocomplete React JSX tags, so when I type <span and then >, I get <span>|</span>. It works really well until I start embedding JSX tags inside dynamically evaluated expressions:

<ul>
  {items.map(item => {
    if (item.value > 10) {
      return <li>{item.value}</li>;
    }
    return null;
  })}
</ul>

In this case, I want the > key to complete the tag on the internal <li>, but not on the arrow function’s => symbol or the > inside the if statement. It seems to me that the best solution is to match the keybinding on the final entry of the scope, which is always meta.jsx.js at the place that I want to complete the tag. But I don’t know how to create a keybinding that only fires when the last entry in scope matches.

Any thoughts? I am also open to alternative methods of autocompleting tags, because I may be making this more complex than it needs to be.

1 Like

Syntax Testing: Asserting *Unmatched* Text
#2

I don’t know of a way to match only the final scope using a scope selector. Also, to make it harder, scope selectors have the limitation of needing to work with specific scopes, but maybe you can achieve it by doing something like this:

meta.jsx.js - (meta.jsx.js source | meta.jsx.js text | meta.jsx.js meta)

obviously you will need to include any relevant base scopes that need to be excluded.


personally, I think you’d be better off writing a small plugin to do the work for you:

import sublime
import sublime_plugin


class FinalScopeEventListener(sublime_plugin.EventListener):
    def on_query_context(self, view, key, operator, operand, match_all):
        if key != 'final_scope':
            return None
        if operator not in (sublime.OP_EQUAL, sublime.OP_NOT_EQUAL):
            return None
        
        match = False
        for sel in view.sel():
            match = (view.scope_name(sel.end()).strip().split(' ')[-1] == operand)
            if operator == sublime.OP_NOT_EQUAL:
                match = not match
            if match != match_all:
                break
        return match

then use this in your keybinding context:

{ "key": "final_scope", "operator": "equal", "operand": "meta.jsx.js", "match_all": true },

the idea is that it will loop through all your selections and check whether each ends in the scope you specify in the operand. Then there is an exclusive or, so that if a selection doesn’t match, and all selections should match, it will return false, and if the selection does match, and all selections don’t have to match, then it will return true. If all selections have to match, and they do, then it will return true.

1 Like