Files
atom/src/syntax.coffee
2014-02-06 17:22:37 -08:00

114 lines
3.4 KiB
CoffeeScript

_ = require 'underscore-plus'
{specificity} = require 'clear-cut'
{Subscriber} = require 'emissary'
{GrammarRegistry, ScopeSelector} = require 'first-mate'
{$, $$} = require './space-pen-extensions'
Token = require './token'
# Public: Syntax class holding the grammars used for tokenizing.
#
# An instance of this class is always available as the `atom.syntax` global.
#
# The Syntax class also contains properties for things such as the
# language-specific comment regexes.
module.exports =
class Syntax extends GrammarRegistry
Subscriber.includeInto(this)
atom.deserializers.add(this)
@deserialize: ({grammarOverridesByPath}) ->
syntax = new Syntax()
syntax.grammarOverridesByPath = grammarOverridesByPath
syntax
constructor: ->
super
@scopedPropertiesIndex = 0
@scopedProperties = []
serialize: ->
{deserializer: @constructor.name, @grammarOverridesByPath}
createToken: (value, scopes) -> new Token({value, scopes})
addProperties: (args...) ->
name = args.shift() if args.length > 2
[selector, properties] = args
@scopedProperties.unshift(
name: name
selector: selector,
properties: properties,
specificity: specificity(selector),
index: @scopedPropertiesIndex++
)
removeProperties: (name) ->
for properties in @scopedProperties.filter((properties) -> properties.name is name)
_.remove(@scopedProperties, properties)
clearProperties: ->
@scopedProperties = []
@scopedPropertiesIndex = 0
# Public: Get a property for the given scope and key path.
#
# ## Example
# ```coffee
# comment = atom.syntax.getProperty(['.source.ruby'], 'editor.commentStart')
# console.log(comment) # '# '
# ```
#
# scope - An {Array} of {String} scopes.
# keyPath - A {String} key path.
#
# Returns a {String} property value or undefined.
getProperty: (scope, keyPath) ->
for object in @propertiesForScope(scope, keyPath)
value = _.valueForKeyPath(object, keyPath)
return value if value?
undefined
propertiesForScope: (scope, keyPath) ->
matchingProperties = []
candidates = @scopedProperties.filter ({properties}) -> _.valueForKeyPath(properties, keyPath)?
if candidates.length
element = @buildScopeElement(scope)
while element
matchingProperties.push(@matchingPropertiesForElement(element, candidates)...)
element = element.parentNode
matchingProperties
matchingPropertiesForElement: (element, candidates) ->
matchingScopedProperties = candidates.filter ({selector}) ->
$.find.matchesSelector(element, selector)
matchingScopedProperties.sort (a, b) ->
if a.specificity == b.specificity
b.index - a.index
else
b.specificity - a.specificity
_.pluck matchingScopedProperties, 'properties'
buildScopeElement: (scope) ->
scope = scope.slice()
element = $$ ->
elementsForRemainingScopes = =>
classString = scope.shift()
classes = classString.replace(/^\./, '').replace(/\./g, ' ')
if scope.length
@div class: classes, elementsForRemainingScopes
else
@div class: classes
elementsForRemainingScopes()
deepestChild = element.find(":not(:has(*))")
if deepestChild.length
deepestChild[0]
else
element[0]
cssSelectorFromScopeSelector: (scopeSelector) ->
new ScopeSelector(scopeSelector).toCssSelector()