Store grammars on the syntax global

This commit is contained in:
Nathan Sobo
2012-12-31 18:11:11 -06:00
parent 4f50133fd8
commit 188d8f8604
8 changed files with 71 additions and 101 deletions

View File

@@ -1,4 +1,22 @@
describe "the `syntax` global", ->
describe ".grammarForFilePath(filePath)", ->
it "uses the filePath's extension to load the correct grammar", ->
expect(syntax.grammarForFilePath("file.js").name).toBe "JavaScript"
it "uses the filePath's base name if there is no extension", ->
expect(syntax.grammarForFilePath("Rakefile").name).toBe "Ruby"
it "uses the filePath's shebang line if the grammar cannot be determined by the extension or basename", ->
filePath = require.resolve("fixtures/shebang")
expect(syntax.grammarForFilePath(filePath).name).toBe "Ruby"
it "uses the grammar's fileType as a suffix of the full filePath if the grammar cannot be determined by shebang line", ->
expect(syntax.grammarForFilePath("/tmp/.git/config").name).toBe "Git Config"
it "uses plain text if no grammar can be found", ->
filePath = require.resolve("this-is-not-a-real-file")
expect(syntax.grammarForFilePath(filePath).name).toBe "Plain Text"
describe ".getProperty(scopeDescriptor)", ->
it "returns the property with the most specific scope selector", ->
syntax.addProperties(".source.coffee .string.quoted.double.coffee", foo: bar: baz: 42)

View File

@@ -2,14 +2,6 @@ fs = require('fs')
TextMateBundle = require 'text-mate-bundle'
describe "TextMateBundle", ->
describe ".getPreferencesByScopeSelector()", ->
it "logs warning, but does not raise errors if a preference can't be parsed", ->
bundlePath = fs.join(require.resolve('fixtures'), "test.tmbundle")
spyOn(console, 'warn')
bundle = new TextMateBundle(bundlePath)
expect(-> bundle.getPreferencesByScopeSelector()).not.toThrow()
expect(console.warn).toHaveBeenCalled()
describe ".constructor(bundlePath)", ->
it "logs warning, but does not raise errors if a grammar can't be parsed", ->
bundlePath = fs.join(require.resolve('fixtures'), "test.tmbundle")
@@ -17,20 +9,3 @@ describe "TextMateBundle", ->
expect(-> new TextMateBundle(bundlePath)).not.toThrow()
expect(console.warn).toHaveBeenCalled()
describe ".grammarForFilePath(filePath)", ->
it "uses the filePath's extension to load the correct grammar", ->
expect(TextMateBundle.grammarForFilePath("file.js").name).toBe "JavaScript"
it "uses the filePath's base name if there is no extension", ->
expect(TextMateBundle.grammarForFilePath("Rakefile").name).toBe "Ruby"
it "uses the filePath's shebang line if the grammar cannot be determined by the extension or basename", ->
filePath = require.resolve("fixtures/shebang")
expect(TextMateBundle.grammarForFilePath(filePath).name).toBe "Ruby"
it "uses the grammar's fileType as a suffix of the full filePath if the grammar cannot be determined by shebang line", ->
expect(TextMateBundle.grammarForFilePath("/tmp/.git/config").name).toBe "Git Config"
it "uses plain text if no grammar can be found", ->
filePath = require.resolve("this-is-not-a-real-file")
expect(TextMateBundle.grammarForFilePath(filePath).name).toBe "Plain Text"

View File

