mirror of
https://github.com/atom/atom.git
synced 2026-01-22 21:38:10 -05:00
114 lines
3.4 KiB
CoffeeScript
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()
|