diff --git a/src/grammar-registry.coffee b/src/grammar-registry.coffee deleted file mode 100644 index a2341c967..000000000 --- a/src/grammar-registry.coffee +++ /dev/null @@ -1,130 +0,0 @@ -_ = require 'underscore-plus' -FirstMate = require 'first-mate' -Token = require './token' -fs = require 'fs-plus' -Grim = require 'grim' - -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, maxLineLength: 1000) - - 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) -> - @selectGrammarWithScore(filePath, fileContents).grammar - - selectGrammarWithScore: (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 - {grammar: bestMatch, score: highestScore} - - # Extended: Returns a {Number} representing how well the grammar matches the - # `filePath` and `contents`. - getGrammarScore: (grammar, filePath, contents) -> - 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')) - - # Deprecated: Get the grammar override for the given file path. - # - # * `filePath` A {String} file path. - # - # Returns a {String} such as `"source.js"`. - grammarOverrideForPath: (filePath) -> - Grim.deprecate 'Use atom.textEditors.getGrammarOverride(editor) instead' - if editor = getEditorForPath(filePath) - atom.textEditors.getGrammarOverride(editor) - - # Deprecated: Set the grammar override for the given file path. - # - # * `filePath` A non-empty {String} file path. - # * `scopeName` A {String} such as `"source.js"`. - # - # Returns undefined - setGrammarOverrideForPath: (filePath, scopeName) -> - Grim.deprecate 'Use atom.textEditors.setGrammarOverride(editor, scopeName) instead' - if editor = getEditorForPath(filePath) - atom.textEditors.setGrammarOverride(editor, scopeName) - return - - # Deprecated: Remove the grammar override for the given file path. - # - # * `filePath` A {String} file path. - # - # Returns undefined. - clearGrammarOverrideForPath: (filePath) -> - Grim.deprecate 'Use atom.textEditors.clearGrammarOverride(editor) instead' - if editor = getEditorForPath(filePath) - atom.textEditors.clearGrammarOverride(editor) - return - -getEditorForPath = (filePath) -> - if filePath? - atom.workspace.getTextEditors().find (editor) -> - editor.getPath() is filePath diff --git a/src/grammar-registry.js b/src/grammar-registry.js new file mode 100644 index 000000000..b1de16ba1 --- /dev/null +++ b/src/grammar-registry.js @@ -0,0 +1,171 @@ +const _ = require('underscore-plus') +const FirstMate = require('first-mate') +const Token = require('./token') +const fs = require('fs-plus') +const Grim = require('grim') + +const 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, maxLineLength: 1000}) + this.config = config + } + + createToken (value, scopes) { + return 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) { + return this.selectGrammarWithScore(filePath, fileContents).grammar + } + + selectGrammarWithScore (filePath, fileContents) { + let bestMatch = null + let highestScore = -Infinity + for (let grammar of this.grammars) { + const score = this.getGrammarScore(grammar, filePath, fileContents) + if ((score > highestScore) || (bestMatch == null)) { + bestMatch = grammar + highestScore = score + } + } + return {grammar: bestMatch, score: highestScore} + } + + // Extended: Returns a {Number} representing how well the grammar matches the + // `filePath` and `contents`. + getGrammarScore (grammar, filePath, contents) { + if ((contents == null) && fs.isFileSync(filePath)) { + contents = fs.readFileSync(filePath, 'utf8') + } + + let score = this.getGrammarPathScore(grammar, filePath) + if ((score > 0) && !grammar.bundledPackage) { + score += 0.25 + } + if (this.grammarMatchesContents(grammar, contents)) { + score += 0.125 + } + return score + } + + getGrammarPathScore (grammar, filePath) { + if (!filePath) { return -1 } + if (process.platform === 'win32') { filePath = filePath.replace(/\\/g, '/') } + + const pathComponents = filePath.toLowerCase().split(PathSplitRegex) + let pathScore = -1 + + let customFileTypes + if (this.config.get('core.customFileTypes')) { + customFileTypes = this.config.get('core.customFileTypes')[grammar.scopeName] + } + + let { fileTypes } = grammar + if (customFileTypes) { + fileTypes = fileTypes.concat(customFileTypes) + } + + for (let i = 0; i < fileTypes.length; i++) { + const fileType = fileTypes[i] + const fileTypeComponents = fileType.toLowerCase().split(PathSplitRegex) + const pathSuffix = pathComponents.slice(-fileTypeComponents.length) + if (_.isEqual(pathSuffix, fileTypeComponents)) { + pathScore = Math.max(pathScore, fileType.length) + if (i >= grammar.fileTypes.length) { + pathScore += 0.5 + } + } + } + + return pathScore + } + + grammarMatchesContents (grammar, contents) { + if ((contents == null) || (grammar.firstLineRegex == null)) { return false } + + let escaped = false + let numberOfNewlinesInRegex = 0 + for (let character of grammar.firstLineRegex.source) { + switch (character) { + case '\\': + escaped = !escaped + break + case 'n': + if (escaped) { numberOfNewlinesInRegex++ } + escaped = false + break + default: + escaped = false + } + } + const lines = contents.split('\n') + return grammar.firstLineRegex.testSync(lines.slice(0, numberOfNewlinesInRegex + 1).join('\n')) + } + + // Deprecated: Get the grammar override for the given file path. + // + // * `filePath` A {String} file path. + // + // Returns a {String} such as `"source.js"`. + grammarOverrideForPath (filePath) { + Grim.deprecate('Use atom.textEditors.getGrammarOverride(editor) instead') + + const editor = getEditorForPath(filePath) + if (editor) { + return atom.textEditors.getGrammarOverride(editor) + } + } + + // Deprecated: Set the grammar override for the given file path. + // + // * `filePath` A non-empty {String} file path. + // * `scopeName` A {String} such as `"source.js"`. + // + // Returns undefined. + setGrammarOverrideForPath (filePath, scopeName) { + Grim.deprecate('Use atom.textEditors.setGrammarOverride(editor, scopeName) instead') + + const editor = getEditorForPath(filePath) + if (editor) { + atom.textEditors.setGrammarOverride(editor, scopeName) + } + } + + // Deprecated: Remove the grammar override for the given file path. + // + // * `filePath` A {String} file path. + // + // Returns undefined. + clearGrammarOverrideForPath (filePath) { + Grim.deprecate('Use atom.textEditors.clearGrammarOverride(editor) instead') + + const editor = getEditorForPath(filePath) + if (editor) { + atom.textEditors.clearGrammarOverride(editor) + } + } +} + +function getEditorForPath (filePath) { + if (filePath != null) { + return atom.workspace.getTextEditors().find(editor => editor.getPath() === filePath) + } +}