@@ -1,5 +1,4 @@
TextMateGrammar = require 'text-mate-grammar'
TextMateBundle = require 'text-mate-bundle'
plist = require 'plist'
fs = require 'fs'
_ = require 'underscore'
@@ -8,7 +7,7 @@ describe "TextMateGrammar", ->
grammar = null
beforeEach ->
grammar = TextMateBundle.grammarForFilePath("hello.coffee")
grammar = syntax.grammarForFilePath("hello.coffee")
describe ".tokenizeLine(line, ruleStack)", ->
describe "when the entire line matches a single pattern with no capture groups", ->
@@ -31,7 +30,7 @@ describe "TextMateGrammar", ->
describe "when the line doesn't match any patterns", ->
it "returns the entire line as a single simple token with the grammar's scope", ->
textGrammar = TextMateBundle.grammarForFilePath('foo.txt')
textGrammar = syntax.grammarForFilePath('foo.txt')
{tokens} = textGrammar.tokenizeLine("abc def")
expect(tokens.length).toBe 1
@@ -108,20 +107,20 @@ describe "TextMateGrammar", ->
describe "when the line matches no patterns", ->
it "does not infinitely loop", ->
grammar = TextMateBundle.grammarForFilePath("sample.txt")
grammar = syntax.grammarForFilePath("sample.txt")
{tokens} = grammar.tokenizeLine('hoo')
expect(tokens.length).toBe 1
expect(tokens[0]).toEqual value: 'hoo', scopes: ["text.plain", "meta.paragraph.text"]
describe "when the line matches a pattern with a 'contentName'", ->
it "creates tokens using the content of contentName as the token name", ->
grammar = TextMateBundle.grammarForFilePath("sample.txt")
grammar = syntax.grammarForFilePath("sample.txt")
{tokens} = grammar.tokenizeLine('ok, cool')
expect(tokens[0]).toEqual value: 'ok, cool', scopes: ["text.plain", "meta.paragraph.text"]
describe "when the line matches a pattern with no `name` or `contentName`", ->
it "creates tokens without adding a new scope", ->
grammar = TextMateBundle.grammarsByFileType["rb"]
grammar = syntax.grammarsByFileType["rb"]
{tokens} = grammar.tokenizeLine('%w|oh \\look|')
expect(tokens.length).toBe 5
expect(tokens[0]).toEqual value: '%w|', scopes: ["source.ruby", "string.quoted.other.literal.lower.ruby", "punctuation.definition.string.begin.ruby"]
@@ -166,7 +165,7 @@ describe "TextMateGrammar", ->
describe "when the end pattern contains a back reference", ->
it "constructs the end rule based on its back-references to captures in the begin rule", ->
grammar = TextMateBundle.grammarsByFileType["rb"]
grammar = syntax.grammarsByFileType["rb"]
{tokens} = grammar.tokenizeLine('%w|oh|,')
expect(tokens.length).toBe 4
expect(tokens[0]).toEqual value: '%w|', scopes: ["source.ruby", "string.quoted.other.literal.lower.ruby", "punctuation.definition.string.begin.ruby"]
@@ -175,7 +174,7 @@ describe "TextMateGrammar", ->
expect(tokens[3]).toEqual value: ',', scopes: ["source.ruby", "punctuation.separator.object.ruby"]
it "allows the rule containing that end pattern to be pushed to the stack multiple times", ->
grammar = TextMateBundle.grammarsByFileType["rb"]
grammar = syntax.grammarsByFileType["rb"]
{tokens} = grammar.tokenizeLine('%Q+matz had some #{%Q-crazy ideas-} for ruby syntax+ # damn.')
expect(tokens[0]).toEqual value: '%Q+', scopes: ["source.ruby","string.quoted.other.literal.upper.ruby","punctuation.definition.string.begin.ruby"]
expect(tokens[1]).toEqual value: 'matz had some ', scopes: ["source.ruby","string.quoted.other.literal.upper.ruby"]
@@ -192,7 +191,7 @@ describe "TextMateGrammar", ->
describe "when the pattern includes rules from another grammar", ->
it "parses tokens inside the begin/end patterns based on the included grammar's rules", ->
grammar = TextMateBundle.grammarsByFileType["html.erb"]
grammar = syntax.grammarsByFileType["html.erb"]
{tokens} = grammar.tokenizeLine("<div class='name'><%= User.find(2).full_name %></div>")
expect(tokens[0]).toEqual value: '<', scopes: ["text.html.ruby","meta.tag.block.any.html","punctuation.definition.tag.begin.html"]
@@ -243,18 +242,18 @@ describe "TextMateGrammar", ->
expect(tokens[1].value).toBe " a singleLineComment"
it "does not loop infinitely (regression)", ->
grammar = TextMateBundle.grammarForFilePath("hello.js")
grammar = syntax.grammarForFilePath("hello.js")
{tokens, ruleStack} = grammar.tokenizeLine("// line comment")
{tokens, ruleStack} = grammar.tokenizeLine(" // second line comment with a single leading space", ruleStack)
describe "when inside a C block", ->
it "correctly parses a method. (regression)", ->
grammar = TextMateBundle.grammarForFilePath("hello.c")
grammar = syntax.grammarForFilePath("hello.c")
{tokens, ruleStack} = grammar.tokenizeLine("if(1){m()}")
expect(tokens[5]).toEqual value: "m", scopes: ["source.c", "meta.block.c", "meta.function-call.c", "support.function.any-method.c"]
it "correctly parses nested blocks. (regression)", ->
grammar = TextMateBundle.grammarForFilePath("hello.c")
grammar = syntax.grammarForFilePath("hello.c")
{tokens, ruleStack} = grammar.tokenizeLine("if(1){if(1){m()}}")
expect(tokens[5]).toEqual value: "if", scopes: ["source.c", "meta.block.c", "keyword.control.c"]
expect(tokens[10]).toEqual value: "m", scopes: ["source.c", "meta.block.c", "meta.block.c", "meta.function-call.c", "support.function.any-method.c"]

View File

@@ -17,7 +17,7 @@ class LanguageMode
constructor: (@editSession) ->
@buffer = @editSession.buffer
@grammar = TextMateBundle.grammarForFilePath(@buffer.getPath())
@grammar = syntax.grammarForFilePath(@buffer.getPath())
@bracketAnchorRanges = []
_.adviseBefore @editSession, 'insertText', (text) =>

