Files
atom/src/app/syntax.coffee
Kevin Sawicki 1db21c91cc Give first line regex the required amount of lines
Certain bundles require multi-line matches in the firstLineMatch
value so count the number of newlines in the regex and only test
the regex against only those lines.
2013-02-20 15:11:07 -08:00

129 lines
3.7 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)
catch e
return
return unless fileContents
lines = fileContents.split('\n')
_.find @grammars, (grammar) ->
regex = grammar.firstLineRegex
return unless regex?
escaped = false
numberOfNewlinesInRegex = 0
for character in regex.source
switch character
when '\\'
escaped = !escaped
when 'n'
numberOfNewlinesInRegex++ if escaped
escaped = false
else
escaped = false
regex.test(lines[0..numberOfNewlinesInRegex].join('\n'))
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)