Update grammars when grammars they include are added/removed

If the Ruby on Rails grammar depends on HTML, but it isn't loaded, its
syntax highlighting won't include HTMl tokens. If we later load HTML,
we should update any buffer with the Rails grammar to reflect the
change. This commit changes grammars to memoize their initial rule and
repository. If an included grammar is added or removed, we clear the
memoized rules and emit a 'grammar-updated' event. Any tokenized
buffer that points to this grammar can then retokenize to reflect the
newly available/unavailable included grammar.
This commit is contained in:
Nathan Sobo
2013-04-20 19:25:03 -06:00
parent e6274b2f45
commit 9204836d70
6 changed files with 102 additions and 39 deletions

View File

@@ -12,3 +12,5 @@ class NullGrammar
tokenizeLine: (line) ->
{ tokens: [new Token(value: line, scopes: ['null-grammar.text.plain'])] }
grammarAddedOrRemoved: -> # no op

View File

@@ -31,13 +31,19 @@ class Syntax
{ deserializer: @constructor.name, @grammarOverridesByPath }
addGrammar: (grammar) ->
previousGrammars = new Array(@grammars...)
@grammars.push(grammar)
@grammarsByScopeName[grammar.scopeName] = grammar
@notifyOtherGrammars(previousGrammars, grammar.scopeName)
@trigger 'grammar-added', grammar
removeGrammar: (grammar) ->
_.remove(@grammars, grammar)
delete @grammarsByScopeName[grammar.scopeName]
@notifyOtherGrammars(@grammars, grammar.scopeName)
notifyOtherGrammars: (grammars, scopeName) ->
grammar.grammarAddedOrRemoved(scopeName) for grammar in grammars
setGrammarOverrideForPath: (path, scopeName) ->
@grammarOverridesByPath[path] = scopeName

View File

@@ -4,6 +4,7 @@ plist = require 'plist'
Token = require 'token'
{OnigRegExp, OnigScanner} = require 'oniguruma'
nodePath = require 'path'
EventEmitter = require 'event-emitter'
pathSplitRegex = new RegExp("[#{nodePath.sep}.]")
TextMateScopeSelector = require 'text-mate-scope-selector'
@@ -27,23 +28,46 @@ class TextMateGrammar
new TextMateGrammar(fsUtils.readObject(path))
name: null
rawPatterns: null
rawRepository: null
fileTypes: null
scopeName: null
repository: null
initialRule: null
firstLineRegex: null
includedGrammarScopes: null
maxTokensPerLine: 100
constructor: ({ @name, @fileTypes, @scopeName, injections, patterns, repository, @foldingStopMarker, firstLineMatch}) ->
@rawPatterns = patterns
@rawRepository = repository
@injections = new Injections(this, injections)
@initialRule = new Rule(this, {@scopeName, patterns})
@repository = {}
@firstLineRegex = new OnigRegExp(firstLineMatch) if firstLineMatch
@fileTypes ?= []
@includedGrammarScopes = []
for name, data of repository
data = {patterns: [data], tempName: name} if data.begin? or data.match?
@repository[name] = new Rule(this, data)
clearRules: ->
@initialRule = null
@repository = null
getInitialRule: ->
@initialRule ?= new Rule(this, {@scopeName, patterns: @rawPatterns})
getRepository: ->
@repository ?= do =>
repository = {}
for name, data of @rawRepository
data = {patterns: [data], tempName: name} if data.begin? or data.match?
repository[name] = new Rule(this, data)
repository
addIncludedGrammarScope: (scope) ->
@includedGrammarScopes.push(scope) unless _.include(@includedGrammarScopes, scope)
grammarAddedOrRemoved: (scopeName) =>
return unless _.include(@includedGrammarScopes, scopeName)
@clearRules()
@trigger 'grammar-updated'
getScore: (path, contents) ->
contents = fsUtils.read(path) if not contents? and fsUtils.isFile(path)
@@ -82,7 +106,7 @@ class TextMateGrammar
pathSuffix = pathComponents[-fileTypeComponents.length..-1]
_.isEqual(pathSuffix, fileTypeComponents)
tokenizeLine: (line, ruleStack=[@initialRule], firstLine=false) ->
tokenizeLine: (line, ruleStack=[@getInitialRule()], firstLine=false) ->
originalRuleStack = ruleStack
ruleStack = new Array(ruleStack...) # clone ruleStack
tokens = []
@@ -173,6 +197,8 @@ class Injections
scanners.push(scanner)
scanners
_.extend TextMateGrammar.prototype, EventEmitter
class Rule
grammar: null
scopeName: null
@@ -359,13 +385,14 @@ class Pattern
ruleForInclude: (baseGrammar, name) ->
if name[0] == "#"
@grammar.repository[name[1..]]
@grammar.getRepository()[name[1..]]
else if name == "$self"
@grammar.initialRule
@grammar.getInitialRule()
else if name == "$base"
baseGrammar.initialRule
baseGrammar.getInitialRule()
else
syntax.grammarForScopeName(name)?.initialRule
@grammar.addIncludedGrammarScope(name)
syntax.grammarForScopeName(name)?.getInitialRule()
getIncludedPatterns: (baseGrammar, included) ->
if @include