mirror of
https://github.com/atom/atom.git
synced 2026-02-07 13:14:55 -05:00
176 lines
5.2 KiB
CoffeeScript
176 lines
5.2 KiB
CoffeeScript
_ = require 'underscore'
|
|
jQuery = require 'jquery'
|
|
Specificity = require 'specificity'
|
|
{$$} = require 'space-pen'
|
|
fsUtils = require 'fs-utils'
|
|
EventEmitter = require 'event-emitter'
|
|
NullGrammar = require 'null-grammar'
|
|
nodePath = require 'path'
|
|
pathSplitRegex = new RegExp("[#{nodePath.sep}.]")
|
|
|
|
module.exports =
|
|
class Syntax
|
|
registerDeserializer(this)
|
|
|
|
@deserialize: ({grammarOverridesByPath}) ->
|
|
syntax = new Syntax()
|
|
syntax.grammarOverridesByPath = grammarOverridesByPath
|
|
syntax
|
|
|
|
constructor: ->
|
|
@grammars = []
|
|
@grammarsByFileType = {}
|
|
@grammarsByScopeName = {}
|
|
@grammarOverridesByPath = {}
|
|
@scopedPropertiesIndex = 0
|
|
@scopedProperties = []
|
|
@nullGrammar = new NullGrammar
|
|
|
|
serialize: ->
|
|
{ deserializer: @constructor.name, @grammarOverridesByPath }
|
|
|
|
addGrammar: (grammar) ->
|
|
@grammars.push(grammar)
|
|
@grammarsByFileType[fileType] = grammar for fileType in grammar.fileTypes
|
|
@grammarsByScopeName[grammar.scopeName] = grammar
|
|
|
|
removeGrammar: (grammar) ->
|
|
if _.include(@grammars, grammar)
|
|
_.remove(@grammars, grammar)
|
|
delete @grammarsByFileType[fileType] for fileType in grammar.fileTypes
|
|
delete @grammarsByScopeName[grammar.scopeName]
|
|
|
|
setGrammarOverrideForPath: (path, scopeName) ->
|
|
@grammarOverridesByPath[path] = scopeName
|
|
|
|
clearGrammarOverrideForPath: (path) ->
|
|
delete @grammarOverridesByPath[path]
|
|
|
|
clearGrammarOverrides: ->
|
|
@grammarOverridesByPath = {}
|
|
|
|
selectGrammar: (filePath, fileContents) ->
|
|
|
|
return @grammarsByFileType["txt"] ? @nullGrammar unless filePath
|
|
|
|
@grammarOverrideForPath(filePath) ?
|
|
@grammarByFirstLineRegex(filePath, fileContents) ?
|
|
@grammarByPath(filePath) ?
|
|
@grammarsByFileType["txt"] ?
|
|
@nullGrammar
|
|
|
|
grammarOverrideForPath: (path) ->
|
|
@grammarsByScopeName[@grammarOverridesByPath[path]]
|
|
|
|
grammarByPath: (path) ->
|
|
pathComponents = path.split(pathSplitRegex)
|
|
for fileType, grammar of @grammarsByFileType
|
|
fileTypeComponents = fileType.split(pathSplitRegex)
|
|
pathSuffix = pathComponents[-fileTypeComponents.length..-1]
|
|
return grammar if _.isEqual(pathSuffix, fileTypeComponents)
|
|
|
|
grammarByFirstLineRegex: (filePath, fileContents) ->
|
|
try
|
|
fileContents ?= fsUtils.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...) ->
|
|
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}) ->
|
|
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]
|
|
|
|
cssSelectorFromScopeSelector: (scopeSelector) ->
|
|
scopeSelector.split(', ').map((commaFragment) ->
|
|
commaFragment.split(' ').map((spaceFragment) ->
|
|
spaceFragment.split('.').map((dotFragment) ->
|
|
'.' + dotFragment.replace(/\+/g, '\\+')
|
|
).join('')
|
|
).join(' ')
|
|
).join(', ')
|
|
|
|
_.extend(Syntax.prototype, EventEmitter)
|