View File

@@ -2,14 +2,50 @@ _ = require 'underscore'
jQuery = require 'jquery'
Specificity = require 'specificity'
{$$} = require 'space-pen'
fs = require 'fs'
module.exports =
class Syntax
constructor: ->
@grammars = []
@grammarsByFileType = {}
@grammarsByScopeName = {}
@globalProperties = {}
@scopedPropertiesIndex = 0
@scopedProperties = []
@propertiesBySelector = {}
addGrammar: (grammar) ->
@grammars.push(grammar)
for fileType in grammar.fileTypes
@grammarsByFileType[fileType] = grammar
@grammarsByScopeName[grammar.scopeName] = grammar
grammarForFilePath: (filePath) ->
return @grammarsByFileType["txt"] unless filePath
extension = fs.extension(filePath)?[1..]
if filePath and extension.length == 0
extension = fs.base(filePath)
@grammarsByFileType[extension] or
@grammarByShebang(filePath) or
@grammarByFileTypeSuffix(filePath) or
@grammarsByFileType["txt"]
grammarByFileTypeSuffix: (filePath) ->
for fileType, grammar of @grammarsByFileType
return grammar if _.endsWith(filePath, fileType)
grammarByShebang: (filePath) ->
try
fileContents = fs.read(filePath)
catch e
null
_.find @grammars, (grammar) -> grammar.firstLineRegex?.test(fileContents)
grammarForScopeName: (scopeName) ->
@grammarsByScopeName[scopeName]
addProperties: (args...) ->
selector = args.shift() if args.length > 1

View File

@@ -9,50 +9,13 @@ module.exports =
class TextMateBundle
@grammarsByFileType: {}
@grammarsByScopeName: {}
@preferencesByScopeSelector: {}
@grammars: []
@load: (name)->
@load: (name) ->
bundle = new TextMateBundle(require.resolve(name))
for scopeSelector, preferences of bundle.getPreferencesByScopeSelector()
if @preferencesByScopeSelector[scopeSelector]?
_.extend(@preferencesByScopeSelector[scopeSelector], preferences)
else
@preferencesByScopeSelector[scopeSelector] = preferences
for grammar in bundle.grammars
@grammars.push(grammar)
for fileType in grammar.fileTypes
@grammarsByFileType[fileType] = grammar
@grammarsByScopeName[grammar.scopeName] = grammar
syntax.addGrammar(grammar) for grammar in bundle.grammars
bundle
@grammarForFilePath: (filePath) ->
return @grammarsByFileType["txt"] unless filePath
extension = fs.extension(filePath)?[1...]
if filePath and extension.length == 0
extension = fs.base(filePath)
@grammarsByFileType[extension] or @grammarByShebang(filePath) or @grammarByFileTypeSuffix(filePath) or @grammarsByFileType["txt"]
@grammarByFileTypeSuffix: (filePath) ->
for fileType, grammar of @grammarsByFileType
return grammar if _.endsWith(filePath, fileType)
@grammarByShebang: (filePath) ->
try
fileContents = fs.read(filePath)
catch e
null
_.find @grammars, (grammar) -> grammar.firstLineRegex?.test(fileContents)
@grammarForScopeName: (scopeName) ->
@grammarsByScopeName[scopeName]
grammars: null
constructor: (@path) ->
@@ -64,25 +27,5 @@ class TextMateBundle
catch e
console.warn "Failed to load grammar at path '#{syntaxPath}'", e
getPreferencesByScopeSelector: ->
return {} unless fs.exists(@getPreferencesPath())
preferencesByScopeSelector = {}
for preferencePath in fs.list(@getPreferencesPath())
plist.parseString fs.read(preferencePath), (e, data) ->
if e
console.warn "Failed to parse preference at path '#{preferencePath}'", e
else
{ scope, settings } = data[0]
return unless scope
for scope in scope.split(',')
scope = $.trim(scope)
continue unless scope
preferencesByScopeSelector[scope] = _.extend(preferencesByScopeSelector[scope] ? {}, settings)
preferencesByScopeSelector
getSyntaxesPath: ->
fs.join(@path, "Syntaxes")
getPreferencesPath: ->
fs.join(@path, "Preferences")

View File

@@ -72,8 +72,7 @@ class TextMateGrammar
else if name == "$self" or name == "$base"
@initialRule
else
TextMateBundle = require 'text-mate-bundle'
TextMateBundle.grammarForScopeName(name)?.initialRule
syntax.grammarForScopeName(name)?.initialRule
class Rule
grammar: null

View File

@@ -71,4 +71,4 @@ class TextMatePackage extends Package
{ editor: editorProperties } if _.size(editorProperties) > 0
cssSelectorFromScopeSelector: (scopeSelector) ->
@constructor.cssSelectorFromScopeSelector(scopeSelector)
@constructor.cssSelectorFromScopeSelector(scopeSelector)