mirror of
https://github.com/atom/atom.git
synced 2026-01-23 05:48:10 -05:00
136 lines
4.1 KiB
CoffeeScript
136 lines
4.1 KiB
CoffeeScript
_ = require 'underscore-plus'
|
|
{specificity} = require 'clear-cut'
|
|
{$, $$} = require './space-pen-extensions'
|
|
{Emitter} = require 'emissary'
|
|
NullGrammar = require './null-grammar'
|
|
TextMateScopeSelector = require('first-mate').ScopeSelector
|
|
|
|
### Internal ###
|
|
|
|
module.exports =
|
|
class Syntax
|
|
Emitter.includeInto(this)
|
|
|
|
atom.deserializers.add(this)
|
|
|
|
@deserialize: ({grammarOverridesByPath}) ->
|
|
syntax = new Syntax()
|
|
syntax.grammarOverridesByPath = grammarOverridesByPath
|
|
syntax
|
|
|
|
constructor: ->
|
|
@nullGrammar = new NullGrammar
|
|
@grammars = [@nullGrammar]
|
|
@grammarsByScopeName = {}
|
|
@injectionGrammars = []
|
|
@grammarOverridesByPath = {}
|
|
@scopedPropertiesIndex = 0
|
|
@scopedProperties = []
|
|
|
|
serialize: ->
|
|
{ deserializer: @constructor.name, @grammarOverridesByPath }
|
|
|
|
addGrammar: (grammar) ->
|
|
previousGrammars = new Array(@grammars...)
|
|
@grammars.push(grammar)
|
|
@grammarsByScopeName[grammar.scopeName] = grammar
|
|
@injectionGrammars.push(grammar) if grammar.injectionSelector?
|
|
@grammarUpdated(grammar.scopeName)
|
|
@emit 'grammar-added', grammar
|
|
|
|
removeGrammar: (grammar) ->
|
|
_.remove(@grammars, grammar)
|
|
delete @grammarsByScopeName[grammar.scopeName]
|
|
_.remove(@injectionGrammars, grammar)
|
|
@grammarUpdated(grammar.scopeName)
|
|
|
|
grammarUpdated: (scopeName) ->
|
|
for grammar in @grammars when grammar.scopeName isnt scopeName
|
|
@emit 'grammar-updated', grammar if grammar.grammarUpdated(scopeName)
|
|
|
|
setGrammarOverrideForPath: (path, scopeName) ->
|
|
@grammarOverridesByPath[path] = scopeName
|
|
|
|
clearGrammarOverrideForPath: (path) ->
|
|
delete @grammarOverridesByPath[path]
|
|
|
|
clearGrammarOverrides: ->
|
|
@grammarOverridesByPath = {}
|
|
|
|
selectGrammar: (filePath, fileContents) ->
|
|
grammar = _.max @grammars, (grammar) -> grammar.getScore(filePath, fileContents)
|
|
grammar
|
|
|
|
grammarOverrideForPath: (path) ->
|
|
@grammarOverridesByPath[path]
|
|
|
|
grammarForScopeName: (scopeName) ->
|
|
@grammarsByScopeName[scopeName]
|
|
|
|
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
|
|
|
|
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 = new Array(scope...)
|
|
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 TextMateScopeSelector(scopeSelector).toCssSelector()
|