Merge branch 'master' into tree-sitter-ruby-folds

This commit is contained in:
Maurício Szabo 2024-03-21 19:49:02 -03:00 committed by GitHub
commit ab2df0ebf1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
28 changed files with 757 additions and 255 deletions

View File

@ -63,7 +63,7 @@ arm_linux_task:
memory: 8G
env:
USE_SYSTEM_FPM: 'true'
ROLLING_UPLOAD_TOKEN: ENCRYPTED[e19187268935fd1a2e1db3304105cc4bf37d5f5a46ba519a1b952f8ac9a3d01982fa9b8274b838547aa37920774b7518]
ROLLING_UPLOAD_TOKEN: ENCRYPTED[f935c396a9f4bca108ec2fdedb00dbc9be2f4c411f100d577acdab42db59ea134be059ce8535396db8222a2b1eb68c27]
prepare_script:
- apt-get update
- export DEBIAN_FRONTEND="noninteractive"
@ -123,7 +123,7 @@ silicon_mac_task:
only_if: $CIRRUS_CRON != "" || $CIRRUS_TAG != ""
skip: $CIRRUS_CHANGE_IN_REPO == $CIRRUS_LAST_GREEN_CHANGE
macos_instance:
image: ghcr.io/cirruslabs/macos-monterey-xcode:14
image: ghcr.io/cirruslabs/macos-ventura-xcode:latest
memory: 8G
env:
CSC_LINK: ENCRYPTED[0078015a03bb6cfdbd80113ae5bbb6f448fd4bbbc40efd81bf2cb1554373046b475a4d7c77e3e3e82ac1ce2f7e3d2da5]
@ -131,39 +131,45 @@ silicon_mac_task:
APPLEID: ENCRYPTED[549ce052bd5666dba5245f4180bf93b74ed206fe5e6e7c8f67a8596d3767c1f682b84e347b326ac318c62a07c8844a57]
APPLEID_PASSWORD: ENCRYPTED[774c3307fd3b62660ecf5beb8537a24498c76e8d90d7f28e5bc816742fd8954a34ffed13f9aa2d1faf66ce08b4496e6f]
TEAM_ID: ENCRYPTED[11f3fedfbaf4aff1859bf6c105f0437ace23d84f5420a2c1cea884fbfa43b115b7834a463516d50cb276d4c4d9128b49]
ROLLING_UPLOAD_TOKEN: ENCRYPTED[e19187268935fd1a2e1db3304105cc4bf37d5f5a46ba519a1b952f8ac9a3d01982fa9b8274b838547aa37920774b7518]
ROLLING_UPLOAD_TOKEN: ENCRYPTED[f935c396a9f4bca108ec2fdedb00dbc9be2f4c411f100d577acdab42db59ea134be059ce8535396db8222a2b1eb68c27]
prepare_script:
- brew update
- brew install node@16 yarn git python@$PYTHON_VERSION
- python3 -m pip install setuptools
- brew uninstall node
- brew install git python@$PYTHON_VERSION python-setuptools
- git submodule init
- git submodule update
- ln -s /opt/homebrew/bin/python$PYTHON_VERSION /opt/homebrew/bin/python
- export PATH="/opt/homebrew/bin:/opt/homebrew/opt/node@16/bin:$PATH"
- export PATH="/opt/homebrew/bin:$PATH"
- mkdir tj_n && cd tj_n
- curl -L https://github.com/tj/n/archive/0ce85771fdff8f4b3e09ade700461b4f58a64444.tar.gz -O
- tar xf 0ce85771fdff8f4b3e09ade700461b4f58a64444.tar.gz
- sudo bash ./n-0ce85771fdff8f4b3e09ade700461b4f58a64444/bin/n 16
- cd ..
- sudo npm install -g yarn
- sed -i -e "s/[0-9]*-dev/`date -u +%Y%m%d%H`/g" package.json
install_script:
- export PATH="/opt/homebrew/bin:/opt/homebrew/opt/node@16/bin:$PATH"
- export PATH="/opt/homebrew/bin:$PATH"
- yarn install --ignore-engines || yarn install --ignore-engines
build_script:
- export PATH="/opt/homebrew/bin:/opt/homebrew/opt/node@16/bin:$PATH"
- export PATH="/opt/homebrew/bin:$PATH"
- yarn build
- yarn run build:apm
build_binary_script:
- export PATH="/opt/homebrew/bin:/opt/homebrew/opt/node@16/bin:$PATH"
- export PATH="/opt/homebrew/bin:$PATH"
- yarn dist || yarn dist
rename_binary_script:
- export PATH="/opt/homebrew/bin:/opt/homebrew/opt/node@16/bin:$PATH"
- export PATH="/opt/homebrew/bin:$PATH"
- node script/rename.js "Silicon.Mac"
binary_artifacts:
path: ./binaries/*
test_script:
- export PATH="/opt/homebrew/bin:/opt/homebrew/opt/node@16/bin:$PATH"
- export PATH="/opt/homebrew/bin:$PATH"
- rm -R node_modules/electron; yarn install --check-files
- hdiutil mount binaries/*Pulsar*dmg
- export BINARY_NAME=`ls /Volumes/Pulsar*/Pulsar.app/Contents/MacOS/Pulsar`
- PLAYWRIGHT_JUNIT_OUTPUT_NAME=report.xml npx playwright test --reporter=junit,list
rolling_upload_script:
- export PATH="/opt/homebrew/bin:/opt/homebrew/opt/node@16/bin:$PATH"
- export PATH="/opt/homebrew/bin:$PATH"
- cd ./script/rolling-release-scripts
- npm install
- node ./rolling-release-binary-upload.js cirrus
@ -178,7 +184,7 @@ silicon_mac_task:
# intel_mac_task:
# alias: mac
# macos_instance:
# image: ghcr.io/cirruslabs/macos-monterey-xcode:14
# image: ghcr.io/cirruslabs/macos-ventura-xcode:latest
# memory: 8G
# env:
# CSC_LINK: ENCRYPTED[0078015a03bb6cfdbd80113ae5bbb6f448fd4bbbc40efd81bf2cb1554373046b475a4d7c77e3e3e82ac1ce2f7e3d2da5]
@ -191,30 +197,32 @@ silicon_mac_task:
# - echo A | softwareupdate --install-rosetta
# - arch -x86_64 xcode-select --install
# - arch -x86_64 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"
# - export PATH="/usr/local/opt/node@16/bin:/usr/local/bin:$PATH"
# - export PATH="/usr/local/bin:$PATH"
# - arch -x86_64 brew update
# - arch -x86_64 brew install node@16 yarn git python@$PYTHON_VERSION
# - arch -x86_64 brew uninstall node
# - arch -x86_64 brew install node@16 git python@$PYTHON_VERSION python-setuptools
# - ln -s /usr/local/bin/python$PYTHON_VERSION /usr/local/bin/python
# - npm install -g yarn
# - git submodule init
# - git submodule update
# - sed -i -e "s/[0-9]*-dev/`date -u +%Y%m%d%H`/g" package.json
# install_script:
# - export PATH="/usr/local/opt/node@16/bin:/usr/local/bin:$PATH"
# - export PATH="/usr/local/bin:$PATH"
# - arch -x86_64 npx yarn install --ignore-engines || arch -x86_64 npx yarn install --ignore-engines
# build_script:
# - export PATH="/usr/local/opt/node@16/bin:/usr/local/bin:$PATH"
# - export PATH="/usr/local/bin:$PATH"
# - arch -x86_64 npx yarn build
# - arch -x86_64 yarn run build:apm
# build_binary_script:
# - export PATH="/usr/local/opt/node@16/bin:/usr/local/bin:$PATH"
# - export PATH="/usr/local/bin:$PATH"
# - arch -x86_64 npx yarn dist || arch -x86_64 npx yarn dist
# rename_binary_script:
# - export PATH="/usr/local/opt/node@16/bin:/usr/local/bin:$PATH"
# - export PATH="/usr/local/bin:$PATH"
# - node script/rename.js "Intel.Mac"
# binary_artifacts:
# path: ./binaries/*
# test_script:
# - export PATH="/usr/local/opt/node@16/bin:/usr/local/bin:$PATH"
# - export PATH="/usr/local/bin:$PATH"
# - rm -R node_modules/electron; yarn install --check-files
# - hdiutil mount binaries/*Pulsar*dmg
# - export BINARY_NAME=`ls /Volumes/Pulsar*/Pulsar.app/Contents/MacOS/Pulsar`

View File

@ -166,10 +166,14 @@ module.exports = class FuzzyFinderView {
this.moveToCaretPosition(caretPosition)
} else if (!uri) {
this.cancel()
} else if (fs.lstatSync(uri).isDirectory()) {
this.selectListView.update({errorMessage: 'Selected path is a directory'})
setTimeout(() => { this.selectListView.update({errorMessage: null}) }, 2000)
} else {
try {
if (fs.lstatSync(uri).isDirectory()) {
this.selectListView.update({errorMessage: 'Selected path is a directory'})
setTimeout(() => { this.selectListView.update({errorMessage: null}) }, 2000)
return
}
} catch (e) {}
const caretPosition = this.getCaretPosition()
this.cancel()
this.openURI(uri, caretPosition, openOptions)

View File

@ -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;
}

View File

@ -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 items language on the right side of Pulsars 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 users settings."
}
}
}

View File

@ -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);
}
}
});
});
});

View File

@ -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;

View File

@ -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
; =========

View File

@ -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))

View File

@ -8,7 +8,7 @@ exports.activate = function () {
return language;
},
content(node) {
return node.lastNamedChild;
return node.descendantsOfType('preproc_arg');
}
});
}

View File

@ -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

View File

@ -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")

View File

@ -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)

View File

@ -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 theyre delimited with another character pair instead of
; parentheses.)
"%w("
"%i("
] @indent
[

View File

@ -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'

View File

@ -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
[
"=="

View File

@ -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)$'

View File

@ -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)$'

View File

@ -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) {

View File

@ -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 });
}
}

View File

@ -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 () => {

View File

@ -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');

View File

@ -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 }
`)
})
});
});

View File

@ -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;
};

View File

@ -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;
}

View File

@ -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;

View File

@ -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);

Binary file not shown.