Make useExperimentalModernTreeSitter the default…

…and create `useLegacyTreeSitter` for those who want to opt into the previous default behavior.

(Legacy Tree-sitter grammars will soon be removed, but this is a step toward that future!)
This commit is contained in:
Andrew Dupont 2024-01-06 15:46:53 -08:00
parent fd908ca0b7
commit 76ac2cf81c
21 changed files with 106 additions and 57 deletions

View File

@ -33,7 +33,7 @@ const packagesToTest = {
const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms)); const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms));
const whenEditorReady = function(editor) { const whenEditorReady = function (editor) {
const languageMode = editor.getBuffer().getLanguageMode(); const languageMode = editor.getBuffer().getLanguageMode();
if (!languageMode.constructor.name.includes('TreeSitter')) { if (!languageMode.constructor.name.includes('TreeSitter')) {
return Promise.resolve(); return Promise.resolve();
@ -105,8 +105,6 @@ describe("CSS property name and value autocompletions", async () => {
await atom.workspace.open(packagesToTest[packageLabel].file); await atom.workspace.open(packagesToTest[packageLabel].file);
editor = atom.workspace.getActiveTextEditor(); editor = atom.workspace.getActiveTextEditor();
await whenEditorReady(editor); await whenEditorReady(editor);
console.warn('USING TREE SITTER?!?', packageLabel, meta.useTreeSitter);
atom.config.set('core.useExperimentalModernTreeSitter', meta.useTreeSitter ?? false);
atom.config.set('core.useTreeSitterParsers', meta.useTreeSitter ?? false); atom.config.set('core.useTreeSitterParsers', meta.useTreeSitter ?? false);
}); });
@ -738,7 +736,7 @@ div:nth {
}) })
); );
Object.keys(packagesToTest).forEach(function(packageLabel) { Object.keys(packagesToTest).forEach(function (packageLabel) {
if (packagesToTest[packageLabel].name !== 'language-css') { if (packagesToTest[packageLabel].name !== 'language-css') {
describe(`${packageLabel} files`, async () => { describe(`${packageLabel} files`, async () => {
beforeEach(async () => { beforeEach(async () => {

View File

@ -0,0 +1,12 @@
module.exports = {
env: { jasmine: true },
globals: {
"waitsForPromise": true
},
rules: {
"node/no-unpublished-require": "off",
"node/no-extraneous-require": "off",
"no-unused-vars": "off",
"no-empty": "off"
}
};

View File

@ -26,10 +26,10 @@ module.exports = class GrammarListView {
let badgeColor = 'badge-success'; let badgeColor = 'badge-success';
let badgeText = 'Tree-sitter'; let badgeText = 'Tree-sitter';
if (isExperimentalTreeSitterMode()) { if (isLegacyTreeSitterMode()) {
badgeColor = isModernTreeSitter(grammar) ? badgeColor = isLegacyTreeSitter(grammar) ?
'badge-success' : 'badge-warning'; 'badge-success' : 'badge-warning';
badgeText = isModernTreeSitter(grammar) ? badgeText = isLegacyTreeSitter(grammar) ?
'Tree-sitter' : 'Legacy Tree-sitter'; 'Tree-sitter' : 'Legacy Tree-sitter';
} }
@ -118,13 +118,15 @@ module.exports = class GrammarListView {
return grammar !== atom.grammars.nullGrammar && grammar.name; return grammar !== atom.grammars.nullGrammar && grammar.name;
}); });
// Don't show modern tree-sitter grammars in the selector unless the user // Don't show legacy Tree-sitter grammars in the selector unless the user
// has opted into it. // has opted into it.
if (!isExperimentalTreeSitterMode()) { if (!isLegacyTreeSitterMode()) {
grammars = grammars.filter(grammar => !isModernTreeSitter(grammar)); grammars = grammars.filter(grammar => !isLegacyTreeSitter(grammar));
} }
if (atom.config.get('grammar-selector.hideDuplicateTextMateGrammars')) { if (atom.config.get('grammar-selector.hideDuplicateTextMateGrammars')) {
// Filter out all TextMate grammars for which there is a Tree-sitter
// grammar with the exact same name.
const blacklist = new Set(); const blacklist = new Set();
grammars.forEach(grammar => { grammars.forEach(grammar => {
if (isTreeSitter(grammar)) { if (isTreeSitter(grammar)) {
@ -155,24 +157,24 @@ module.exports = class GrammarListView {
function getLanguageModeConfig() { function getLanguageModeConfig() {
let isTreeSitterMode = atom.config.get('core.useTreeSitterParsers'); let isTreeSitterMode = atom.config.get('core.useTreeSitterParsers');
let isExperimental = atom.config.get('core.useExperimentalModernTreeSitter'); let isLegacy = atom.config.get('core.useLegacyTreeSitter');
if (!isTreeSitterMode) return 'textmate'; if (!isTreeSitterMode) return 'textmate';
return isExperimental ? 'wasm-tree-sitter' : 'node-tree-sitter'; return isLegacy ? 'node-tree-sitter' : 'wasm-tree-sitter';
} }
function isExperimentalTreeSitterMode() { function isLegacyTreeSitterMode() {
return getLanguageModeConfig() === 'wasm-tree-sitter'; return getLanguageModeConfig() === 'node-tree-sitter';
} }
function isTreeSitter(grammar) { function isTreeSitter(grammar) {
return isOldTreeSitter(grammar) || isModernTreeSitter(grammar); return isLegacyTreeSitter(grammar) || isModernTreeSitter(grammar);
} }
function isModernTreeSitter(grammar) { function isModernTreeSitter(grammar) {
return grammar.constructor.name === 'WASMTreeSitterGrammar'; return grammar.constructor.name === 'WASMTreeSitterGrammar';
} }
function isOldTreeSitter(grammar) { function isLegacyTreeSitter(grammar) {
return grammar.constructor.name === 'TreeSitterGrammar'; return grammar.constructor.name === 'TreeSitterGrammar';
} }
@ -183,6 +185,6 @@ function compareGrammarType(a, b) {
function getGrammarScore(grammar) { function getGrammarScore(grammar) {
let languageParser = getLanguageModeConfig(); let languageParser = getLanguageModeConfig();
if (isModernTreeSitter(grammar)) { return -2; } if (isModernTreeSitter(grammar)) { return -2; }
if (isOldTreeSitter(grammar)) { return -1; } if (isLegacyTreeSitter(grammar)) { return -1; }
return languageParser === 'textmate' ? -3 : 0; return languageParser === 'textmate' ? -3 : 0;
} }

View File

@ -0,0 +1,8 @@
module.exports = {
env: { jasmine: true },
rules: {
"node/no-unpublished-require": "off",
"no-unused-vars": "off",
"no-empty": "off"
}
};

View File

@ -3,9 +3,9 @@ const SelectListView = require('atom-select-list');
function setConfigForLanguageMode(mode) { function setConfigForLanguageMode(mode) {
let useTreeSitterParsers = mode !== 'textmate'; let useTreeSitterParsers = mode !== 'textmate';
let useExperimentalModernTreeSitter = mode === 'wasm-tree-sitter'; let useLegacyTreeSitter = mode === 'node-tree-sitter';
atom.config.set('core.useTreeSitterParsers', useTreeSitterParsers); atom.config.set('core.useTreeSitterParsers', useTreeSitterParsers);
atom.config.set('core.useExperimentalModernTreeSitter', useExperimentalModernTreeSitter); atom.config.set('core.useLegacyTreeSitter', useLegacyTreeSitter);
} }
describe('GrammarSelector', () => { describe('GrammarSelector', () => {

View File

@ -0,0 +1,9 @@
module.exports = {
env: { jasmine: true },
rules: {
"node/no-unpublished-require": "off",
"node/no-extraneous-require": "off",
"no-unused-vars": "off",
"no-empty": "off"
}
};

View File

@ -4,7 +4,6 @@ describe("Clojure grammar", function() {
beforeEach(function() { beforeEach(function() {
atom.config.set('core.useTreeSitterParsers', false); atom.config.set('core.useTreeSitterParsers', false);
atom.config.set('core.useExperimentalModernTreeSitter', false);
waitsForPromise(() => atom.packages.activatePackage("language-clojure")); waitsForPromise(() => atom.packages.activatePackage("language-clojure"));

View File

@ -3,9 +3,7 @@ const path = require('path');
function setConfigForLanguageMode(mode) { function setConfigForLanguageMode(mode) {
let useTreeSitterParsers = mode !== 'textmate'; let useTreeSitterParsers = mode !== 'textmate';
let useExperimentalModernTreeSitter = mode === 'modern-tree-sitter';
atom.config.set('core.useTreeSitterParsers', useTreeSitterParsers); atom.config.set('core.useTreeSitterParsers', useTreeSitterParsers);
atom.config.set('core.useExperimentalModernTreeSitter', useExperimentalModernTreeSitter);
} }
describe('Clojure grammars', () => { describe('Clojure grammars', () => {

View File

@ -3,7 +3,7 @@ const dedent = require('dedent');
describe('Tree-sitter HTML grammar', () => { describe('Tree-sitter HTML grammar', () => {
beforeEach(async () => { beforeEach(async () => {
atom.config.set('core.useTreeSitterParsers', true); atom.config.set('core.useTreeSitterParsers', true);
atom.config.set('core.useExperimentalModernTreeSitter', false); atom.config.set('core.useLegacyTreeSitter', true);
await atom.packages.activatePackage('language-html'); await atom.packages.activatePackage('language-html');
}); });

View File

@ -0,0 +1,12 @@
module.exports = {
env: { jasmine: true },
globals: {
waitsForPromise: true
},
rules: {
"node/no-unpublished-require": "off",
"node/no-extraneous-require": "off",
"no-unused-vars": "off",
"no-empty": "off"
}
};

View File

@ -8,7 +8,7 @@ describe('Tree-sitter based Java grammar', function() {
beforeEach(function() { beforeEach(function() {
atom.config.set('core.useTreeSitterParsers', true); atom.config.set('core.useTreeSitterParsers', true);
atom.config.set('core.useExperimentalModernTreeSitter', false); atom.config.set('core.useLegacyTreeSitter', true);
waitsForPromise(() => atom.packages.activatePackage('language-java')); waitsForPromise(() => atom.packages.activatePackage('language-java'));

View File

@ -0,0 +1,15 @@
module.exports = {
env: { jasmine: true },
globals: {
waitsForPromise: true,
runGrammarTests: true,
runFoldsTests: true,
normalizeTreeSitterTextData: true
},
rules: {
"node/no-unpublished-require": "off",
"node/no-extraneous-require": "off",
"no-unused-vars": "off",
"no-empty": "off"
}
};

View File

@ -16,7 +16,7 @@ describe('Ruby grammars', () => {
xit('tokenizes the editor using node tree-sitter parser', async () => { xit('tokenizes the editor using node tree-sitter parser', async () => {
atom.config.set('core.useTreeSitterParsers', true); atom.config.set('core.useTreeSitterParsers', true);
atom.config.set('core.useExperimentalModernTreeSitter', false); atom.config.set('core.useLegacyTreeSitter', true);
await runGrammarTests(path.join(__dirname, 'fixtures', 'textmate-grammar.rb'), /#/) await runGrammarTests(path.join(__dirname, 'fixtures', 'textmate-grammar.rb'), /#/)
}); });
}); });

View File

@ -3,7 +3,7 @@ const dedent = require('dedent');
describe('Tree-sitter Ruby grammar', () => { describe('Tree-sitter Ruby grammar', () => {
beforeEach(async () => { beforeEach(async () => {
atom.config.set('core.useTreeSitterParsers', true); atom.config.set('core.useTreeSitterParsers', true);
atom.config.set('core.useExperimentalModernTreeSitter', false); atom.config.set('core.useLegacyTreeSitter', true);
await atom.packages.activatePackage('language-ruby'); await atom.packages.activatePackage('language-ruby');
}); });

View File

@ -7,7 +7,6 @@ xdescribe('WASM Tree-sitter Ruby grammar', () => {
beforeEach(async () => { beforeEach(async () => {
await atom.packages.activatePackage('language-ruby'); await atom.packages.activatePackage('language-ruby');
atom.config.set('core.useTreeSitterParsers', true); atom.config.set('core.useTreeSitterParsers', true);
atom.config.set('core.useExperimentalModernTreeSitter', true);
}); });
it('tokenizes symbols', async () => { it('tokenizes symbols', async () => {

View File

@ -11,9 +11,9 @@ const { OnigScanner } = SecondMate;
// Expects one of `textmate`, `node-tree-sitter`, or `wasm-tree-sitter`. // Expects one of `textmate`, `node-tree-sitter`, or `wasm-tree-sitter`.
function setConfigForLanguageMode(mode, options = {}) { function setConfigForLanguageMode(mode, options = {}) {
let useTreeSitterParsers = mode !== 'textmate'; let useTreeSitterParsers = mode !== 'textmate';
let useExperimentalModernTreeSitter = mode === 'wasm-tree-sitter'; let useLegacyTreeSitter = mode === 'node-tree-sitter';
atom.config.set('core.useTreeSitterParsers', useTreeSitterParsers, options); atom.config.set('core.useTreeSitterParsers', useTreeSitterParsers, options);
atom.config.set('core.useExperimentalModernTreeSitter', useExperimentalModernTreeSitter, options); atom.config.set('core.useLegacyTreeSitter', useLegacyTreeSitter, options);
} }
describe('GrammarRegistry', () => { describe('GrammarRegistry', () => {

View File

@ -93,7 +93,7 @@ function rangeFromDescriptor(rawRange) {
} }
describe('ScopeResolver', () => { describe('ScopeResolver', () => {
let editor, buffer, grammar, scopeResolver; let editor, buffer, grammar;
beforeEach(async () => { beforeEach(async () => {
grammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, jsConfig); grammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, jsConfig);
@ -101,7 +101,6 @@ describe('ScopeResolver', () => {
buffer = editor.getBuffer(); buffer = editor.getBuffer();
atom.grammars.addGrammar(grammar); atom.grammars.addGrammar(grammar);
atom.config.set('core.useTreeSitterParsers', true); atom.config.set('core.useTreeSitterParsers', true);
atom.config.set('core.useExperimentalModernTreeSitter', true);
}); });
afterEach(() => { afterEach(() => {
@ -126,7 +125,7 @@ describe('ScopeResolver', () => {
let { scopeResolver, captures } = await getAllCaptures(grammar, languageMode); let { scopeResolver, captures } = await getAllCaptures(grammar, languageMode);
for (let capture of captures) { for (let capture of captures) {
let { node, name } = capture; let { node } = capture;
let range = scopeResolver.store(capture); let range = scopeResolver.store(capture);
expect(stringForNodeRange(range)) expect(stringForNodeRange(range))
.toBe(stringForNodeRange(node)); .toBe(stringForNodeRange(node));

View File

@ -42,7 +42,7 @@ describe('TreeSitterLanguageMode', () => {
buffer = editor.getBuffer(); buffer = editor.getBuffer();
editor.displayLayer.reset({ foldCharacter: '…' }); editor.displayLayer.reset({ foldCharacter: '…' });
atom.config.set('core.useTreeSitterParsers', true); atom.config.set('core.useTreeSitterParsers', true);
atom.config.set('core.useExperimentalModernTreeSitter', false); atom.config.set('core.useLegacyTreeSitter', true);
}); });
describe('highlighting', () => { describe('highlighting', () => {

View File

@ -66,7 +66,6 @@ describe('WASMTreeSitterLanguageMode', () => {
buffer = editor.getBuffer(); buffer = editor.getBuffer();
editor.displayLayer.reset({ foldCharacter: '…' }); editor.displayLayer.reset({ foldCharacter: '…' });
atom.config.set('core.useTreeSitterParsers', true); atom.config.set('core.useTreeSitterParsers', true);
atom.config.set('core.useExperimentalModernTreeSitter', true);
}); });
afterEach(() => { afterEach(() => {

View File

@ -360,13 +360,14 @@ const configSchema = {
useTreeSitterParsers: { useTreeSitterParsers: {
type: 'boolean', type: 'boolean',
default: true, default: true,
title: 'Use Tree-sitter Parsers',
description: 'Use Tree-sitter parsers for supported languages.' description: 'Use Tree-sitter parsers for supported languages.'
}, },
useExperimentalModernTreeSitter: { useLegacyTreeSitter: {
type: 'boolean', type: 'boolean',
default: false, default: false,
title: 'Use Modern Tree-Sitter Implementation', title: 'Use legacy Tree-sitter Implementation',
description: 'Experimental: Use the new query-file-based Tree-sitter system instead of the legacy system from Atom. (This system will eventually replace the legacy system.) Has no effect unless "Use Tree Sitter Parsers" is also checked.' description: 'Opt into the legacy Atom Tree-sitter system instead of the modern system added by Pulsar. (This legacy system will soon be removed.) Has no effect unless “Use Tree-sitter Parsers” is also checked.'
}, },
colorProfile: { colorProfile: {
description: description:

View File

@ -46,7 +46,7 @@ module.exports = class GrammarRegistry {
this.textmateRegistry.onDidUpdateGrammar(grammarAddedOrUpdated); this.textmateRegistry.onDidUpdateGrammar(grammarAddedOrUpdated);
let onLanguageModeChange = () => { let onLanguageModeChange = () => {
this.grammarScoresByBuffer.forEach((score, buffer) => { this.grammarScoresByBuffer.forEach((_score, buffer) => {
if (!this.languageOverridesByBufferId.has(buffer.id)) { if (!this.languageOverridesByBufferId.has(buffer.id)) {
this.autoAssignLanguageMode(buffer); this.autoAssignLanguageMode(buffer);
} }
@ -55,7 +55,7 @@ module.exports = class GrammarRegistry {
this.subscriptions.add( this.subscriptions.add(
this.config.onDidChange('core.useTreeSitterParsers', onLanguageModeChange), this.config.onDidChange('core.useTreeSitterParsers', onLanguageModeChange),
this.config.onDidChange('core.useExperimentalModernTreeSitter', onLanguageModeChange) this.config.onDidChange('core.useLegacyTreeSitter', onLanguageModeChange)
); );
} }
@ -253,10 +253,10 @@ module.exports = class GrammarRegistry {
scope = new ScopeDescriptor({ scopes: [scope] }) scope = new ScopeDescriptor({ scopes: [scope] })
} }
let useTreeSitterParsers = this.config.get('core.useTreeSitterParsers', { scope }); let useTreeSitterParsers = this.config.get('core.useTreeSitterParsers', { scope });
let useExperimentalModernTreeSitter = this.config.get('core.useExperimentalModernTreeSitter', { scope }); let useLegacyTreeSitter = this.config.get('core.useLegacyTreeSitter', { scope });
if (!useTreeSitterParsers) return 'textmate'; if (!useTreeSitterParsers) return 'textmate';
return useExperimentalModernTreeSitter ? 'wasm-tree-sitter' : 'node-tree-sitter'; return useLegacyTreeSitter ? 'node-tree-sitter' : 'wasm-tree-sitter';
} }
// Extended: Returns a {Number} representing how well the grammar matches the // Extended: Returns a {Number} representing how well the grammar matches the
@ -288,19 +288,15 @@ module.exports = class GrammarRegistry {
if (isNewTreeSitter) { if (isNewTreeSitter) {
if (parserConfig === 'wasm-tree-sitter') { if (parserConfig === 'wasm-tree-sitter') {
score += 0.1; score += 0.1;
} else {
// Never automatically switch to a new Tree-sitter grammar unless the
// user has opted into the experimental setting.
score = -Infinity;
} }
} else if (isOldTreeSitter) { } else if (isOldTreeSitter) {
if (parserConfig === 'node-tree-sitter') { if (parserConfig === 'node-tree-sitter') {
score += 0.1; score += 0.1;
} else if (parserConfig === 'wasm-tree-sitter') { } else if (parserConfig === 'wasm-tree-sitter') {
// In experimental new-tree-sitter mode, we still would rather fall // If `useLegacyTreeSitter` isn't checked, we probably still prefer a
// back to an old-tree-sitter grammar than a TM grammar. Bump the // legacy Tree-sitter grammar over a TextMate-style grammar. Bump the
// score, but just a bit less than we'd bump it if this were a // score, but just a bit less than we'd bump it if this were a
// new-tree-sitter grammar. // modern Tree-sitter grammar.
score += 0.09; score += 0.09;
} }
} }
@ -770,17 +766,17 @@ module.exports = class GrammarRegistry {
let result = this.textmateRegistry.getGrammars(); let result = this.textmateRegistry.getGrammars();
if (!(params && params.includeTreeSitter)) return result; if (!(params && params.includeTreeSitter)) return result;
let includeModernTreeSitterGrammars = let includeLegacyTreeSitterGrammars =
atom.config.get('core.useExperimentalModernTreeSitter') === true; atom.config.get('core.useLegacyTreeSitter') === true;
const tsGrammars = Object.values(this.treeSitterGrammarsById) let modernTsGrammars = Object.values(this.wasmTreeSitterGrammarsById)
.filter(g => g.scopeName); .filter(g => g.scopeName);
result = result.concat(tsGrammars); result = result.concat(modernTsGrammars);
if (includeModernTreeSitterGrammars) { if (includeLegacyTreeSitterGrammars) {
let modernTsGrammars = Object.values(this.wasmTreeSitterGrammarsById) const legacyTsGrammars = Object.values(this.treeSitterGrammarsById)
.filter(g => g.scopeName); .filter(g => g.scopeName);
result = result.concat(modernTsGrammars); result = result.concat(legacyTsGrammars);
} }
return result; return result;
@ -790,8 +786,10 @@ module.exports = class GrammarRegistry {
return this.textmateRegistry.scopeForId(id); return this.textmateRegistry.scopeForId(id);
} }
// TODO: why is this being used? Can we remove it soon? // Match up a language string (of the sort generated by an injection point)
treeSitterGrammarForLanguageString(languageString, type = 'original') { // with a grammar. Checks the `injectionRegex` property on grammars and
// returns the one with the longest match.
treeSitterGrammarForLanguageString(languageString, type = 'wasm') {
let longestMatchLength = 0; let longestMatchLength = 0;
let grammarWithLongestMatch = null; let grammarWithLongestMatch = null;
let table = type === 'original' ? this.treeSitterGrammarsById : this.wasmTreeSitterGrammarsById; let table = type === 'original' ? this.treeSitterGrammarsById : this.wasmTreeSitterGrammarsById;