mirror of
https://github.com/pulsar-edit/pulsar.git
synced 2024-08-16 22:50:22 +03:00
Merge pull request #941 from savetheclocktower/tree-sitter-march
Tree-sitter rolling fixes: 1.115 edition
This commit is contained in:
commit
1481d392bd
@ -4,6 +4,13 @@ module.exports = class GrammarListView {
|
||||
constructor() {
|
||||
this.autoDetect = { name: 'Auto Detect' };
|
||||
|
||||
this.configSubscription = atom.config.observe(
|
||||
'grammar-selector.hideDuplicateTextMateGrammars',
|
||||
(value) => {
|
||||
this.hideDuplicateGrammars = value
|
||||
}
|
||||
);
|
||||
|
||||
this.selectListView = new SelectListView({
|
||||
itemsClassList: ['mark-active'],
|
||||
items: [],
|
||||
@ -14,25 +21,20 @@ module.exports = class GrammarListView {
|
||||
if (grammar === this.currentGrammar) {
|
||||
element.classList.add('active');
|
||||
}
|
||||
element.classList.add('grammar-item');
|
||||
element.textContent = grammarName;
|
||||
element.dataset.grammar = grammarName;
|
||||
|
||||
const div = document.createElement('div');
|
||||
div.classList.add('pull-right');
|
||||
|
||||
if (isTreeSitter(grammar)) {
|
||||
if (!this.hideDuplicateGrammars) {
|
||||
// When we show all grammars, we should add a badge to each grammar
|
||||
// to distinguish them from one another in the list.
|
||||
const parser = document.createElement('span');
|
||||
|
||||
let badgeColor = 'badge-success';
|
||||
let badgeText = 'Tree-sitter';
|
||||
|
||||
if (isLegacyTreeSitterMode()) {
|
||||
// Color the legacy badge green to represent the user's preference.
|
||||
badgeColor = isLegacyTreeSitter(grammar) ?
|
||||
'badge-success' : 'badge-warning';
|
||||
badgeText = isLegacyTreeSitter(grammar) ?
|
||||
'Legacy Tree-sitter' : 'Modern Tree-sitter';
|
||||
}
|
||||
let badgeText = getBadgeTextForGrammar(grammar);
|
||||
let badgeColor = getBadgeColorForGrammar(grammar);
|
||||
|
||||
parser.classList.add(
|
||||
'grammar-selector-parser',
|
||||
@ -40,10 +42,12 @@ module.exports = class GrammarListView {
|
||||
badgeColor
|
||||
);
|
||||
parser.textContent = badgeText;
|
||||
parser.setAttribute(
|
||||
'title',
|
||||
'(Recommended) A faster parser with improved syntax highlighting & code navigation support.'
|
||||
);
|
||||
if (isModernTreeSitter(grammar)) {
|
||||
parser.setAttribute(
|
||||
'title',
|
||||
'(Recommended) A faster parser with improved syntax highlighting & code navigation support.'
|
||||
);
|
||||
}
|
||||
div.appendChild(parser);
|
||||
}
|
||||
|
||||
@ -99,6 +103,16 @@ module.exports = class GrammarListView {
|
||||
this.selectListView.reset();
|
||||
}
|
||||
|
||||
getAllDisplayableGrammars() {
|
||||
let allGrammars = atom.grammars
|
||||
.getGrammars({ includeTreeSitter: true })
|
||||
.filter(grammar => {
|
||||
return grammar !== atom.grammars.nullGrammar && grammar.name;
|
||||
});
|
||||
|
||||
return allGrammars;
|
||||
}
|
||||
|
||||
async toggle() {
|
||||
if (this.panel != null) {
|
||||
this.cancel();
|
||||
@ -113,31 +127,7 @@ module.exports = class GrammarListView {
|
||||
this.currentGrammar = this.autoDetect;
|
||||
}
|
||||
|
||||
let grammars = atom.grammars
|
||||
.getGrammars({ includeTreeSitter: true })
|
||||
.filter(grammar => {
|
||||
return grammar !== atom.grammars.nullGrammar && grammar.name;
|
||||
});
|
||||
|
||||
// Don't show legacy Tree-sitter grammars in the selector unless the user
|
||||
// has opted into it.
|
||||
if (!isLegacyTreeSitterMode()) {
|
||||
grammars = grammars.filter(grammar => !isLegacyTreeSitter(grammar));
|
||||
}
|
||||
|
||||
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();
|
||||
grammars.forEach(grammar => {
|
||||
if (isTreeSitter(grammar)) {
|
||||
blacklist.add(grammar.name);
|
||||
}
|
||||
});
|
||||
grammars = grammars.filter(
|
||||
grammar => isTreeSitter(grammar) || !blacklist.has(grammar.name)
|
||||
);
|
||||
}
|
||||
let grammars = this.getAllDisplayableGrammars();
|
||||
|
||||
grammars.sort((a, b) => {
|
||||
if (a.scopeName === 'text.plain') {
|
||||
@ -149,6 +139,20 @@ module.exports = class GrammarListView {
|
||||
}
|
||||
return a.name.localeCompare(b.name);
|
||||
});
|
||||
|
||||
if (this.hideDuplicateGrammars) {
|
||||
let displayedGrammars = [];
|
||||
let seenIds = new Set();
|
||||
|
||||
for (let grammar of grammars) {
|
||||
if (seenIds.has(grammar.scopeName)) continue;
|
||||
seenIds.add(grammar.scopeName);
|
||||
displayedGrammars.push(grammar);
|
||||
}
|
||||
|
||||
grammars = displayedGrammars;
|
||||
}
|
||||
|
||||
grammars.unshift(this.autoDetect);
|
||||
await this.selectListView.update({ items: grammars });
|
||||
this.attach();
|
||||
@ -156,19 +160,14 @@ module.exports = class GrammarListView {
|
||||
}
|
||||
};
|
||||
|
||||
// We look up global settings here, but it's just to determine the badge
|
||||
// colors. Otherwise we should be looking up these values in a scope-specific
|
||||
// manner.
|
||||
function getLanguageModeConfig() {
|
||||
let isTreeSitterMode = atom.config.get('core.useTreeSitterParsers');
|
||||
let isLegacy = atom.config.get('core.useLegacyTreeSitter');
|
||||
if (!isTreeSitterMode) return 'textmate';
|
||||
return isLegacy ? 'node-tree-sitter' : 'wasm-tree-sitter';
|
||||
}
|
||||
|
||||
function isLegacyTreeSitterMode() {
|
||||
return getLanguageModeConfig() === 'node-tree-sitter';
|
||||
}
|
||||
|
||||
function isTreeSitter(grammar) {
|
||||
return isLegacyTreeSitter(grammar) || isModernTreeSitter(grammar);
|
||||
return isLegacy ? 'node-tree-sitter' : 'web-tree-sitter';
|
||||
}
|
||||
|
||||
function isModernTreeSitter(grammar) {
|
||||
@ -183,8 +182,64 @@ function compareGrammarType(a, b) {
|
||||
return getGrammarScore(a) - getGrammarScore(b);
|
||||
}
|
||||
|
||||
// Given a scope name, determines the user's preferred parser type for that
|
||||
// language.
|
||||
function getParserPreferenceForScopeName(scopeName) {
|
||||
let useTreeSitterParsers = atom.config.get(
|
||||
'core.useTreeSitterParsers',
|
||||
{ scope: [scopeName] }
|
||||
);
|
||||
let useLegacyTreeSitter = atom.config.get(
|
||||
'core.useLegacyTreeSitter',
|
||||
{ scope: [scopeName] }
|
||||
);
|
||||
|
||||
if (!useTreeSitterParsers) {
|
||||
return 'textmate';
|
||||
} else if (useLegacyTreeSitter) {
|
||||
return 'node-tree-sitter';
|
||||
} else {
|
||||
return 'web-tree-sitter';
|
||||
}
|
||||
}
|
||||
|
||||
function getBadgeTextForGrammar(grammar) {
|
||||
switch (grammar.constructor.name) {
|
||||
case 'Grammar':
|
||||
return 'TextMate';
|
||||
case 'WASMTreeSitterGrammar':
|
||||
return 'Modern Tree-sitter';
|
||||
case 'TreeSitterGrammar':
|
||||
return 'Legacy Tree-sitter';
|
||||
}
|
||||
}
|
||||
|
||||
const BADGE_COLORS_BY_LANGUAGE_MODE_CONFIG = {
|
||||
'textmate': {
|
||||
'Grammar': 'badge-success',
|
||||
'TreeSitterGrammar': 'badge-info',
|
||||
'WASMTreeSitterGrammar': 'badge-info'
|
||||
},
|
||||
'web-tree-sitter': {
|
||||
'WASMTreeSitterGrammar': 'badge-success',
|
||||
'TreeSitterGrammar': 'badge-warning',
|
||||
'Grammar': 'badge-info'
|
||||
},
|
||||
'node-tree-sitter': {
|
||||
'TreeSitterGrammar': 'badge-success',
|
||||
'WASMTreeSitterGrammar': 'badge-warning',
|
||||
'Grammar': 'badge-info'
|
||||
}
|
||||
};
|
||||
|
||||
function getBadgeColorForGrammar(grammar) {
|
||||
let languageModeConfig = getLanguageModeConfig();
|
||||
let classNameMap = BADGE_COLORS_BY_LANGUAGE_MODE_CONFIG[languageModeConfig];
|
||||
return classNameMap[grammar.constructor.name];
|
||||
}
|
||||
|
||||
function getGrammarScore(grammar) {
|
||||
let languageParser = getLanguageModeConfig();
|
||||
let languageParser = getParserPreferenceForScopeName(grammar.scopeName);
|
||||
if (isModernTreeSitter(grammar)) {
|
||||
return languageParser === 'node-tree-sitter' ? -1 : -2;
|
||||
}
|
||||
|
@ -22,12 +22,13 @@
|
||||
"showOnRightSideOfStatusBar": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Show the active pane item's language on the right side of Pulsar's status bar, instead of the left."
|
||||
"description": "Show the active pane item’s language on the right side of Pulsar’s status bar, instead of the left."
|
||||
},
|
||||
"hideDuplicateTextMateGrammars": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Hides the TextMate grammar when there is an existing Tree-sitter grammar"
|
||||
"title": "Hide Duplicate Grammars",
|
||||
"description": "Hides non-preferred grammars when there is more than one grammar. When checked, whichever grammar is preferred for a given scope name (TextMate or Tree-sitter) will be the only one shown. When unchecked, all grammars will always be shown in the list, regardless of the user’s settings."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -39,12 +39,14 @@ describe('GrammarSelector', () => {
|
||||
it('displays a list of all the available grammars', async () => {
|
||||
const grammarView = (await getGrammarView(editor)).element;
|
||||
|
||||
let allGrammars = atom.grammars
|
||||
.getGrammars({ includeTreeSitter: true })
|
||||
.filter(g => g.name)
|
||||
|
||||
// -1 for removing nullGrammar, +1 for adding "Auto Detect"
|
||||
// Tree-sitter names the regex and JSDoc grammars
|
||||
expect(grammarView.querySelectorAll('li').length).toBe(
|
||||
atom.grammars
|
||||
.getGrammars({ includeTreeSitter: true })
|
||||
.filter(g => g.name).length
|
||||
allGrammars.length
|
||||
);
|
||||
expect(grammarView.querySelectorAll('li')[0].textContent).toBe(
|
||||
'Auto Detect'
|
||||
@ -55,7 +57,9 @@ describe('GrammarSelector', () => {
|
||||
.forEach(li =>
|
||||
expect(li.textContent).not.toBe(atom.grammars.nullGrammar.name)
|
||||
);
|
||||
expect(grammarView.textContent.includes('Tree-sitter')).toBe(true); // check we are showing and labelling Tree-sitter grammars
|
||||
if (!atom.config.get('grammar-selector.hideDuplicateTextMateGrammars')) {
|
||||
expect(grammarView.textContent.includes('Tree-sitter')).toBe(true); // check we are showing and labelling Tree-sitter grammars
|
||||
}
|
||||
}));
|
||||
|
||||
describe('when a grammar is selected', () =>
|
||||
@ -204,6 +208,11 @@ describe('GrammarSelector', () => {
|
||||
}));
|
||||
|
||||
describe('when toggling hideDuplicateTextMateGrammars', () => {
|
||||
// For continuity reasons, the name of the setting won't be changed; but
|
||||
// this should now be construed as “hide duplicate grammars” — with the
|
||||
// grammar selector showing whatever the user-indicated preference is for
|
||||
// a given grammar.
|
||||
|
||||
it('shows only the Tree-sitter if true and both exist', async () => {
|
||||
// the main JS grammar has both a TextMate and Tree-sitter implementation
|
||||
atom.config.set('grammar-selector.hideDuplicateTextMateGrammars', true);
|
||||
@ -259,7 +268,7 @@ describe('GrammarSelector', () => {
|
||||
expect(cppCount).toBe(3); // ensure we actually saw all three grammars
|
||||
});
|
||||
|
||||
it('shows two if false (in proper order when language parser is wasm-tree-sitter)', async () => {
|
||||
it('shows all three if false (in proper order when language parser is wasm-tree-sitter)', async () => {
|
||||
await atom.packages.activatePackage('language-c'); // punctuation making it sort wrong
|
||||
setConfigForLanguageMode('wasm-tree-sitter');
|
||||
atom.config.set(
|
||||
@ -268,16 +277,19 @@ describe('GrammarSelector', () => {
|
||||
);
|
||||
await getGrammarView(editor);
|
||||
let cppCount = 0;
|
||||
|
||||
const listItems = atom.workspace.getModalPanels()[0].item.items;
|
||||
for (let i = 0; i < listItems.length; i++) {
|
||||
const grammar = listItems[i];
|
||||
const name = grammar.name;
|
||||
if (cppCount === 0 && name === 'C++') {
|
||||
// first C++ entry should be modern Tree-sitter
|
||||
// first C++ entry should be legacy Tree-sitter
|
||||
expect(grammar.constructor.name).toBe('WASMTreeSitterGrammar');
|
||||
cppCount++;
|
||||
} else if (cppCount === 1) {
|
||||
// next C++ entry should be modern Tree-sitter
|
||||
expect(grammar.constructor.name).toBe('TreeSitterGrammar');
|
||||
cppCount++;
|
||||
} else if (cppCount === 2) {
|
||||
// immediate next grammar should be the TextMate version
|
||||
expect(name).toBe('C++');
|
||||
expect(grammar.constructor.name).toBe('Grammar');
|
||||
@ -287,10 +299,10 @@ describe('GrammarSelector', () => {
|
||||
}
|
||||
}
|
||||
|
||||
expect(cppCount).toBe(2); // ensure we actually saw two grammars
|
||||
expect(cppCount).toBe(3); // ensure we actually saw three grammars
|
||||
});
|
||||
|
||||
it('shows both if false (in proper order when language parser is textmate)', async () => {
|
||||
it('shows all three if false (in proper order when language parser is textmate)', async () => {
|
||||
await atom.packages.activatePackage('language-c'); // punctuation making it sort wrong
|
||||
atom.config.set(
|
||||
'grammar-selector.hideDuplicateTextMateGrammars',
|
||||
@ -304,20 +316,24 @@ describe('GrammarSelector', () => {
|
||||
const grammar = listItems[i];
|
||||
const name = grammar.name;
|
||||
if (cppCount === 0 && name === 'C++') {
|
||||
// first C++ entry should be TextMate
|
||||
// first C++ entry should be legacy Tree-sitter
|
||||
expect(grammar.constructor.name).toBe('Grammar');
|
||||
cppCount++;
|
||||
} else if (cppCount === 1) {
|
||||
// immediate next grammar should be the Tree-sitter version
|
||||
expect(name).toBe('C++');
|
||||
// next C++ entry should be modern Tree-sitter
|
||||
expect(grammar.constructor.name).toBe('WASMTreeSitterGrammar');
|
||||
cppCount++;
|
||||
} else if (cppCount === 2) {
|
||||
// immediate next grammar should be the TextMate version
|
||||
expect(name).toBe('C++');
|
||||
expect(grammar.constructor.name).toBe('TreeSitterGrammar');
|
||||
cppCount++;
|
||||
} else {
|
||||
expect(name).not.toBe('C++'); // there should not be any other C++ grammars
|
||||
}
|
||||
}
|
||||
|
||||
expect(cppCount).toBe(2); // ensure we actually saw both grammars
|
||||
expect(cppCount).toBe(3); // ensure we actually saw three grammars
|
||||
});
|
||||
|
||||
});
|
||||
@ -387,17 +403,16 @@ describe('GrammarSelector', () => {
|
||||
.forEach(li =>
|
||||
expect(li.textContent).not.toBe(atom.grammars.nullGrammar.name)
|
||||
);
|
||||
// Ensure we're showing and labelling tree-sitter grammars…
|
||||
if (!atom.config.get('grammar-selector.hideDuplicateTextMateGrammars')) {
|
||||
// Ensure we're showing and labelling tree-sitter grammars.
|
||||
expect(grammarView.textContent.includes('Tree-sitter')).toBe(true);
|
||||
// …but not old tree-sitter grammars.
|
||||
expect(grammarView.textContent.includes('Legacy Tree-sitter')).toBe(false);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('when toggling hideDuplicateTextMateGrammars', () => {
|
||||
it('shows only the Tree-sitter if true and both exist', async () => {
|
||||
// the main JS grammar has both a TextMate and Tree-sitter implementation
|
||||
it('shows only the preferred if true and several exist (and preferred is default)', async () => {
|
||||
atom.config.set('grammar-selector.hideDuplicateTextMateGrammars', true);
|
||||
const grammarView = await getGrammarView(editor);
|
||||
const observedNames = new Map();
|
||||
@ -420,7 +435,53 @@ describe('GrammarSelector', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('shows both if false', async () => {
|
||||
it('shows only the preferred if true and several exist (and preferred is node-tree-sitter)', async () => {
|
||||
atom.config.set('grammar-selector.hideDuplicateTextMateGrammars', true);
|
||||
setConfigForLanguageMode('node-tree-sitter', { scopeSelector: '.source.js' })
|
||||
const grammarView = await getGrammarView(editor);
|
||||
const observedNames = new Map();
|
||||
grammarView.element.querySelectorAll('li').forEach(li => {
|
||||
const name = li.getAttribute('data-grammar');
|
||||
if (!observedNames.has(name)) {
|
||||
observedNames.set(name, 0);
|
||||
}
|
||||
observedNames.set(name, observedNames.get(name) + 1);
|
||||
expect(observedNames.get(name) < 2).toBe(true, `found ${observedNames.get(name)} of ${name}`);
|
||||
});
|
||||
|
||||
// check the seen JS is actually the Tree-sitter one
|
||||
const list = atom.workspace.getModalPanels()[0].item;
|
||||
for (const item of list.items) {
|
||||
if (item.name === 'JavaScript') {
|
||||
expect(item.constructor.name).toBe('TreeSitterGrammar');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('shows only the preferred if true and several exist (and preferred is textmate)', async () => {
|
||||
atom.config.set('grammar-selector.hideDuplicateTextMateGrammars', true);
|
||||
setConfigForLanguageMode('textmate', { scopeSelector: '.source.js' })
|
||||
const grammarView = await getGrammarView(editor);
|
||||
const observedNames = new Map();
|
||||
grammarView.element.querySelectorAll('li').forEach(li => {
|
||||
const name = li.getAttribute('data-grammar');
|
||||
if (!observedNames.has(name)) {
|
||||
observedNames.set(name, 0);
|
||||
}
|
||||
observedNames.set(name, observedNames.get(name) + 1);
|
||||
expect(observedNames.get(name) < 2).toBe(true, `found ${observedNames.get(name)} of ${name}`);
|
||||
});
|
||||
|
||||
// check the seen JS is actually the Tree-sitter one
|
||||
const list = atom.workspace.getModalPanels()[0].item;
|
||||
for (const item of list.items) {
|
||||
if (item.name === 'JavaScript') {
|
||||
expect(item.constructor.name).toBe('Grammar');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('shows three if false (in the proper order when language parser is web-tree-sitter)', async () => {
|
||||
await atom.packages.activatePackage('language-c'); // punctuation making it sort wrong
|
||||
atom.config.set(
|
||||
'grammar-selector.hideDuplicateTextMateGrammars',
|
||||
@ -437,20 +498,28 @@ describe('GrammarSelector', () => {
|
||||
expect(grammar.constructor.name).toBe('WASMTreeSitterGrammar'); // first C++ entry should be Modern Tree-sitter
|
||||
cppCount++;
|
||||
} else if (cppCount === 1) {
|
||||
expect(name).toBe('C++');
|
||||
expect(grammar.constructor.name).toBe('TreeSitterGrammar'); // first C++ entry should be Modern Tree-sitter
|
||||
cppCount++;
|
||||
} else if (cppCount === 2) {
|
||||
expect(name).toBe('C++');
|
||||
expect(grammar.constructor.name).toBe('Grammar'); // immediate next grammar should be the TextMate version
|
||||
cppCount++;
|
||||
} else {
|
||||
expect(name).not.toBe('C++'); // there should not be any other C++ grammars
|
||||
expect(name).not.toBe('C++');
|
||||
}
|
||||
}
|
||||
|
||||
expect(cppCount).toBe(2); // ensure we actually saw three grammars
|
||||
expect(cppCount).toBe(3); // ensure we actually saw three grammars
|
||||
});
|
||||
});
|
||||
|
||||
describe('for every Tree-sitter grammar', () => {
|
||||
it('adds a label to identify it as Tree-sitter', async () => {
|
||||
atom.config.set(
|
||||
'grammar-selector.hideDuplicateTextMateGrammars',
|
||||
false
|
||||
);
|
||||
const grammarView = await getGrammarView(editor);
|
||||
const elements = grammarView.element.querySelectorAll('li');
|
||||
const listItems = atom.workspace.getModalPanels()[0].item.items;
|
||||
@ -514,33 +583,35 @@ describe('GrammarSelector', () => {
|
||||
});
|
||||
|
||||
describe('when toggling hideDuplicateTextMateGrammars', () => {
|
||||
it('shows only the Tree-sitter if true and both exist', async () => {
|
||||
it('shows only the Tree-sitter if true and several exist', async () => {
|
||||
// the main JS grammar has both a TextMate and Tree-sitter implementation
|
||||
atom.config.set('grammar-selector.hideDuplicateTextMateGrammars', true);
|
||||
atom.config.set(
|
||||
'grammar-selector.hideDuplicateTextMateGrammars',
|
||||
true
|
||||
);
|
||||
const grammarView = await getGrammarView(editor);
|
||||
const observedNames = new Map();
|
||||
// Show a maximum of two different kinds of grammar (both tree-sitter
|
||||
// variants).
|
||||
|
||||
grammarView.element.querySelectorAll('li').forEach(li => {
|
||||
const name = li.getAttribute('data-grammar');
|
||||
if (!observedNames.has(name)) {
|
||||
observedNames.set(name, 0);
|
||||
}
|
||||
observedNames.set(name, observedNames.get(name) + 1);
|
||||
expect(observedNames.get(name) < 3).toBe(true, `found ${observedNames.get(name)} of ${name}`);
|
||||
expect(observedNames.get(name) < 2).toBe(true, `found ${observedNames.get(name)} of ${name}`);
|
||||
});
|
||||
|
||||
// check the seen JS is actually the Tree-sitter one
|
||||
const list = atom.workspace.getModalPanels()[0].item;
|
||||
for (const item of list.items) {
|
||||
if (item.name === 'JavaScript') {
|
||||
expect(item.constructor.name.includes('TreeSitterGrammar'));
|
||||
expect(item.constructor.name).toBe('TreeSitterGrammar');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('shows both if false', async () => {
|
||||
await atom.packages.activatePackage('language-c'); // punctuation making it sort wrong
|
||||
it('shows all if false', async () => {
|
||||
await atom.packages.activatePackage('language-c');
|
||||
atom.config.set(
|
||||
'grammar-selector.hideDuplicateTextMateGrammars',
|
||||
false
|
||||
@ -572,7 +643,11 @@ describe('GrammarSelector', () => {
|
||||
});
|
||||
|
||||
describe('for every Tree-sitter grammar', () => {
|
||||
it('adds a label to identify it as Tree-sitter', async () => {
|
||||
it('adds a label to identify it as Tree-sitter (when showing duplicate grammars)', async () => {
|
||||
atom.config.set(
|
||||
'grammar-selector.hideDuplicateTextMateGrammars',
|
||||
false
|
||||
);
|
||||
const grammarView = await getGrammarView(editor);
|
||||
const elements = grammarView.element.querySelectorAll('li');
|
||||
const listItems = atom.workspace.getModalPanels()[0].item.items;
|
||||
@ -597,6 +672,36 @@ describe('GrammarSelector', () => {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('does not add a label to identify it as Tree-sitter (when hiding duplicate grammars)', async () => {
|
||||
atom.config.set(
|
||||
'grammar-selector.hideDuplicateTextMateGrammars',
|
||||
true
|
||||
);
|
||||
const grammarView = await getGrammarView(editor);
|
||||
const elements = grammarView.element.querySelectorAll('li');
|
||||
const listItems = atom.workspace.getModalPanels()[0].item.items;
|
||||
for (let i = 0; i < listItems.length; i++) {
|
||||
let item = listItems[i];
|
||||
let element = elements[i];
|
||||
if (item.constructor.name.includes('TreeSitterGrammar')) {
|
||||
expect(
|
||||
element.childNodes[1].childNodes[0].className.startsWith(
|
||||
'grammar-selector-parser'
|
||||
)
|
||||
).toBe(false);
|
||||
}
|
||||
if (item.constructor.name === 'TreeSitterGrammar') {
|
||||
expect(
|
||||
element.childNodes[1].childNodes[0].classList.contains('badge-success')
|
||||
).toBe(false);
|
||||
} else if (item.constructor.name === 'WASMTreeSitterGrammar') {
|
||||
expect(
|
||||
element.childNodes[1].childNodes[0].classList.contains('badge-warning')
|
||||
).toBe(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -1,5 +1,13 @@
|
||||
@import "ui-variables";
|
||||
|
||||
.grammar-item {
|
||||
&:after {
|
||||
content: "";
|
||||
display: table;
|
||||
clear: both;
|
||||
}
|
||||
}
|
||||
|
||||
.grammar-status a,
|
||||
.grammar-status a:hover {
|
||||
color: @text-color;
|
||||
|
@ -1,4 +1,3 @@
|
||||
|
||||
; PREPROCESSOR
|
||||
; ============
|
||||
|
||||
@ -223,6 +222,14 @@
|
||||
field: (field_identifier) @variable.other.member.assignment.c)
|
||||
(#set! capture.final))
|
||||
|
||||
; Goto label definitions: the "foo" in `foo:` before a statement.
|
||||
(labeled_statement
|
||||
label: (statement_identifier) @entity.name.label.c)
|
||||
|
||||
; Goto statements — the "foo" in `goto foo;`
|
||||
(goto_statement
|
||||
label: (statement_identifier) @support.other.label.c)
|
||||
|
||||
|
||||
; Function parameters
|
||||
; -------------------
|
||||
@ -256,6 +263,12 @@
|
||||
"->"
|
||||
field: (field_identifier) @variable.other.member.c)
|
||||
|
||||
; The "bar" in `foo.bar`.
|
||||
(field_expression
|
||||
operator: "."
|
||||
field: (field_identifier) @variable.other.member.c)
|
||||
|
||||
|
||||
|
||||
; FUNCTIONS
|
||||
; =========
|
||||
|
@ -305,6 +305,13 @@
|
||||
(identifier) @variable.declaration.cpp)
|
||||
(#is-not? test.descendantOfType parameter_declaration))
|
||||
|
||||
; Goto label definitions like `foo:` before a statement.
|
||||
(labeled_statement
|
||||
label: (statement_identifier) @entity.name.label.cpp)
|
||||
|
||||
(goto_statement
|
||||
label: (statement_identifier) @support.other.label.cpp)
|
||||
|
||||
; Function parameters
|
||||
; -------------------
|
||||
|
||||
@ -341,6 +348,11 @@
|
||||
"->"
|
||||
field: (field_identifier) @variable.other.member.cpp)
|
||||
|
||||
; The "bar" in `foo.bar`.
|
||||
(field_expression
|
||||
operator: "."
|
||||
field: (field_identifier) @variable.other.member.cpp)
|
||||
|
||||
; Common naming idiom for C++ instanced vars: "fMemberName"
|
||||
; ((identifier) @variable.other.readwrite.member.cpp
|
||||
; (#match? @variable.other.readwrite.member.cpp "^(f|m)[A-Z]\\w*$"))
|
||||
@ -372,7 +384,7 @@
|
||||
; macro preprocessors. The convention is decently strong in C/C++ that all-caps
|
||||
; identifiers will refer to `#define`d things.
|
||||
((identifier) @constant.other.cpp
|
||||
(#match? @constant.other.cpp "[_A-Z][_A-Z0-9]*$")
|
||||
(#match? @constant.other.cpp "^[_A-Z][_A-Z0-9]*$")
|
||||
(#set! capture.shy))
|
||||
|
||||
|
||||
|
@ -8,7 +8,7 @@ exports.activate = function () {
|
||||
return language;
|
||||
},
|
||||
content(node) {
|
||||
return node.lastNamedChild;
|
||||
return node.descendantsOfType('preproc_arg');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -862,7 +862,7 @@
|
||||
; use "?." to target it.
|
||||
(optional_chain) @keyword.operator.accessor.optional-chaining.js
|
||||
|
||||
; Optional chaining is illegal…:
|
||||
; Optional chaining is illegal…
|
||||
|
||||
; …on the left-hand side of an assignment.
|
||||
(assignment_expression
|
||||
|
@ -103,9 +103,6 @@
|
||||
; HANGING INDENT ON SPLIT LINES
|
||||
; =============================
|
||||
|
||||
; TODO: We might want to make this configurable behavior with the
|
||||
; `config` scope test.
|
||||
|
||||
; Any of these at the end of a line indicate the next line should be indented…
|
||||
(["||" "&&"] @indent
|
||||
(#is? test.config "language-javascript.indentation.addHangingIndentAfterLogicalOperators")
|
||||
|
@ -9,6 +9,8 @@
|
||||
(lambda)
|
||||
(hash)
|
||||
(array)
|
||||
(string_array)
|
||||
(symbol_array)
|
||||
] @fold
|
||||
|
||||
|
||||
|
@ -515,6 +515,32 @@
|
||||
"[" @punctuation.definition.begin.array.bracket.square.ruby
|
||||
"]" @punctuation.definition.end.array.bracket.square.ruby
|
||||
|
||||
; Some array delimiters don't use the correct anonymous nodes, so we have to test their content.
|
||||
;
|
||||
; Handle `%w[` and `%i[`.
|
||||
(["%w(" "%i("] @punctuation.definition.begin.array.bracket.square.ruby
|
||||
(#match? @punctuation.definition.begin.array.bracket.square.ruby "%[a-z]\\["))
|
||||
|
||||
; Handle `%w(` and `%i(`.
|
||||
(["%w(" "%i("] @punctuation.definition.begin.array.bracket.round.ruby
|
||||
(#match? @punctuation.definition.begin.array.bracket.round.ruby "^%[a-z]\\("))
|
||||
|
||||
; Handle ending delimiter of `%w(`.
|
||||
((string_array ")" @punctuation.definition.end.array.bracket.round.ruby)
|
||||
(#eq? @punctuation.definition.end.array.bracket.round.ruby ")"))
|
||||
|
||||
; Handle ending delimiter of `%i(`.
|
||||
((symbol_array ")" @punctuation.definition.end.array.bracket.round.ruby)
|
||||
(#eq? @punctuation.definition.end.array.bracket.round.ruby ")"))
|
||||
|
||||
; Any character can be used as the delimiter for a special percent-something! For the rest we can define catch-alls.
|
||||
(["%w(" "%i("] @punctuation.definition.begin.array.ruby
|
||||
(#set! capture.shy true))
|
||||
((string_array ")" @punctuation.definition.end.array.ruby)
|
||||
(#set! capture.shy true))
|
||||
((symbol_array ")" @punctuation.definition.end.array.ruby)
|
||||
(#set! capture.shy true))
|
||||
|
||||
(parenthesized_statements
|
||||
"(" @punctuation.definition.expression.begin.bracket.round.ruby
|
||||
")" @punctuation.definition.expression.end.bracket.round.ruby)
|
||||
|
@ -25,7 +25,6 @@
|
||||
"while"
|
||||
"until"
|
||||
"for"
|
||||
; (for)
|
||||
"begin"
|
||||
"do"
|
||||
"rescue"
|
||||
@ -33,6 +32,11 @@
|
||||
"("
|
||||
"{"
|
||||
"["
|
||||
; “Special” array notations. (Currently, they all have the same anonymous
|
||||
; node, even if they’re delimited with another character pair instead of
|
||||
; parentheses.)
|
||||
"%w("
|
||||
"%i("
|
||||
] @indent
|
||||
|
||||
[
|
||||
|
@ -41,9 +41,7 @@ firstLineRegex: [
|
||||
injectionRegex: '(^(bash|BASH)$|sh^|SH^)'
|
||||
|
||||
treeSitter:
|
||||
# Grammar generated on 2023-09-22 from commit:
|
||||
# fd4e40dab883d6456da4d847de8321aee9c80805
|
||||
parserSource: 'github:tree-sitter/tree-sitter-bash#fd4e40dab883d6456da4d847de8321aee9c80805'
|
||||
parserSource: 'github:tree-sitter/tree-sitter-bash#8df9ea875d557a150d025d3a15b6e6a58624379a'
|
||||
grammar: 'tree-sitter/tree-sitter-bash.wasm'
|
||||
highlightsQuery: 'tree-sitter/highlights.scm'
|
||||
tagsQuery: 'tree-sitter/tags.scm'
|
||||
|
Binary file not shown.
@ -44,10 +44,11 @@
|
||||
(required_parameter
|
||||
pattern: (identifier) @variable.parameter.with-default._LANG_
|
||||
value: (_)
|
||||
(#set! capture.final true))
|
||||
(#set! capture.final))
|
||||
|
||||
(required_parameter
|
||||
pattern: (identifier) @variable.parameter._LANG_)
|
||||
pattern: (identifier) @variable.parameter._LANG_
|
||||
(#set! capture.final))
|
||||
|
||||
(required_parameter
|
||||
pattern: (rest_pattern
|
||||
@ -56,16 +57,17 @@
|
||||
(required_parameter
|
||||
pattern: (object_pattern
|
||||
(shorthand_property_identifier_pattern) @variable.parameter.destructuring._LANG_)
|
||||
(#set! capture.final true))
|
||||
(#set! capture.final))
|
||||
|
||||
(required_parameter
|
||||
pattern: (object_pattern
|
||||
(object_assignment_pattern
|
||||
(shorthand_property_identifier_pattern) @variable.parameter.destructuring.with-default._LANG_))
|
||||
(#set! capture.final true))
|
||||
(#set! capture.final))
|
||||
|
||||
(optional_parameter
|
||||
pattern: (identifier) @variable.parameter.optional._LANG_)
|
||||
pattern: (identifier) @variable.parameter.optional._LANG_
|
||||
(#set! capture.final))
|
||||
|
||||
(optional_parameter "?" @keyword.operator.type.optional._LANG_)
|
||||
|
||||
@ -820,6 +822,8 @@
|
||||
(binary_expression
|
||||
["+" "-" "*" "/" "%"] @keyword.operator.arithmetic._LANG_)
|
||||
|
||||
(unary_expression ["+" "-"] @keyword.operator.unary._LANG_)
|
||||
|
||||
(binary_expression
|
||||
[
|
||||
"=="
|
||||
|
@ -3,7 +3,7 @@ scopeName: 'source.tsx'
|
||||
type: 'modern-tree-sitter'
|
||||
parser: 'tree-sitter-typescript/tsx'
|
||||
|
||||
fileTypes: ['tsx']
|
||||
fileTypes: ['tsx', 'ctsx', 'mtsx']
|
||||
|
||||
injectionRegex: '^(tsx|TSX)$'
|
||||
|
||||
|
@ -3,7 +3,7 @@ scopeName: 'source.ts'
|
||||
type: 'modern-tree-sitter'
|
||||
parser: 'tree-sitter-typescript/typescript'
|
||||
|
||||
fileTypes: ['ts']
|
||||
fileTypes: ['ts', 'cts', 'mts']
|
||||
|
||||
injectionRegex: '^(typescript|TYPESCRIPT|ts|TS)$'
|
||||
|
||||
|
@ -170,7 +170,7 @@ class FileView extends SymbolsView {
|
||||
let populated = this.populate(editor);
|
||||
if (!populated) return;
|
||||
this.attach();
|
||||
this.selectListView.update({ query: filterTerm });
|
||||
this.selectListView.update({ query: filterTerm, selectQuery: true });
|
||||
}
|
||||
|
||||
serializeEditorState(editor) {
|
||||
|
@ -26,7 +26,7 @@ module.exports = class ProjectView extends SymbolsView {
|
||||
} else {
|
||||
this.populate();
|
||||
this.attach();
|
||||
this.selectListView.update({ query: filterTerm });
|
||||
this.selectListView.update({ query: filterTerm, selectQuery: true });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -157,6 +157,12 @@ describe('SymbolsView', () => {
|
||||
|
||||
expect(symbolsView.element.querySelector('li:first-child .primary-line')).toHaveText('Symbol on Row 13');
|
||||
expect(symbolsView.element.querySelector('li:first-child .secondary-line')).toHaveText('Line 13');
|
||||
|
||||
// We reach inside of the `SelectListView` instance to its `TextEditor`
|
||||
// so that we can assert that the text in the query field is selected.
|
||||
// This allows the user to start typing and replace the prefilled
|
||||
// selection if they didn't mean to prefill the query.
|
||||
expect(symbolsView.selectListView.refs.queryEditor.getSelectedText()).toBe('Symbol on Row 13');
|
||||
});
|
||||
|
||||
it('does not prefill the query field if `prefillSelectedText` is `false`', async () => {
|
||||
|
@ -478,6 +478,14 @@ describe('GrammarRegistry', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it(`returns a legacy Tree-sitter grammar if the user opted into it via a scope-specific setting`, async () => {
|
||||
await atom.packages.activatePackage('language-javascript');
|
||||
setConfigForLanguageMode('node-tree-sitter', { scopeSelector: '.source.js' })
|
||||
let grammar = atom.grammars.selectGrammar('file.js');
|
||||
expect(grammar.name).toBe('JavaScript');
|
||||
expect(grammar.constructor.name).toBe('TreeSitterGrammar');
|
||||
})
|
||||
|
||||
it("uses the filePath's shebang line if the grammar cannot be determined by the extension or basename", async () => {
|
||||
await atom.packages.activatePackage('language-javascript');
|
||||
await atom.packages.activatePackage('language-ruby');
|
||||
|
@ -1931,18 +1931,10 @@ describe('WASMTreeSitterLanguageMode', () => {
|
||||
const grammar = new WASMTreeSitterGrammar(atom.grammars, htmlGrammarPath, htmlConfig);
|
||||
|
||||
await grammar.setQueryForTest('foldsQuery', scm`
|
||||
((element
|
||||
(start_tag
|
||||
(tag_name) @_IGNORE_) @fold)
|
||||
(#match? @_IGNORE_ "^(area|base|br|col|embed|hr|img|input|keygen|link|meta|param|source|track|wbr)$")
|
||||
(#set! fold.invalidateOnChange true)
|
||||
)
|
||||
|
||||
(element
|
||||
(start_tag
|
||||
(tag_name) @_IGNORE_
|
||||
">" @fold)
|
||||
(#not-match? @_IGNORE_ "^(area|base|br|col|embed|hr|img|input|keygen|link|meta|param|source|track|wbr)$")
|
||||
(#set! fold.endAt parent.parent.lastNamedChild.startPosition)
|
||||
(#set! fold.adjustToEndOfPreviousRow true)
|
||||
)
|
||||
@ -1950,12 +1942,14 @@ describe('WASMTreeSitterLanguageMode', () => {
|
||||
(element
|
||||
(start_tag
|
||||
(tag_name) @_IGNORE_) @fold
|
||||
(#not-match? @_IGNORE_ "^(area|base|br|col|embed|hr|img|input|keygen|link|meta|param|source|track|wbr)$")
|
||||
(#set! fold.invalidateOnChange true)
|
||||
(#set! fold.endAt lastChild.startPosition)
|
||||
(#set! fold.adjustToEndOfPreviousRow true))
|
||||
`);
|
||||
|
||||
// This is almost the exact scenario that created the need for this
|
||||
// predicate. Since we use `adjustToEndOfPreviousRow`, this fold won't be
|
||||
// valid in the below scenario because it'd start and end on row 0.
|
||||
buffer.setText(dedent`
|
||||
<div
|
||||
foo="bar">
|
||||
@ -1978,6 +1972,11 @@ describe('WASMTreeSitterLanguageMode', () => {
|
||||
editor.insertText('\n');
|
||||
await languageMode.atTransactionEnd();
|
||||
|
||||
// It's only after we make this edit — and `start_tag` ends on row 2
|
||||
// instead of row 1 — that the fold becomes valid, since now the fold
|
||||
// range will start at row 0 and end at row 1. But without
|
||||
// `fold.invalidateOnChange`, we wouldn't know that the change on line 1
|
||||
// could have any effect on whether row 0 was foldable.
|
||||
expect(editor.getText()).toBe(dedent`
|
||||
<div
|
||||
foo="bar"
|
||||
@ -1987,8 +1986,6 @@ describe('WASMTreeSitterLanguageMode', () => {
|
||||
</div>
|
||||
`)
|
||||
|
||||
// Making that buffer change on line 1 should invalidate the fold cache
|
||||
// on line 0.
|
||||
expect(editor.isFoldableAtBufferRow(0)).toBe(true);
|
||||
expect(editor.isFoldableAtBufferRow(1)).toBe(false);
|
||||
expect(editor.isFoldableAtBufferRow(2)).toBe(true);
|
||||
@ -3883,7 +3880,6 @@ describe('WASMTreeSitterLanguageMode', () => {
|
||||
expect(editor.getText()).toEqual(emptyClassText);
|
||||
});
|
||||
|
||||
// This test is known to fail (and expected to fail) without async-indent enabled.
|
||||
it('auto-indents correctly if any change in a transaction wants auto-indentation', async () => {
|
||||
jasmine.useRealClock();
|
||||
const grammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, jsConfig);
|
||||
@ -3907,20 +3903,47 @@ describe('WASMTreeSitterLanguageMode', () => {
|
||||
buffer.setText(emptyClassText);
|
||||
|
||||
const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
|
||||
// Force this test to use async indent in all cases.
|
||||
languageMode.transactionReparseBudgetMs = 0;
|
||||
languageMode.currentTransactionReparseBudgetMs = 0;
|
||||
buffer.setLanguageMode(languageMode);
|
||||
await languageMode.ready;
|
||||
await wait(0);
|
||||
|
||||
editor.setCursorBufferPosition([1, 0]);
|
||||
spyOn(languageMode, 'suggestedIndentForBufferRows').andCallThrough();
|
||||
|
||||
editor.setCursorBufferPosition([0, 15]);
|
||||
editor.transact(() => {
|
||||
editor.insertText('// this is a comment', { autoIndent: true });
|
||||
// This is a transaction in which each indentation decision is
|
||||
// contingent on the previous indentation decisions that have been
|
||||
// made. But in async indent mode, we cannot make any indentation
|
||||
// suggestions until the end of the transaction.
|
||||
//
|
||||
// Still, in some scenarios, this will be OK. Either we can make each
|
||||
// of these indentation decisions in order once the transaction is
|
||||
// done, or we can give up and auto-indent the whole range covered by
|
||||
// the transaction when we're done.
|
||||
//
|
||||
// This is an imperfect heuristic and won't produce good results in
|
||||
// many cases, which is why we flip to async indent reluctantly and
|
||||
// only in certain scenarios. But it's better than committing to N
|
||||
// re-parses (where N equals the number of indentation suggestions
|
||||
// we're asked to make during a given transaction) no matter how high N
|
||||
// may be. And it's also better than performing no indentation at all
|
||||
// in these cases.
|
||||
editor.insertNewline();
|
||||
editor.insertText('// and this is another', { autoIndent: true });
|
||||
editor.insertText('// this is a comment', { autoIndent: true, autoDecreaseIndent: true });
|
||||
editor.insertNewline();
|
||||
editor.insertText('// and this is another', { autoIndent: true, autoDecreaseIndent: true });
|
||||
editor.insertNewline();
|
||||
});
|
||||
|
||||
await wait(0);
|
||||
|
||||
expect(
|
||||
languageMode.suggestedIndentForBufferRows
|
||||
).toHaveBeenCalled();
|
||||
|
||||
expect(editor.lineTextForBufferRow(1)).toEqual(
|
||||
` // this is a comment`
|
||||
);
|
||||
@ -4078,7 +4101,6 @@ describe('WASMTreeSitterLanguageMode', () => {
|
||||
["}"] @dedent
|
||||
`);
|
||||
|
||||
// let textToPaste = `// this is a comment\n // and this is another`;
|
||||
let textToPaste = `a comment`;
|
||||
buffer.setText(textToPaste);
|
||||
|
||||
@ -4123,43 +4145,101 @@ describe('WASMTreeSitterLanguageMode', () => {
|
||||
await grammar.setQueryForTest('indentsQuery', `
|
||||
["{"] @indent
|
||||
["}"] @dedent
|
||||
`);
|
||||
`);
|
||||
|
||||
let emptyClassText = dedent`
|
||||
class Example {
|
||||
let emptyClassText = dedent`
|
||||
class Example {
|
||||
|
||||
}
|
||||
`;
|
||||
}
|
||||
`;
|
||||
|
||||
buffer.setText(emptyClassText);
|
||||
buffer.setText(emptyClassText);
|
||||
|
||||
const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
|
||||
buffer.setLanguageMode(languageMode);
|
||||
await languageMode.ready;
|
||||
const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
|
||||
buffer.setLanguageMode(languageMode);
|
||||
await languageMode.ready;
|
||||
|
||||
editor.setCursorBufferPosition([1, 0]);
|
||||
editor.indent();
|
||||
await languageMode.atTransactionEnd();
|
||||
editor.insertText('// this is a comment', { autoIndent: true });
|
||||
await languageMode.atTransactionEnd();
|
||||
expect(editor.lineTextForBufferRow(1)).toEqual(' // this is a comment');
|
||||
editor.setCursorBufferPosition([1, 0]);
|
||||
editor.indent();
|
||||
await languageMode.atTransactionEnd();
|
||||
editor.insertText('// this is a comment', { autoIndent: true });
|
||||
await languageMode.atTransactionEnd();
|
||||
expect(editor.lineTextForBufferRow(1)).toEqual(' // this is a comment');
|
||||
|
||||
editor.insertNewline();
|
||||
await languageMode.atTransactionEnd();
|
||||
await wait(0);
|
||||
expect(editor.lineTextForBufferRow(2)).toEqual(' ');
|
||||
editor.insertNewline();
|
||||
await languageMode.atTransactionEnd();
|
||||
await wait(0);
|
||||
expect(editor.lineTextForBufferRow(2)).toEqual(' ');
|
||||
|
||||
editor.insertNewline();
|
||||
await languageMode.atTransactionEnd();
|
||||
await wait(0);
|
||||
expect(editor.lineTextForBufferRow(3)).toEqual(' ');
|
||||
editor.insertNewline();
|
||||
await languageMode.atTransactionEnd();
|
||||
await wait(0);
|
||||
expect(editor.lineTextForBufferRow(3)).toEqual(' ');
|
||||
|
||||
editor.insertNewline();
|
||||
await languageMode.atTransactionEnd();
|
||||
await wait(0);
|
||||
expect(editor.lineTextForBufferRow(4)).toEqual(' ');
|
||||
editor.insertNewline();
|
||||
await languageMode.atTransactionEnd();
|
||||
await wait(0);
|
||||
expect(editor.lineTextForBufferRow(4)).toEqual(' ');
|
||||
});
|
||||
|
||||
it(`can indent properly in a multi-cursor environment without auto-indenting large ranges of the buffer`, async () => {
|
||||
jasmine.useRealClock();
|
||||
const grammar = new WASMTreeSitterGrammar(atom.grammars, jsGrammarPath, jsConfig);
|
||||
|
||||
expect(editor.getUndoGroupingInterval()).toBe(300);
|
||||
|
||||
await grammar.setQueryForTest('indentsQuery', `
|
||||
["{"] @indent
|
||||
["}"] @dedent
|
||||
`);
|
||||
|
||||
const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
|
||||
// Force this test to use async indent in all cases.
|
||||
languageMode.transactionReparseBudgetMs = 0;
|
||||
languageMode.currentTransactionReparseBudgetMs = 0;
|
||||
spyOn(languageMode, 'suggestedIndentForBufferRows').andCallThrough();
|
||||
buffer.setLanguageMode(languageMode);
|
||||
await languageMode.ready;
|
||||
|
||||
// No spaces after the `{`s in these examples so that we can more easily
|
||||
// compare expected output to actual output.
|
||||
buffer.setText(dedent`
|
||||
function test () {return }
|
||||
|
||||
function test () {return }
|
||||
|
||||
function test () {return }
|
||||
`);
|
||||
|
||||
expect(
|
||||
languageMode.suggestedIndentForBufferRows
|
||||
).not.toHaveBeenCalled();
|
||||
|
||||
editor.setCursorBufferPosition([0, 18])
|
||||
editor.addCursorAtBufferPosition([2, 18])
|
||||
editor.addCursorAtBufferPosition([4, 18])
|
||||
|
||||
editor.insertNewline({
|
||||
autoIndent: true,
|
||||
autoIndentNewline: true,
|
||||
autoDecreaseIndent: true
|
||||
})
|
||||
|
||||
await wait(0);
|
||||
|
||||
expect(buffer.getText()).toBe(dedent`
|
||||
function test () {
|
||||
return }
|
||||
|
||||
function test () {
|
||||
return }
|
||||
|
||||
function test () {
|
||||
return }
|
||||
`)
|
||||
|
||||
})
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -2027,11 +2027,14 @@ describe('Workspace', () => {
|
||||
'source.coffee',
|
||||
'source.js', // Tree-sitter grammars also load
|
||||
'source.js',
|
||||
'source.js',
|
||||
'source.js.regexp',
|
||||
'source.js.regexp',
|
||||
'source.js.regexp',
|
||||
'source.js.regexp.replacement',
|
||||
'source.jsdoc',
|
||||
'source.jsdoc',
|
||||
'source.jsdoc',
|
||||
'source.litcoffee',
|
||||
'text.plain.null-grammar',
|
||||
'text.todo',
|
||||
@ -2277,11 +2280,11 @@ describe('Workspace', () => {
|
||||
Ctor.prototype = HTMLElement.prototype;
|
||||
TestItemElement.prototype = new Ctor();
|
||||
TestItemElement.__super__ = HTMLElement.prototype;
|
||||
TestItemElement.prototype.initialize = function(model) {
|
||||
TestItemElement.prototype.initialize = function (model) {
|
||||
this.model = model;
|
||||
return this;
|
||||
};
|
||||
TestItemElement.prototype.getModel = function() {
|
||||
TestItemElement.prototype.getModel = function () {
|
||||
return this.model;
|
||||
};
|
||||
|
||||
|
@ -770,18 +770,16 @@ module.exports = class GrammarRegistry {
|
||||
let result = this.textmateRegistry.getGrammars();
|
||||
if (!(params && params.includeTreeSitter)) return result;
|
||||
|
||||
let includeLegacyTreeSitterGrammars =
|
||||
atom.config.get('core.useLegacyTreeSitter') === true;
|
||||
|
||||
let modernTsGrammars = Object.values(this.wasmTreeSitterGrammarsById)
|
||||
.filter(g => g.scopeName);
|
||||
result = result.concat(modernTsGrammars);
|
||||
|
||||
if (includeLegacyTreeSitterGrammars) {
|
||||
const legacyTsGrammars = Object.values(this.treeSitterGrammarsById)
|
||||
.filter(g => g.scopeName);
|
||||
result = result.concat(legacyTsGrammars);
|
||||
}
|
||||
// We must include all legacy Tree-sitter grammars here just in case the
|
||||
// user has opted into `useTreeSitterGrammars` via a scope-specific
|
||||
// setting.
|
||||
const legacyTsGrammars = Object.values(this.treeSitterGrammarsById)
|
||||
.filter(g => g.scopeName);
|
||||
result = result.concat(legacyTsGrammars);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
@ -16,6 +16,11 @@ const FEATURE_ASYNC_PARSE = true;
|
||||
|
||||
const LINE_LENGTH_LIMIT_FOR_HIGHLIGHTING = 10000;
|
||||
|
||||
// How many milliseconds we can spend on synchronous re-parses (for indentation
|
||||
// purposes) in a given transaction before we fall back to asynchronous
|
||||
// indentation instead. Only comes into play when async indentation is enabled.
|
||||
const REPARSE_BUDGET_PER_TRANSACTION_MILLIS = 10
|
||||
|
||||
const PARSE_JOB_LIMIT_MICROS = 3000;
|
||||
const PARSERS_IN_USE = new Set();
|
||||
|
||||
@ -155,6 +160,8 @@ class WASMTreeSitterLanguageMode {
|
||||
this.syncTimeoutMicros = syncTimeoutMicros ?? PARSE_JOB_LIMIT_MICROS;
|
||||
this.useAsyncParsing = FEATURE_ASYNC_PARSE;
|
||||
this.useAsyncIndent = FEATURE_ASYNC_INDENT;
|
||||
this.transactionReparseBudgetMs = REPARSE_BUDGET_PER_TRANSACTION_MILLIS;
|
||||
this.currentTransactionReparseBudgetMs = undefined;
|
||||
|
||||
this.injectionsMarkerLayer = buffer.addMarkerLayer();
|
||||
|
||||
@ -339,6 +346,9 @@ class WASMTreeSitterLanguageMode {
|
||||
this.resolveNextTransactionPromise();
|
||||
this.transactionChangeCount = 0;
|
||||
this.autoIndentRequests = 0;
|
||||
// Since a new transaction is starting, we can reset our reparse
|
||||
// budget.
|
||||
this.currentTransactionReparseBudgetMs = this.transactionReparseBudgetMs;
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -1150,20 +1160,88 @@ class WASMTreeSitterLanguageMode {
|
||||
return indentLength / tabLength
|
||||
}
|
||||
|
||||
// In an ideal world, we would use synchronous indentation all the time. It's
|
||||
// feature-equivalent to TextMate-style indentation.
|
||||
//
|
||||
// But it requires us to be able to tell the editor, at an arbitrary point in
|
||||
// time, what the suggested indentation for a buffer row is. We might get
|
||||
// asked this question only once in a transaction — or 100 times. We don't
|
||||
// know ahead of time. And if we want to be able to answer the question
|
||||
// synchronously, we must reparse the buffer synchronously _each time_.
|
||||
//
|
||||
// That's fine in the only-one-edit case, but unacceptable in the
|
||||
// 100-edits-in-one-transaction case. The problem isn't the extra work; it's
|
||||
// the extra _lag_. We don't want the editor to freeze because we're doing
|
||||
// 100 buffer parses in a row.
|
||||
//
|
||||
// In order to do synchronous indentation most of the time while still
|
||||
// guarding against this edge case, we'll
|
||||
//
|
||||
// * start out each transaction preferring synchronous indentation, but
|
||||
// * switch to async indentation if our time budget is exceeded in any one
|
||||
// transaction.
|
||||
//
|
||||
shouldUseAsyncIndent() {
|
||||
let result = true;
|
||||
if (!this.useAsyncParsing || !this.useAsyncIndent) result = false;
|
||||
if (this.currentTransactionReparseBudgetMs > 0) {
|
||||
result = false;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Get the suggested indentation level for an existing line in the buffer.
|
||||
//
|
||||
// * bufferRow - A {Number} indicating the buffer row
|
||||
// * tabLength - A {Number} signifying the length of a tab, in spaces,
|
||||
// * `bufferRow` - A {Number} indicating the buffer row.
|
||||
// * `tabLength` - A {Number} signifying the length of a tab, in spaces,
|
||||
// according to the current settings of the buffer.
|
||||
// * `options` (optional) - An {Object} will the following properties,all of
|
||||
// which are optional:
|
||||
// * `options.skipBlankLines`: {Boolean} indicating whether to ignore blank
|
||||
// lines when determining which row to use as a reference row. Default is
|
||||
// `true`. Irrelevant if `options.comparisonRow` is specified.
|
||||
// * `options.skipDedentCheck`: {Boolean} indicationg whether to skip the
|
||||
// second phase of the check and determine only if the current row should
|
||||
// _start out_ indented from the reference row.
|
||||
// * `options.preserveLeadingWhitespace`: {Boolean} indicating whether to
|
||||
// adjust the returned number to account for the indentation level of any
|
||||
// whitespace that may already be on the row. Defaults to `false`.
|
||||
// * `options.forceTreeParse`: {Boolean} indicating whether to force this
|
||||
// method to synchronously parse the buffer into a tree, even if it
|
||||
// otherwise would not. Defaults to `false`.
|
||||
// * `options.comparisonRow`: A {Number} specifying the row to use as a
|
||||
// reference row. Must be a valid row that occurs earlier in the buffer
|
||||
// than `row`. If omitted, this method will determine the reference row on
|
||||
// its own.
|
||||
//
|
||||
// Will return either an immediate result or a {Promise}, depending on
|
||||
// whether it can make a synchronous decision.
|
||||
//
|
||||
// When acting synchronously, this method returns a {Number}, or {null} if
|
||||
// this method cannot make a suggestion. It will return a synchronous result
|
||||
// if (a) the tree is clean, (b) the language mode decides it can afford to
|
||||
// do a synchronous re-parse of the buffer, or (c) `options.forceTreeParse`
|
||||
// is `true`. Otherwise, this method will wait until the end of the current
|
||||
// buffer transaction.
|
||||
//
|
||||
// When acting asynchronously, this method may or may not be able to give an
|
||||
// answer. If it can, it will return a {Promise} that resolves with a
|
||||
// {Number} signifying the suggested indentation level. If it can't — because
|
||||
// it thinks the content has been altered too much for it to make a
|
||||
// suggestion — it will return a {Promise} that resolves with {undefined},
|
||||
// signalling that a fallback style of indentation adjustment should take
|
||||
// place.
|
||||
//
|
||||
// Returns a {Number}, or {null} if this method cannot make a suggestion.
|
||||
suggestedIndentForBufferRow(row, tabLength, rawOptions = {}) {
|
||||
// NOTE: This method is hundreds of lines long, but so much of that total
|
||||
// consists of comments like this one — because this is a hard thing to
|
||||
// intuit. This code needs lots of explanation, but that doesn't mean
|
||||
// that the logic is impossibly complex.
|
||||
let root = this.rootLanguageLayer;
|
||||
if (row === 0) { return 0; }
|
||||
if (!root || !root.tree || !root.ready) { return null; }
|
||||
|
||||
let options = {
|
||||
allowMatchCapture: true,
|
||||
skipBlankLines: true,
|
||||
skipDedentCheck: false,
|
||||
preserveLeadingWhitespace: false,
|
||||
@ -1207,9 +1285,9 @@ class WASMTreeSitterLanguageMode {
|
||||
this.buffer.lineForRow(comparisonRow), tabLength);
|
||||
}
|
||||
|
||||
// TODO: What's the right place to measure from? Often we're here because
|
||||
// the user just hit Enter, which means we'd run before injection layers
|
||||
// have been re-parsed. Hence the injection's language layer might not know
|
||||
// What's the right place to measure from? Often we're here because the
|
||||
// user just hit Enter, which means we'd run before injection layers have
|
||||
// been re-parsed. Hence the injection's language layer might not know
|
||||
// whether it controls the point at the cursor. So instead we look for the
|
||||
// layer that controls the point at the end of the comparison row. This may
|
||||
// not always be correct, but we'll find out.
|
||||
@ -1267,39 +1345,75 @@ class WASMTreeSitterLanguageMode {
|
||||
}
|
||||
|
||||
if (!indentTree) {
|
||||
if (!controllingLayer.treeIsDirty || options.forceTreeParse || !this.useAsyncParsing || !this.useAsyncIndent) {
|
||||
if (!controllingLayer.treeIsDirty || options.forceTreeParse || !this.shouldUseAsyncIndent()) {
|
||||
// If we're in this code path, it either means the tree is clean (the
|
||||
// `get` path) or that we're willing to spend the time to do a
|
||||
// synchronous reparse (the `parse` path). Either way, we'll be able to
|
||||
// deliver a synchronous answer to the question.
|
||||
indentTree = controllingLayer.getOrParseTree();
|
||||
} else {
|
||||
// We can't answer this yet because we don't yet have a new syntax
|
||||
// tree. Return a promise that will fulfill once we can determine the
|
||||
// right indent level.
|
||||
// tree, and are unwilling to spend time doing a synchronous re-parse.
|
||||
// Return a promise that will fulfill once the transaction is over.
|
||||
//
|
||||
// TODO: For async, we might need an approach where we suggest a
|
||||
// preliminary indent level and then follow up later with a more
|
||||
// accurate one. It's a bit disorienting that the editor falls back to
|
||||
// an indent level of `0` when a newline is inserted.
|
||||
let comparisonRowText = this.buffer.lineForRow(comparisonRow)
|
||||
let rowText = this.buffer.lineForRow(row)
|
||||
return this.atTransactionEnd().then(({ changeCount }) => {
|
||||
let shouldFallback = false;
|
||||
// If this was the only change in the transaction, then we can
|
||||
// definitely adjust the indentation level after the fact. If not,
|
||||
// then we might still be able to make indentation decisions in cases
|
||||
// where they do not affect one another.
|
||||
//
|
||||
// Hence if neither the comparison row nor the current row has had
|
||||
// its contents change in any way since we were first called, we will
|
||||
// assume it's safe to adjust the indentation level after the fact.
|
||||
// Otherwise we'll fall back to a single transaction-wide indentation
|
||||
// adjustment — fewer tree parses, but more likely to produce unusual
|
||||
// results.
|
||||
if (changeCount > 1) {
|
||||
// There were multiple changes in this transaction, so it's not
|
||||
// safe to assume that the original row still needs its indentation
|
||||
// adjusted. The row could've been shifted up or down by other
|
||||
// edits, or it could've been deleted entirely.
|
||||
if (comparisonRowText !== this.buffer.lineForRow(comparisonRow)) {
|
||||
shouldFallback = true;
|
||||
}
|
||||
if (rowText !== this.buffer.lineForRow(row)) {
|
||||
shouldFallback = true;
|
||||
}
|
||||
}
|
||||
if (shouldFallback) {
|
||||
// We're now revisiting this indentation question at the end of the
|
||||
// transaction. Other changes may have taken place since we were
|
||||
// first asked what the indent level should be for this line. So
|
||||
// how do we know if the question is still relevant? After all, the
|
||||
// text that was on this row earlier might be on some other row
|
||||
// now.
|
||||
//
|
||||
// Instead, we return `undefined` here, and the `TextEditor` will
|
||||
// understand that its only recourse is to auto-indent the whole
|
||||
// extent of the transaction instead.
|
||||
// So we compare the text that was on the row when we were first
|
||||
// called… to the text that is on the row now that the transaction
|
||||
// is over. If they're the same, that's a _strong_ indicator that
|
||||
// the result we return will still be relevant.
|
||||
//
|
||||
// If not, as is the case in this code path, we return `undefined`,
|
||||
// signalling to the `TextEditor` that its only recourse is to
|
||||
// auto-indent the whole extent of the transaction instead.
|
||||
return undefined;
|
||||
}
|
||||
// Otherwise, it's safe to auto-indent this line alone, because it
|
||||
// was the only change in this transaction. But we've retained the
|
||||
// original values for `comparisonRow` and `comparisonRowIndent`
|
||||
// because that's the proper basis from which to determine the given
|
||||
// row's indent level.
|
||||
|
||||
// If we get this far, it's safe to auto-indent this line. Either it
|
||||
// was the only change in its transaction or the other changes
|
||||
// happened on different lines. But we've retained the original
|
||||
// values for `comparisonRow` and `comparisonRowIndent` because
|
||||
// that's the proper basis from which to determine the given row's
|
||||
// indent level.
|
||||
let result = this.suggestedIndentForBufferRow(row, tabLength, {
|
||||
...rawOptions,
|
||||
comparisonRow: comparisonRow,
|
||||
comparisonRowIndent: comparisonRowIndent,
|
||||
tree: controllingLayer.tree
|
||||
tree: controllingLayer.tree,
|
||||
controllingLayer
|
||||
});
|
||||
return result;
|
||||
});
|
||||
@ -1379,6 +1493,19 @@ class WASMTreeSitterLanguageMode {
|
||||
continue;
|
||||
}
|
||||
indentDelta--;
|
||||
if (indentDelta < 0) {
|
||||
// In the _indent_ phase, the delta won't ever go lower than `0`.
|
||||
// This is because we assume that the previous line is correctly
|
||||
// indented! The only function that `dedent` serves for us in this
|
||||
// phase is canceling out an earlier `indent` and preventing false
|
||||
// positives.
|
||||
//
|
||||
// So no matter how many `dedent` tokens we see on a particular line…
|
||||
// if the _last_ token we see is an `indent` token, then it hints
|
||||
// that the next line should be indented by one level.
|
||||
indentDelta = 0;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -1444,9 +1571,9 @@ class WASMTreeSitterLanguageMode {
|
||||
|
||||
if (dedentControllingLayer && dedentControllingLayer !== controllingLayer) {
|
||||
// If this layer is different from the one we used above, then we
|
||||
// should run this layer's indents query against its own tree. If _no_
|
||||
// layers qualify at this position, we can still reluctantly use the
|
||||
// original layer.
|
||||
// should run this layer's indents query against its own tree. (If _no_
|
||||
// layers qualify at this position, we won't hit this code path, so
|
||||
// we'll reluctantly still use the original layer and tree.)
|
||||
indentsQuery = dedentControllingLayer.indentsQuery;
|
||||
indentTree = dedentControllingLayer.getOrParseTree();
|
||||
}
|
||||
@ -1510,7 +1637,8 @@ class WASMTreeSitterLanguageMode {
|
||||
}
|
||||
|
||||
// Only `@dedent` or `@match` captures can change this line's
|
||||
// indentation.
|
||||
// indentation. We handled `@match` above, so we'll filter out all non-
|
||||
// `@dedent`s now.
|
||||
if (name !== 'dedent') { continue; }
|
||||
|
||||
// Only consider a given range once, even if it's marked with multiple
|
||||
@ -1521,7 +1649,6 @@ class WASMTreeSitterLanguageMode {
|
||||
dedentDelta--;
|
||||
}
|
||||
|
||||
|
||||
// `@indent`/`@dedent` captures, no matter how many there are, can
|
||||
// dedent the current line by one level at most. To indent more than
|
||||
// that, one must use a `@match` capture.
|
||||
@ -2261,13 +2388,21 @@ class FoldResolver {
|
||||
}
|
||||
}
|
||||
|
||||
// Returns `true` if there is no non-whitespace content on this position's
|
||||
// row before this position's column.
|
||||
positionIsNotPrecededByTextOnLine(position) {
|
||||
let textForRow = this.buffer.lineForRow(position.row)
|
||||
let precedingText = textForRow.substring(0, position.column)
|
||||
return !(/\S/.test(precedingText))
|
||||
}
|
||||
|
||||
resolvePositionForDividedFold(capture) {
|
||||
let { name, node } = capture;
|
||||
if (name === 'fold.start') {
|
||||
return new Point(node.startPosition.row, Infinity);
|
||||
} else if (name === 'fold.end') {
|
||||
let end = node.startPosition;
|
||||
if (end.column === 0) {
|
||||
if (end.column === 0 || this.positionIsNotPrecededByTextOnLine(end)) {
|
||||
// If the fold ends at the start of the line, adjust it so that it
|
||||
// actually ends at the end of the previous line. This behavior is
|
||||
// implied in the existing specs.
|
||||
@ -3877,6 +4012,23 @@ class LanguageLayer {
|
||||
return ranges.some(r => r.containsPoint(point, exclusive));
|
||||
}
|
||||
|
||||
// Returns a syntax tree for the current buffer.
|
||||
//
|
||||
// By default, this method will either return the current tree (if it's up to
|
||||
// date) or synchronously parse the buffer into a new tree (if it isn't).
|
||||
//
|
||||
// If you don't want to force a re-parse and don't mind that the current tree
|
||||
// might be stale, pass `force: false` as an option.
|
||||
//
|
||||
// In certain circumstances, the new tree might be promoted to the canonical
|
||||
// tree for this layer. To prevent this, pass `anonymous: false` as an option.
|
||||
//
|
||||
// All trees returned by this method are managed by this language layer and
|
||||
// will be deleted when the next transaction is complete. Retaining a
|
||||
// reference to the returned tree will not prevent this from happening. To
|
||||
// opt into managing the life cycle of the returned tree, copy it immediately
|
||||
// when you receive it.
|
||||
//
|
||||
getOrParseTree({ force = true, anonymous = false } = {}) {
|
||||
if (this.tree && (!this.treeIsDirty || !force)) { return this.tree; }
|
||||
|
||||
@ -3916,12 +4068,27 @@ class LanguageLayer {
|
||||
// probably isn't a way to “fix” this for injection layers except through
|
||||
// cutting down on off-schedule parses.
|
||||
//
|
||||
let then = performance.now()
|
||||
let tree = this.languageMode.parse(
|
||||
this.language,
|
||||
this.tree,
|
||||
ranges,
|
||||
// { tag: `Re-parsing ${this.inspect()}` }
|
||||
);
|
||||
let now = performance.now()
|
||||
|
||||
let parseTime = now - then;
|
||||
|
||||
// Since we can't look into the future, we don't know how many times during
|
||||
// this transaction we'll be asked to make indentation sugestions. If we
|
||||
// knew ahead of time, we'd be able to decide at the beginning of a
|
||||
// transaction whether we could afford to do synchronous indentation.
|
||||
//
|
||||
// Instead, we do the next best thing: we start out doing synchronous
|
||||
// indentation, then fall back to asynchronous indentation once we've
|
||||
// exceeded our time budget. So we keep track of how long each reparse
|
||||
// takes and subtract it from our budget.
|
||||
this.languageMode.currentTransactionReparseBudgetMs -= parseTime;
|
||||
|
||||
if (this.depth === 0 && !anonymous) {
|
||||
this.tree = tree;
|
||||
|
101
vendor/web-tree-sitter/tree-sitter.js
vendored
101
vendor/web-tree-sitter/tree-sitter.js
vendored
@ -1,9 +1,9 @@
|
||||
var Module = typeof Module != "undefined" ? Module : {};
|
||||
var TreeSitter = function() {
|
||||
function checkForAsmVersion(prop) {
|
||||
if (!(prop in Module['asm'])) {
|
||||
console.warn(`Warning: parser wants to call function ${prop}, but it is not defined. If parsing fails, this is probably the reason why. Please report this to the Pulsar team so that this parser can be supported properly.`);
|
||||
}
|
||||
if (!(prop in Module['asm'])) {
|
||||
console.warn(`Warning: parser wants to call function ${prop}, but it is not defined. If parsing fails, this is probably the reason why. Please report this to the Pulsar team so that this parser can be supported properly.`);
|
||||
}
|
||||
}
|
||||
var initPromise;
|
||||
var document = typeof window == "object" ? {
|
||||
@ -57,11 +57,11 @@ var TreeSitter = function() {
|
||||
}
|
||||
return ret
|
||||
};
|
||||
readAsync = (filename, onload, onerror) => {
|
||||
readAsync = (filename, onload, onerror, binary = true) => {
|
||||
filename = isFileURI(filename) ? new URL(filename) : nodePath.normalize(filename);
|
||||
fs.readFile(filename, function(err, data) {
|
||||
fs.readFile(filename, binary ? undefined : "utf8", (err, data) => {
|
||||
if (err) onerror(err);
|
||||
else onload(data.buffer)
|
||||
else onload(binary ? data.buffer : data)
|
||||
})
|
||||
};
|
||||
if (!Module["thisProgram"] && process.argv.length > 1) {
|
||||
@ -75,9 +75,7 @@ var TreeSitter = function() {
|
||||
process.exitCode = status;
|
||||
throw toThrow
|
||||
};
|
||||
Module["inspect"] = function() {
|
||||
return "[Emscripten Module object]"
|
||||
}
|
||||
Module["inspect"] = () => "[Emscripten Module object]"
|
||||
} else if (ENVIRONMENT_IS_WEB || ENVIRONMENT_IS_WORKER) {
|
||||
if (ENVIRONMENT_IS_WORKER) {
|
||||
scriptDirectory = self.location.href
|
||||
@ -230,6 +228,10 @@ var TreeSitter = function() {
|
||||
var runDependencyWatcher = null;
|
||||
var dependenciesFulfilled = null;
|
||||
|
||||
function getUniqueRunDependency(id) {
|
||||
return id
|
||||
}
|
||||
|
||||
function addRunDependency(id) {
|
||||
runDependencies++;
|
||||
if (Module["monitorRunDependencies"]) {
|
||||
@ -301,35 +303,29 @@ var TreeSitter = function() {
|
||||
if (typeof fetch == "function" && !isFileURI(binaryFile)) {
|
||||
return fetch(binaryFile, {
|
||||
credentials: "same-origin"
|
||||
}).then(function(response) {
|
||||
}).then(response => {
|
||||
if (!response["ok"]) {
|
||||
throw "failed to load wasm binary file at '" + binaryFile + "'"
|
||||
}
|
||||
return response["arrayBuffer"]()
|
||||
}).catch(function() {
|
||||
return getBinary(binaryFile)
|
||||
})
|
||||
}).catch(() => getBinary(binaryFile))
|
||||
} else {
|
||||
if (readAsync) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
readAsync(binaryFile, function(response) {
|
||||
resolve(new Uint8Array(response))
|
||||
}, reject)
|
||||
return new Promise((resolve, reject) => {
|
||||
readAsync(binaryFile, response => resolve(new Uint8Array(response)), reject)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return Promise.resolve().then(function() {
|
||||
return getBinary(binaryFile)
|
||||
})
|
||||
return Promise.resolve().then(() => getBinary(binaryFile))
|
||||
}
|
||||
|
||||
function instantiateArrayBuffer(binaryFile, imports, receiver) {
|
||||
return getBinaryPromise(binaryFile).then(function(binary) {
|
||||
return getBinaryPromise(binaryFile).then(binary => {
|
||||
return WebAssembly.instantiate(binary, imports)
|
||||
}).then(function(instance) {
|
||||
}).then(instance => {
|
||||
return instance
|
||||
}).then(receiver, function(reason) {
|
||||
}).then(receiver, reason => {
|
||||
err("failed to asynchronously prepare wasm: " + reason);
|
||||
abort(reason)
|
||||
})
|
||||
@ -339,7 +335,7 @@ var TreeSitter = function() {
|
||||
if (!binary && typeof WebAssembly.instantiateStreaming == "function" && !isDataURI(binaryFile) && !isFileURI(binaryFile) && !ENVIRONMENT_IS_NODE && typeof fetch == "function") {
|
||||
return fetch(binaryFile, {
|
||||
credentials: "same-origin"
|
||||
}).then(function(response) {
|
||||
}).then(response => {
|
||||
var result = WebAssembly.instantiateStreaming(response, imports);
|
||||
return result.then(callback, function(reason) {
|
||||
err("wasm streaming compile failed: " + reason);
|
||||
@ -1001,26 +997,20 @@ var TreeSitter = function() {
|
||||
var instance = new WebAssembly.Instance(binary, info);
|
||||
return Promise.resolve(postInstantiation(instance))
|
||||
}
|
||||
return WebAssembly.instantiate(binary, info).then(function(result) {
|
||||
return postInstantiation(result.instance)
|
||||
})
|
||||
return WebAssembly.instantiate(binary, info).then(result => postInstantiation(result.instance))
|
||||
}
|
||||
var module = binary instanceof WebAssembly.Module ? binary : new WebAssembly.Module(binary);
|
||||
var instance = new WebAssembly.Instance(module, info);
|
||||
return postInstantiation(instance)
|
||||
}
|
||||
if (flags.loadAsync) {
|
||||
return metadata.neededDynlibs.reduce(function(chain, dynNeeded) {
|
||||
return chain.then(function() {
|
||||
return metadata.neededDynlibs.reduce((chain, dynNeeded) => {
|
||||
return chain.then(() => {
|
||||
return loadDynamicLibrary(dynNeeded, flags)
|
||||
})
|
||||
}, Promise.resolve()).then(function() {
|
||||
return loadModule()
|
||||
})
|
||||
}, Promise.resolve()).then(loadModule)
|
||||
}
|
||||
metadata.neededDynlibs.forEach(function(dynNeeded) {
|
||||
loadDynamicLibrary(dynNeeded, flags, localScope)
|
||||
});
|
||||
metadata.neededDynlibs.forEach(needed => loadDynamicLibrary(needed, flags, localScope));
|
||||
return loadModule()
|
||||
}
|
||||
|
||||
@ -1048,6 +1038,22 @@ var TreeSitter = function() {
|
||||
}
|
||||
}
|
||||
|
||||
function asyncLoad(url, onload, onerror, noRunDep) {
|
||||
var dep = !noRunDep ? getUniqueRunDependency("al " + url) : "";
|
||||
readAsync(url, arrayBuffer => {
|
||||
assert(arrayBuffer, 'Loading data file "' + url + '" failed (no arrayBuffer).');
|
||||
onload(new Uint8Array(arrayBuffer));
|
||||
if (dep) removeRunDependency(dep)
|
||||
}, event => {
|
||||
if (onerror) {
|
||||
onerror()
|
||||
} else {
|
||||
throw 'Loading data file "' + url + '" failed.'
|
||||
}
|
||||
});
|
||||
if (dep) addRunDependency(dep)
|
||||
}
|
||||
|
||||
function loadDynamicLibrary(libName, flags = {
|
||||
global: true,
|
||||
nodelete: true
|
||||
@ -1086,7 +1092,7 @@ var TreeSitter = function() {
|
||||
var libFile = locateFile(libName);
|
||||
if (flags.loadAsync) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
readAsync(libFile, data => resolve(new Uint8Array(data)), reject)
|
||||
asyncLoad(libFile, data => resolve(data), reject)
|
||||
})
|
||||
}
|
||||
if (!readBinary) {
|
||||
@ -1148,8 +1154,8 @@ var TreeSitter = function() {
|
||||
return
|
||||
}
|
||||
addRunDependency("loadDylibs");
|
||||
dynamicLibraries.reduce(function(chain, lib) {
|
||||
return chain.then(function() {
|
||||
dynamicLibraries.reduce((chain, lib) => {
|
||||
return chain.then(() => {
|
||||
return loadDynamicLibrary(lib, {
|
||||
loadAsync: true,
|
||||
global: true,
|
||||
@ -1157,7 +1163,7 @@ var TreeSitter = function() {
|
||||
allowUndefined: true
|
||||
})
|
||||
})
|
||||
}, Promise.resolve()).then(function() {
|
||||
}, Promise.resolve()).then(() => {
|
||||
reportUndefinedSymbols();
|
||||
removeRunDependency("loadDylibs")
|
||||
})
|
||||
@ -1179,7 +1185,7 @@ var TreeSitter = function() {
|
||||
HEAP32[ptr >> 2] = value;
|
||||
break;
|
||||
case "i64":
|
||||
tempI64 = [value >>> 0, (tempDouble = value, +Math.abs(tempDouble) >= 1 ? tempDouble > 0 ? (Math.min(+Math.floor(tempDouble / 4294967296), 4294967295) | 0) >>> 0 : ~~+Math.ceil((tempDouble - +(~~tempDouble >>> 0)) / 4294967296) >>> 0 : 0)], HEAP32[ptr >> 2] = tempI64[0], HEAP32[ptr + 4 >> 2] = tempI64[1];
|
||||
tempI64 = [value >>> 0, (tempDouble = value, +Math.abs(tempDouble) >= 1 ? tempDouble > 0 ? +Math.floor(tempDouble / 4294967296) >>> 0 : ~~+Math.ceil((tempDouble - +(~~tempDouble >>> 0)) / 4294967296) >>> 0 : 0)], HEAP32[ptr >> 2] = tempI64[0], HEAP32[ptr + 4 >> 2] = tempI64[1];
|
||||
break;
|
||||
case "float":
|
||||
HEAPF32[ptr >> 2] = value;
|
||||
@ -1307,19 +1313,19 @@ var TreeSitter = function() {
|
||||
HEAP32[buf + 20 >> 2] = stat.uid;
|
||||
HEAP32[buf + 24 >> 2] = stat.gid;
|
||||
HEAP32[buf + 28 >> 2] = stat.rdev;
|
||||
tempI64 = [stat.size >>> 0, (tempDouble = stat.size, +Math.abs(tempDouble) >= 1 ? tempDouble > 0 ? (Math.min(+Math.floor(tempDouble / 4294967296), 4294967295) | 0) >>> 0 : ~~+Math.ceil((tempDouble - +(~~tempDouble >>> 0)) / 4294967296) >>> 0 : 0)], HEAP32[buf + 40 >> 2] = tempI64[0], HEAP32[buf + 44 >> 2] = tempI64[1];
|
||||
tempI64 = [stat.size >>> 0, (tempDouble = stat.size, +Math.abs(tempDouble) >= 1 ? tempDouble > 0 ? +Math.floor(tempDouble / 4294967296) >>> 0 : ~~+Math.ceil((tempDouble - +(~~tempDouble >>> 0)) / 4294967296) >>> 0 : 0)], HEAP32[buf + 40 >> 2] = tempI64[0], HEAP32[buf + 44 >> 2] = tempI64[1];
|
||||
HEAP32[buf + 48 >> 2] = 4096;
|
||||
HEAP32[buf + 52 >> 2] = stat.blocks;
|
||||
var atime = stat.atime.getTime();
|
||||
var mtime = stat.mtime.getTime();
|
||||
var ctime = stat.ctime.getTime();
|
||||
tempI64 = [Math.floor(atime / 1e3) >>> 0, (tempDouble = Math.floor(atime / 1e3), +Math.abs(tempDouble) >= 1 ? tempDouble > 0 ? (Math.min(+Math.floor(tempDouble / 4294967296), 4294967295) | 0) >>> 0 : ~~+Math.ceil((tempDouble - +(~~tempDouble >>> 0)) / 4294967296) >>> 0 : 0)], HEAP32[buf + 56 >> 2] = tempI64[0], HEAP32[buf + 60 >> 2] = tempI64[1];
|
||||
tempI64 = [Math.floor(atime / 1e3) >>> 0, (tempDouble = Math.floor(atime / 1e3), +Math.abs(tempDouble) >= 1 ? tempDouble > 0 ? +Math.floor(tempDouble / 4294967296) >>> 0 : ~~+Math.ceil((tempDouble - +(~~tempDouble >>> 0)) / 4294967296) >>> 0 : 0)], HEAP32[buf + 56 >> 2] = tempI64[0], HEAP32[buf + 60 >> 2] = tempI64[1];
|
||||
HEAPU32[buf + 64 >> 2] = atime % 1e3 * 1e3;
|
||||
tempI64 = [Math.floor(mtime / 1e3) >>> 0, (tempDouble = Math.floor(mtime / 1e3), +Math.abs(tempDouble) >= 1 ? tempDouble > 0 ? (Math.min(+Math.floor(tempDouble / 4294967296), 4294967295) | 0) >>> 0 : ~~+Math.ceil((tempDouble - +(~~tempDouble >>> 0)) / 4294967296) >>> 0 : 0)], HEAP32[buf + 72 >> 2] = tempI64[0], HEAP32[buf + 76 >> 2] = tempI64[1];
|
||||
tempI64 = [Math.floor(mtime / 1e3) >>> 0, (tempDouble = Math.floor(mtime / 1e3), +Math.abs(tempDouble) >= 1 ? tempDouble > 0 ? +Math.floor(tempDouble / 4294967296) >>> 0 : ~~+Math.ceil((tempDouble - +(~~tempDouble >>> 0)) / 4294967296) >>> 0 : 0)], HEAP32[buf + 72 >> 2] = tempI64[0], HEAP32[buf + 76 >> 2] = tempI64[1];
|
||||
HEAPU32[buf + 80 >> 2] = mtime % 1e3 * 1e3;
|
||||
tempI64 = [Math.floor(ctime / 1e3) >>> 0, (tempDouble = Math.floor(ctime / 1e3), +Math.abs(tempDouble) >= 1 ? tempDouble > 0 ? (Math.min(+Math.floor(tempDouble / 4294967296), 4294967295) | 0) >>> 0 : ~~+Math.ceil((tempDouble - +(~~tempDouble >>> 0)) / 4294967296) >>> 0 : 0)], HEAP32[buf + 88 >> 2] = tempI64[0], HEAP32[buf + 92 >> 2] = tempI64[1];
|
||||
tempI64 = [Math.floor(ctime / 1e3) >>> 0, (tempDouble = Math.floor(ctime / 1e3), +Math.abs(tempDouble) >= 1 ? tempDouble > 0 ? +Math.floor(tempDouble / 4294967296) >>> 0 : ~~+Math.ceil((tempDouble - +(~~tempDouble >>> 0)) / 4294967296) >>> 0 : 0)], HEAP32[buf + 88 >> 2] = tempI64[0], HEAP32[buf + 92 >> 2] = tempI64[1];
|
||||
HEAPU32[buf + 96 >> 2] = ctime % 1e3 * 1e3;
|
||||
tempI64 = [stat.ino >>> 0, (tempDouble = stat.ino, +Math.abs(tempDouble) >= 1 ? tempDouble > 0 ? (Math.min(+Math.floor(tempDouble / 4294967296), 4294967295) | 0) >>> 0 : ~~+Math.ceil((tempDouble - +(~~tempDouble >>> 0)) / 4294967296) >>> 0 : 0)], HEAP32[buf + 104 >> 2] = tempI64[0], HEAP32[buf + 108 >> 2] = tempI64[1];
|
||||
tempI64 = [stat.ino >>> 0, (tempDouble = stat.ino, +Math.abs(tempDouble) >= 1 ? tempDouble > 0 ? +Math.floor(tempDouble / 4294967296) >>> 0 : ~~+Math.ceil((tempDouble - +(~~tempDouble >>> 0)) / 4294967296) >>> 0 : 0)], HEAP32[buf + 104 >> 2] = tempI64[0], HEAP32[buf + 108 >> 2] = tempI64[1];
|
||||
return 0
|
||||
},
|
||||
doMsync: function(addr, stream, len, flags, offset) {
|
||||
@ -1371,7 +1377,7 @@ var TreeSitter = function() {
|
||||
if (isNaN(offset)) return 61;
|
||||
var stream = SYSCALLS.getStreamFromFD(fd);
|
||||
FS.llseek(stream, offset, whence);
|
||||
tempI64 = [stream.position >>> 0, (tempDouble = stream.position, +Math.abs(tempDouble) >= 1 ? tempDouble > 0 ? (Math.min(+Math.floor(tempDouble / 4294967296), 4294967295) | 0) >>> 0 : ~~+Math.ceil((tempDouble - +(~~tempDouble >>> 0)) / 4294967296) >>> 0 : 0)], HEAP32[newOffset >> 2] = tempI64[0], HEAP32[newOffset + 4 >> 2] = tempI64[1];
|
||||
tempI64 = [stream.position >>> 0, (tempDouble = stream.position, +Math.abs(tempDouble) >= 1 ? tempDouble > 0 ? +Math.floor(tempDouble / 4294967296) >>> 0 : ~~+Math.ceil((tempDouble - +(~~tempDouble >>> 0)) / 4294967296) >>> 0 : 0)], HEAP32[newOffset >> 2] = tempI64[0], HEAP32[newOffset + 4 >> 2] = tempI64[1];
|
||||
if (stream.getdents && offset === 0 && whence === 0) stream.getdents = null;
|
||||
return 0
|
||||
} catch (e) {
|
||||
@ -2896,8 +2902,7 @@ var TreeSitter = function() {
|
||||
}))
|
||||
}
|
||||
}
|
||||
const loadModule = typeof loadSideModule === "function" ? loadSideModule : loadWebAssemblyModule;
|
||||
return bytes.then(bytes => loadModule(bytes, {
|
||||
return bytes.then(bytes => loadWebAssemblyModule(bytes, {
|
||||
loadAsync: true
|
||||
})).then(mod => {
|
||||
const symbolNames = Object.keys(mod);
|
||||
|
BIN
vendor/web-tree-sitter/tree-sitter.wasm
vendored
BIN
vendor/web-tree-sitter/tree-sitter.wasm
vendored
Binary file not shown.
Loading…
Reference in New Issue
Block a user