Files
atom/src/grammar-registry.coffee
2015-11-11 00:03:24 +02:00

126 lines
4.0 KiB
CoffeeScript

_ = require 'underscore-plus'
{Emitter} = require 'event-kit'
FirstMate = require 'first-mate'
Token = require './token'
fs = require 'fs-plus'
PathSplitRegex = new RegExp("[/.]")
# Extended: Syntax class holding the grammars used for tokenizing.
#
# An instance of this class is always available as the `atom.grammars` global.
#
# The Syntax class also contains properties for things such as the
# language-specific comment regexes. See {::getProperty} for more details.
module.exports =
class GrammarRegistry extends FirstMate.GrammarRegistry
constructor: ({@config}={}) ->
super(maxTokensPerLine: 100)
createToken: (value, scopes) -> new Token({value, scopes})
# Extended: Select a grammar for the given file path and file contents.
#
# This picks the best match by checking the file path and contents against
# each grammar.
#
# * `filePath` A {String} file path.
# * `fileContents` A {String} of text for the file path.
#
# Returns a {Grammar}, never null.
selectGrammar: (filePath, fileContents) ->
bestMatch = null
highestScore = -Infinity
for grammar in @grammars
score = @getGrammarScore(grammar, filePath, fileContents)
if score > highestScore or not bestMatch?
bestMatch = grammar
highestScore = score
bestMatch
# Extended: Returns a {Number} representing how well the grammar matches the
# `filePath` and `contents`.
getGrammarScore: (grammar, filePath, contents) ->
return Infinity if @grammarOverrideForPath(filePath) is grammar.scopeName
contents = fs.readFileSync(filePath, 'utf8') if not contents? and fs.isFileSync(filePath)
score = @getGrammarPathScore(grammar, filePath)
if score > 0 and not grammar.bundledPackage
score += 0.25
if @grammarMatchesContents(grammar, contents)
score += 0.125
score
getGrammarPathScore: (grammar, filePath) ->
return -1 unless filePath
filePath = filePath.replace(/\\/g, '/') if process.platform is 'win32'
pathComponents = filePath.toLowerCase().split(PathSplitRegex)
pathScore = -1
fileTypes = grammar.fileTypes
if customFileTypes = @config.get('core.customFileTypes')?[grammar.scopeName]
fileTypes = fileTypes.concat(customFileTypes)
for fileType, i in fileTypes
fileTypeComponents = fileType.toLowerCase().split(PathSplitRegex)
pathSuffix = pathComponents[-fileTypeComponents.length..-1]
if _.isEqual(pathSuffix, fileTypeComponents)
pathScore = Math.max(pathScore, fileType.length)
if i >= grammar.fileTypes.length
pathScore += 0.5
pathScore
grammarMatchesContents: (grammar, contents) ->
return false unless contents? and grammar.firstLineRegex?
escaped = false
numberOfNewlinesInRegex = 0
for character in grammar.firstLineRegex.source
switch character
when '\\'
escaped = not escaped
when 'n'
numberOfNewlinesInRegex++ if escaped
escaped = false
else
escaped = false
lines = contents.split('\n')
grammar.firstLineRegex.testSync(lines[0..numberOfNewlinesInRegex].join('\n'))
# Public: Get the grammar override for the given file path.
#
# * `filePath` A {String} file path.
#
# Returns a {Grammar} or undefined.
grammarOverrideForPath: (filePath) ->
@grammarOverridesByPath[filePath]
# Public: Set the grammar override for the given file path.
#
# * `filePath` A non-empty {String} file path.
# * `scopeName` A {String} such as `"source.js"`.
#
# Returns a {Grammar} or undefined.
setGrammarOverrideForPath: (filePath, scopeName) ->
if filePath
@grammarOverridesByPath[filePath] = scopeName
# Public: Remove the grammar override for the given file path.
#
# * `filePath` A {String} file path.
#
# Returns undefined.
clearGrammarOverrideForPath: (filePath) ->
delete @grammarOverridesByPath[filePath]
undefined
# Public: Remove all grammar overrides.
#
# Returns undefined.
clearGrammarOverrides: ->
@grammarOverridesByPath = {}
undefined