1+ const { CompositeDisposable } = require ( 'event-kit' ) ;
12const { Point } = require ( 'text-buffer' ) ;
23const ScopeDescriptor = require ( './scope-descriptor' ) ;
34
@@ -16,7 +17,7 @@ function comparePoints(a, b) {
1617 }
1718}
1819
19- function rangeSpecToString ( range ) {
20+ function rangeSpecToString ( range ) {
2021 let [ sp , ep ] = [ range . startPosition , range . endPosition ] ;
2122 return `(${ sp . row } , ${ sp . column } ) - (${ ep . row } , ${ ep . column } )` ;
2223}
@@ -63,6 +64,67 @@ function interpretPossibleKeyValuePair(rawValue, coerceValue = false) {
6364 return [ key , value ] ;
6465}
6566
67+ // `ScopeResolver`s can share a config cache if they have the same grammar.
68+ // There are many such `ScopeResolver`s (because there are many such
69+ // `LanguageLayer`s), and this consolidation cuts down on the number of
70+ // `onDidChange` handlers (which are costly when observing all config values).
71+ class ConfigCache {
72+ constructor ( config ) {
73+ this . subscriptions = new CompositeDisposable ( ) ;
74+ this . cachesByGrammar = new Map ( ) ;
75+ this . config = config ;
76+
77+ this . subscriptions . add (
78+ this . config . onDidChange ( ( ) => this . clearAll ( ) ) ,
79+ atom . grammars . onDidAddGrammar ( ( ) => this . clearAll ( ) ) ,
80+ atom . grammars . onDidUpdateGrammar ( ( ) => this . clearAll ( ) )
81+ ) ;
82+ }
83+
84+ dispose ( ) {
85+ this . subscriptions . dispose ( ) ;
86+ }
87+
88+ clearAll ( ) {
89+ for ( let cache of this . cachesByGrammar . values ( ) ) {
90+ cache . clear ( ) ;
91+ }
92+ }
93+
94+ getCacheForGrammar ( grammar ) {
95+ let { scopeName } = grammar ;
96+ // We key on the scope name rather than the grammar instance. We need to be
97+ // able to iterate over grammars, so we can't use a `WeakSet` here, and we
98+ // have no lifecycle event to keep the map from getting stale.
99+ //
100+ // To prevent two different incarnations of the same grammar (after
101+ // disabling and reenabling) from incorrectly sharing a cache, we clear all
102+ // caches whenever grammars are added or updated.
103+ let cache = this . cachesByGrammar . get ( scopeName ) ;
104+ if ( ! cache ) {
105+ cache = new Map ( ) ;
106+ this . cachesByGrammar . set ( scopeName , cache ) ;
107+ }
108+ return cache ;
109+ }
110+ }
111+
112+ // We can technically have more than one configuration object, though in
113+ // practice this will only point to `atom.config`. We do it this way for ease
114+ // of testing (e.g., if a test mocks a config object) and to avoid silly hacks.
115+ ConfigCache . CACHES_FOR_CONFIG_OBJECTS = new Map ( ) ;
116+
117+ ConfigCache . forConfig = ( config ) => {
118+ let { CACHES_FOR_CONFIG_OBJECTS } = ConfigCache ;
119+ let configCache = CACHES_FOR_CONFIG_OBJECTS . get ( config ) ;
120+ if ( ! configCache ) {
121+ configCache = new ConfigCache ( config ) ;
122+ CACHES_FOR_CONFIG_OBJECTS . set ( config , configCache ) ;
123+ }
124+ return configCache ;
125+ } ;
126+
127+
66128// A data structure for storing scope information while processing capture
67129// data. The data is reset in between each task.
68130//
@@ -86,10 +148,8 @@ class ScopeResolver {
86148 this . rangeData = new Map ;
87149 this . pointKeyCache = new Map ;
88150 this . patternCache = new Map ;
89- this . configCache = new Map ;
90- this . configSubscription = this . config . onDidChange ( ( ) => {
91- this . configCache . clear ( ) ;
92- } ) ;
151+ this . configCache = ConfigCache . forConfig ( this . config )
152+ . getCacheForGrammar ( this . grammar ) ;
93153 }
94154
95155 getOrCompilePattern ( pattern ) {
@@ -477,8 +537,6 @@ class ScopeResolver {
477537 this . reset ( ) ;
478538 this . patternCache . clear ( ) ;
479539 this . pointKeyCache . clear ( ) ;
480- this . configCache . clear ( ) ;
481- this . configSubscription . dispose ( ) ;
482540 }
483541
484542 * [ Symbol . iterator ] ( ) {
0 commit comments