mirror of
https://github.com/atom/atom.git
synced 2026-02-14 00:25:08 -05:00
110 lines
3.3 KiB
CoffeeScript
110 lines
3.3 KiB
CoffeeScript
_ = require 'underscore'
|
|
jQuery = require 'jquery'
|
|
Specificity = require 'specificity'
|
|
{$$} = require 'space-pen'
|
|
fs = require 'fs'
|
|
EventEmitter = require 'event-emitter'
|
|
|
|
module.exports =
|
|
class Syntax
|
|
constructor: ->
|
|
@grammars = []
|
|
@grammarsByFileType = {}
|
|
@grammarsByScopeName = {}
|
|
@globalProperties = {}
|
|
@scopedPropertiesIndex = 0
|
|
@scopedProperties = []
|
|
|
|
addGrammar: (grammar) ->
|
|
@grammars.push(grammar)
|
|
for fileType in grammar.fileTypes
|
|
@grammarsByFileType[fileType] = grammar
|
|
@grammarsByScopeName[grammar.scopeName] = grammar
|
|
|
|
grammarForFilePath: (filePath, fileContents) ->
|
|
return @grammarsByFileType["txt"] unless filePath
|
|
|
|
extension = fs.extension(filePath)?[1..]
|
|
if filePath and extension.length == 0
|
|
extension = fs.base(filePath)
|
|
|
|
@grammarByFirstLineRegex(filePath, fileContents) or
|
|
@grammarsByFileType[extension] or
|
|
@grammarByFileTypeSuffix(filePath) or
|
|
@grammarsByFileType["txt"]
|
|
|
|
grammarByFileTypeSuffix: (filePath) ->
|
|
for fileType, grammar of @grammarsByFileType
|
|
return grammar if _.endsWith(filePath, fileType)
|
|
|
|
grammarByFirstLineRegex: (filePath, fileContents) ->
|
|
try
|
|
fileContents = fs.read(filePath) unless fileContents?
|
|
catch e
|
|
null
|
|
|
|
_.find @grammars, (grammar) -> grammar.firstLineRegex?.test(fileContents)
|
|
|
|
grammarForScopeName: (scopeName) ->
|
|
@grammarsByScopeName[scopeName]
|
|
|
|
addProperties: (args...) ->
|
|
selector = args.shift() if args.length > 1
|
|
properties = args.shift()
|
|
|
|
if selector
|
|
@scopedProperties.unshift(
|
|
selector: selector,
|
|
properties: properties,
|
|
specificity: Specificity(selector),
|
|
index: @scopedPropertiesIndex++
|
|
)
|
|
else
|
|
_.extend(@globalProperties, properties)
|
|
|
|
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.concat([@globalProperties])
|
|
|
|
matchingPropertiesForElement: (element, candidates) ->
|
|
matchingScopedProperties = candidates.filter ({selector}) ->
|
|
jQuery.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]
|
|
|
|
_.extend(Syntax.prototype, EventEmitter)
|