[styleguide] Fix error when styleguide is shown…

…before certain grammars are loaded.
This commit is contained in:
Andrew Dupont 2023-07-18 12:21:42 -07:00
parent 1f41fccfd6
commit 5c00a36c30
4 changed files with 144 additions and 9 deletions

View File

@ -14,6 +14,11 @@ module.exports = {
ecmaVersion: "latest"
},
rules: {
"space-before-function-paren": ["error", {
anonymous: "always",
asyncArrow: "always",
named: "never"
}],
"node/no-unpublished-require": [
"error",
{

View File

@ -1,17 +1,57 @@
const {TextEditor} = require('atom')
const PROMISES_BY_SCOPE_NAME = new Map();
module.exports =
class CodeBlock {
constructor (props) {
constructor(props) {
this.editor = new TextEditor({readonly: true, keyboardInputEnabled: false})
this.element = document.createElement('div')
this.element.appendChild(this.editor.getElement())
atom.grammars.assignLanguageMode(this.editor, props.grammarScopeName)
this.update(props)
this.whenGrammarAdded(props.grammarScopeName)
.then(() => {
// We don't use the returned grammar here; instead we trigger logic
// that matches up the grammars present right now with the user's
// stated preferences for language mode (TextMate vs Tree-sitter).
//
// In other words: “Once any grammar for language X loads, wait another
// second, then pick the language X grammar that best fits our needs.”
atom.grammars.assignLanguageMode(this.editor, props.grammarScopeName)
})
}
update ({cssClass, code}) {
update({cssClass, code}) {
this.editor.setText(code)
this.element.classList.add(cssClass)
}
whenGrammarAdded(scopeName) {
// Lots of these will fire at once for the same scope name; we want them
// all to use the same promise.
if (PROMISES_BY_SCOPE_NAME.has(scopeName)) {
return PROMISES_BY_SCOPE_NAME.get(scopeName)
}
let grammar = atom.grammars.grammarForId(scopeName);
if (grammar) return Promise.resolve(grammar);
let promise = new Promise(resolve => {
let disposable = atom.grammars.onDidAddGrammar(grammar => {
if (grammar?.scopeName !== scopeName) return
disposable.dispose()
// If we resolve immediately, we might not get the right grammar for
// the user's preferred language mode setting. A short pause will allow
// all the grammars of a given language time to activate.
//
// This is how we balance assigning the grammar for the “wrong”
// language mode… versus waiting for another one that may never arrive.
setTimeout(resolve(grammar), 1000)
})
})
PROMISES_BY_SCOPE_NAME.set(scopeName, promise)
return promise
}
}

View File

@ -137,6 +137,22 @@ describe('GrammarRegistry', () => {
grammarRegistry.grammarForId('source.js') instanceof SecondMate.Grammar
).toBe(true);
});
it('never returns a stub object before a grammar has loaded', () => {
grammarRegistry.addInjectionPoint('source.js', {
type: 'some_node_type',
language() {
return 'some_language_name';
},
content(node) {
return node;
}
});
expect(
grammarRegistry.grammarForId('source.js')
).toBe(undefined);
});
});
describe('.autoAssignLanguageMode(buffer)', () => {
@ -878,10 +894,22 @@ describe('GrammarRegistry', () => {
}
};
let addCallbackFired;
let updateCallbackFired;
let addCallbackDisposable;
let updateCallbackDisposable;
beforeEach(() => {
addCallbackFired = false;
updateCallbackFired = false;
setConfigForLanguageMode('node-tree-sitter');
});
afterEach(() => {
addCallbackDisposable?.dispose();
updateCallbackDisposable?.dispose();
});
it('adds an injection point to the grammar with the given id', async () => {
await atom.packages.activatePackage('language-javascript');
atom.grammars.addInjectionPoint('javascript', injectionPoint);
@ -889,12 +917,38 @@ describe('GrammarRegistry', () => {
expect(grammar.injectionPoints).toContain(injectionPoint);
});
it('fires the onDidUpdateGrammar callback', async () => {
let callbackDisposable = atom.grammars.onDidUpdateGrammar((grammar) => {
if (grammar.scopeName === 'source.js') {
updateCallbackFired = true;
}
});
await atom.packages.activatePackage('language-javascript');
atom.grammars.addInjectionPoint('source.js', injectionPoint);
expect(updateCallbackFired).toBe(true);
});
describe('when called before a grammar with the given id is loaded', () => {
it('adds the injection point once the grammar is loaded', async () => {
// Adding an injection point before a grammar loads should not trigger
// onDidUpdateGrammar at any point.
updateCallbackDisposable = atom.grammars.onDidUpdateGrammar((grammar) => {
if (!grammar.scopeName) {
updateCallbackFired = true;
}
});
// But onDidAddGrammar should be triggered when the grammar eventually
// loads.
addCallbackDisposable = atom.grammars.onDidAddGrammar((grammar) => {
if (grammar.scopeName === 'source.js') addCallbackFired = true;
});
atom.grammars.addInjectionPoint('javascript', injectionPoint);
await atom.packages.activatePackage('language-javascript');
const grammar = atom.grammars.grammarForId('javascript');
expect(grammar.injectionPoints).toContain(injectionPoint);
expect(updateCallbackFired).toBe(false);
expect(addCallbackFired).toBe(true);
});
});
});

View File

@ -2,7 +2,7 @@ const _ = require('underscore-plus');
const Grim = require('grim');
const CSON = require('season');
const SecondMate = require('second-mate');
const { Disposable, CompositeDisposable } = require('event-kit');
const { Disposable, CompositeDisposable, Emitter } = require('event-kit');
const TextMateLanguageMode = require('./text-mate-language-mode');
const NodeTreeSitterLanguageMode = require('./tree-sitter-language-mode');
const WASMTreeSitterLanguageMode = require('./wasm-tree-sitter-language-mode');
@ -26,6 +26,7 @@ module.exports = class GrammarRegistry {
maxTokensPerLine: 100,
maxLineLength: 1000
});
this.emitter = new Emitter();
this.clear();
}
@ -137,7 +138,7 @@ module.exports = class GrammarRegistry {
let grammar = null;
if (languageId != null) {
grammar = this.grammarForId(languageId);
if (!grammar) return false;
if (!grammar || !grammar.scopeName) return false;
this.languageOverridesByBufferId.set(buffer.id, languageId);
} else {
this.languageOverridesByBufferId.set(buffer.id, null);
@ -248,6 +249,9 @@ module.exports = class GrammarRegistry {
}
getLanguageParserForScope(scope) {
if (typeof scope === 'string') {
scope = new ScopeDescriptor({ scopes: [scope] })
}
let useTreeSitterParsers = this.config.get('core.useTreeSitterParsers', { scope });
let useExperimentalModernTreeSitter = this.config.get('core.useExperimentalModernTreeSitter', { scope });
@ -401,14 +405,28 @@ module.exports = class GrammarRegistry {
new ScopeDescriptor({ scopes: [languageId] })
);
let getTreeSitterGrammar = (table, languageId) => {
let grammar = table[languageId];
if (grammar?.scopeName) {
return grammar;
}
return null;
};
if (config === 'wasm-tree-sitter') {
return (
this.wasmTreeSitterGrammarsById[languageId] ||
getTreeSitterGrammar(
this.wasmTreeSitterGrammarsById,
languageId
) ||
this.textmateRegistry.grammarForScopeName(languageId)
);
} else if (config === 'node-tree-sitter') {
return (
this.treeSitterGrammarsById[languageId] ||
getTreeSitterGrammar(
this.treeSitterGrammarsById,
languageId
) ||
this.textmateRegistry.grammarForScopeName(languageId)
);
} else {
@ -502,7 +520,12 @@ module.exports = class GrammarRegistry {
//
// Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
onDidAddGrammar(callback) {
return this.textmateRegistry.onDidAddGrammar(callback);
let disposable = new CompositeDisposable();
disposable.add(
this.textmateRegistry.onDidAddGrammar(callback),
this.emitter.on('did-add-grammar', callback)
);
return disposable;
}
// Extended: Invoke the given callback when a grammar is updated due to a grammar
@ -513,7 +536,12 @@ module.exports = class GrammarRegistry {
//
// Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
onDidUpdateGrammar(callback) {
return this.textmateRegistry.onDidUpdateGrammar(callback);
let disposable = new CompositeDisposable();
disposable.add(
this.textmateRegistry.onDidUpdateGrammar(callback),
this.emitter.on('did-update-grammar', callback)
);
return disposable;
}
// Experimental: Specify a type of syntax node that may embed other languages.
@ -557,10 +585,16 @@ module.exports = class GrammarRegistry {
if (grammar) {
if (grammar.addInjectionPoint) {
grammar.addInjectionPoint(injectionPoint);
// This is a grammar that's already loaded — not just a stub. Editors
// that already use this grammar will want to know that we added an
// injection.
this.emitter.emit('did-update-grammar', grammar);
} else {
grammar.injectionPoints.push(injectionPoint);
}
grammarsToDispose.push(grammar);
} else {
table[grammarId] = { injectionPoints: [injectionPoint] }
}
@ -616,6 +650,7 @@ module.exports = class GrammarRegistry {
}
}
this.grammarAddedOrUpdated(grammar);
this.emitter.emit('did-add-grammar', grammar);
return new Disposable(() => this.removeGrammar(grammar));
} else if (grammar instanceof TreeSitterGrammar) {
const existingParams =
@ -628,6 +663,7 @@ module.exports = class GrammarRegistry {
}
}
this.grammarAddedOrUpdated(grammar);
this.emitter.emit('did-add-grammar', grammar);
return new Disposable(() => this.removeGrammar(grammar));
} else {
return this.textmateRegistry.addGrammar(grammar);