From 188d8f8604a9e485a2830ee8f1918ffcb709b17c Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 31 Dec 2012 18:11:11 -0600 Subject: [PATCH] Store grammars on the `syntax` global --- spec/app/syntax-spec.coffee | 18 ++++++++ spec/app/text-mate-bundle-spec.coffee | 25 ----------- spec/app/text-mate-grammar-spec.coffee | 23 +++++----- src/app/language-mode.coffee | 2 +- src/app/syntax.coffee | 38 +++++++++++++++- src/app/text-mate-bundle.coffee | 61 +------------------------- src/app/text-mate-grammar.coffee | 3 +- src/app/text-mate-package.coffee | 2 +- 8 files changed, 71 insertions(+), 101 deletions(-) diff --git a/spec/app/syntax-spec.coffee b/spec/app/syntax-spec.coffee index c3117fb46..60b65b0e9 100644 --- a/spec/app/syntax-spec.coffee +++ b/spec/app/syntax-spec.coffee @@ -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) diff --git a/spec/app/text-mate-bundle-spec.coffee b/spec/app/text-mate-bundle-spec.coffee index c67bc50cc..f2d89e882 100644 --- a/spec/app/text-mate-bundle-spec.coffee +++ b/spec/app/text-mate-bundle-spec.coffee @@ -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" diff --git a/spec/app/text-mate-grammar-spec.coffee b/spec/app/text-mate-grammar-spec.coffee index 96c860306..97e1ead87 100644 --- a/spec/app/text-mate-grammar-spec.coffee +++ b/spec/app/text-mate-grammar-spec.coffee @@ -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("
<%= User.find(2).full_name %>
") 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"] diff --git a/src/app/language-mode.coffee b/src/app/language-mode.coffee index 53fcfada7..023208bec 100644 --- a/src/app/language-mode.coffee +++ b/src/app/language-mode.coffee @@ -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) => diff --git a/src/app/syntax.coffee b/src/app/syntax.coffee index 5a71da15e..699c6cbd3 100644 --- a/src/app/syntax.coffee +++ b/src/app/syntax.coffee @@ -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 diff --git a/src/app/text-mate-bundle.coffee b/src/app/text-mate-bundle.coffee index 5d31499eb..7baf505f5 100644 --- a/src/app/text-mate-bundle.coffee +++ b/src/app/text-mate-bundle.coffee @@ -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") diff --git a/src/app/text-mate-grammar.coffee b/src/app/text-mate-grammar.coffee index 28c599528..f46c5f40a 100644 --- a/src/app/text-mate-grammar.coffee +++ b/src/app/text-mate-grammar.coffee @@ -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 diff --git a/src/app/text-mate-package.coffee b/src/app/text-mate-package.coffee index c28c63937..48feae536 100644 --- a/src/app/text-mate-package.coffee +++ b/src/app/text-mate-package.coffee @@ -71,4 +71,4 @@ class TextMatePackage extends Package { editor: editorProperties } if _.size(editorProperties) > 0 cssSelectorFromScopeSelector: (scopeSelector) -> - @constructor.cssSelectorFromScopeSelector(scopeSelector) \ No newline at end of file + @constructor.cssSelectorFromScopeSelector(scopeSelector)