Merge branch 'master' into no-request

This commit is contained in:
confused-Techie 2023-05-20 20:39:08 -07:00
commit 4eef8250fb
228 changed files with 27979 additions and 385 deletions

View File

@ -1,5 +1,7 @@
env:
PYTHON_VERSION: 3.10
GITHUB_TOKEN: ENCRYPTED[13da504dc34d1608564d891fb7f456b546019d07d1abb059f9ab4296c56ccc0e6e32c7b313629776eda40ab74a54e95c]
# The above token, is a GitHub API Token, that allows us to download RipGrep without concern of API limits
linux_task:
alias: linux

View File

@ -7,7 +7,7 @@ module.exports = {
extends: [
"eslint:recommended",
"plugin:node/recommended",
"plugin:jsdoc/recommended"
// "plugin:jsdoc/recommended"
],
overrides: [],
parserOptions: {

2
.gitattributes vendored
View File

@ -1,3 +1,5 @@
CHANGELOG.* merge=union
# Specs depend on character counts, if we don't specify the line endings the
# fixtures will vary depending on platform
spec/fixtures/**/*.js text eol=lf

2
.gitignore vendored
View File

@ -8,6 +8,8 @@ Thumbs.db
.nvm-version
.vscode
.python-version
.envrc
.tool-versions
node_modules
*.log
/tags

View File

@ -6,12 +6,56 @@
## [Unreleased]
- Added a modern implementation of Tree-sitter grammars behind an experimental flag. Enable the “Use Modern Tree-Sitter Implementation” in the Core settings to try it out.
## 1.105.0
- Rebranded notifications, using our backend to find new versions of package,
and our github repository to find issues on Pulsar. Also fixed the "view issue"
and "create issue" buttons that were not working
- Bumped to latest version of `second-mate`, fixing a memory usage issue in `vscode-oniguruma`
- Removed a cache for native modules - fix bugs where an user rebuilds a native
module outside of Pulsar, but Pulsar refuses to load anyway
- Removed `nslog` dependency
- Fixed an error where the GitHub package tried to interact with a diff view after it was closed
- Fixed RPM installation failure when Atom was installed on the same machine
- Added a new set of Package `activationHooks`, `...:uri-opened` lets a package activate when any URI is opened within Pulsar, and `...:file-name-opened` lets a package activate when any specific filename is opened within Pulsar.
### Pulsar
- Added: Add new `...:uri-opened` && `...:file-name-opened` Package Activation Hook [@confused-Techie](https://github.com/pulsar-edit/pulsar/pull/518)
- Fixed: Properly localize Download/Stargazer Counts within `settings-view` [@confused-Techie](https://github.com/pulsar-edit/pulsar/pull/526)
- Added: Add bookmarks service for consumption by other packages [@savetheclocktower](https://github.com/pulsar-edit/pulsar/pull/525)
- Added: Bundle notifications [@mauricioszabo](https://github.com/pulsar-edit/pulsar/pull/529)
- Fixed: Fix Ripgrep download issues in CirrusCI [@confused-Techie](https://github.com/pulsar-edit/pulsar/pull/530)
- Removed: Revert Incorrect Commit [@confused-Techie](https://github.com/pulsar-edit/pulsar/pull/528)
- Fixed: Making CI green, hopefully [@mauricioszabo](https://github.com/pulsar-edit/pulsar/pull/523)
- Bumped: Bump `second-mate` to 96866771 [@savetheclocktower](https://github.com/pulsar-edit/pulsar/pull/524)
- Removed: Remove cache of incompatible native packages [@mauricioszabo](https://github.com/pulsar-edit/pulsar/pull/493)
- Added: Simplify and bundle fuzzy-finder [@mauricioszabo](https://github.com/pulsar-edit/pulsar/pull/515)
- Added: Bundle find and replace [@mauricioszabo](https://github.com/pulsar-edit/pulsar/pull/517)
- Added: Bundle tree view [@confused-Techie](https://github.com/pulsar-edit/pulsar/pull/513)
- Added: Bundle `autocomplete-atom-api` [@confused-Techie](https://github.com/pulsar-edit/pulsar/pull/476)
- Added: Add FPM option to stop rpm buildid clash [@Daeraxa](https://github.com/pulsar-edit/pulsar/pull/505)
- Bumped: chore(deps): update dependency minimist [security] [@renovate](https://github.com/pulsar-edit/pulsar/pull/502)
- Fixed: Disable Failing Tests [@confused-Techie](https://github.com/pulsar-edit/pulsar/pull/477)
- Bumped: chore(deps): update dependency ajv to 6.12.3 [security] [@renovate](https://github.com/pulsar-edit/pulsar/pull/501)
- Bumped: chore(deps): update dependency async to 3.2.2 [security] [@renovate](https://github.com/pulsar-edit/pulsar/pull/495)
- Added: Add "icon only" class to settings view icon [@Daeraxa](https://github.com/pulsar-edit/pulsar/pull/456)
- Bumped: chore(deps): update dependency minimatch [security] [@renovate](https://github.com/pulsar-edit/pulsar/pull/496)
- Removed: Remove `nslog` dependency [@mauricioszabo](https://github.com/pulsar-edit/pulsar/pull/494)
- Added: Setup Renovate [@confused-Techie](https://github.com/pulsar-edit/pulsar/pull/469)
- Fixed: Don't mark diff ranges on a destroyed buffer [@savetheclocktower](https://github.com/pulsar-edit/pulsar/pull/481)
- Added: First Architectural Design Records [@mauricioszabo](https://github.com/pulsar-edit/pulsar/pull/480)
- Bumped: use pular's `typscript-simple` fork, which bumps `typescript` to 5.0.3 [@Meadowsys](https://github.com/pulsar-edit/pulsar/pull/458)
- Added: CI: cache and restore dependencies, plus skip rebuilding all over the place (saves a lot of time) [@DeeDeeG](https://github.com/pulsar-edit/pulsar/pull/492)
### notifications
- Fixed: Cleanup and rename [@Sertonix](https://github.com/pulsar-edit/notifications/pull/1)
- Added: reject promise with Error instance [@Sertonix](https://github.com/pulsar-edit/notifications/pull/2)
- Added: Add our Testing Action [@confused-Techie](https://github.com/pulsar-edit/notifications/pull/3)
- Fixed: Change atom strings to pulsar [@mdibella-dev](https://github.com/pulsar-edit/notifications/pull/4)
- Bumped: Bump to v3.2 of action-pulsar-dependency [@confused-Techie](https://github.com/pulsar-edit/notifications/pull/5)
- Fixed: Fix all Tests [@confused-Techie](https://github.com/pulsar-edit/notifications/pull/6)
## 1.104.0

View File

@ -2,7 +2,7 @@
"name": "pulsar",
"author": "Pulsar-Edit <admin@pulsar-edit.dev>",
"productName": "Pulsar",
"version": "1.104.0-dev",
"version": "1.105.0-dev",
"description": "A Community-led Hyper-Hackable Text Editor",
"branding": {
"id": "pulsar",
@ -19,6 +19,9 @@
"bugs": {
"url": "https://github.com/pulsar-edit/pulsar/issues"
},
"engines": {
"node": ">=14"
},
"license": "MIT",
"electronVersion": "12.2.3",
"dependencies": {
@ -66,6 +69,7 @@
"fs-admin": "0.19.0",
"fs-plus": "^3.1.1",
"fstream": "1.0.12",
"functional-red-black-tree": "^1.0.1",
"fuzzy-finder": "file:packages/fuzzy-finder",
"git-diff": "file:packages/git-diff",
"git-utils": "5.7.1",
@ -126,7 +130,7 @@
"mocha-multi-reporters": "^1.1.4",
"mock-spawn": "^0.2.6",
"normalize-package-data": "3.0.2",
"notifications": "https://codeload.github.com/atom/notifications/legacy.tar.gz/refs/tags/v0.72.1",
"notifications": "file:./packages/notifications",
"nsfw": "2.2.2",
"one-dark-syntax": "file:packages/one-dark-syntax",
"one-dark-ui": "file:packages/one-dark-ui",
@ -135,7 +139,7 @@
"open-on-github": "file:packages/open-on-github",
"package-generator": "file:packages/package-generator",
"pathwatcher": "^8.1.2",
"postcss": "8.2.10",
"postcss": "8.2.13",
"postcss-selector-parser": "6.0.4",
"property-accessors": "^1.1.3",
"resolve": "1.18.1",
@ -166,6 +170,7 @@
"underscore-plus": "^1.7.0",
"update-package-dependencies": "file:./packages/update-package-dependencies",
"vscode-ripgrep": "1.9.0",
"web-tree-sitter": "^0.20.7",
"welcome": "file:packages/welcome",
"whitespace": "file:./packages/whitespace",
"winreg": "^1.2.1",
@ -215,7 +220,7 @@
"line-ending-selector": "file:./packages/line-ending-selector",
"link": "file:./packages/link",
"markdown-preview": "file:./packages/markdown-preview",
"notifications": "0.72.1",
"notifications": "file:./packages/notifications",
"open-on-github": "file:./packages/open-on-github",
"package-generator": "file:./packages/package-generator",
"settings-view": "file:./packages/settings-view",

View File

@ -51,6 +51,7 @@ describe "CSS property name and value autocompletions", ->
beforeEach ->
waitsForPromise -> atom.packages.activatePackage('autocomplete-css')
waitsForPromise -> atom.packages.activatePackage('language-css') # Used in all CSS languages
atom.config.set 'core.useTreeSitterParsers', false
runs ->
provider = atom.packages.getActivePackage('autocomplete-css').mainModule.getProvider()

View File

@ -0,0 +1,31 @@
class BookmarksProvider {
constructor(main) {
this.main = main
}
// Returns all bookmarks present in the given editor.
//
// Each bookmark tracks a buffer range and is represented by an instance of
// {DisplayMarker}.
//
// Will return an empty array if there are no bookmarks in the given editor.
//
// Keep in mind that a single bookmark can span multiple buffer rows and/or
// screen rows. Thus there isn't necessarily a 1:1 correlation between the
// number of bookmarks in the editor and the number of bookmark icons that
// the user will see in the gutter.
getBookmarksForEditor(editor) {
let instance = this.getInstanceForEditor(editor)
if (!instance) return null
return instance.getAllBookmarks()
}
// Returns the instance of the `Bookmarks` class that is responsible for
// managing bookmarks in the given editor.
getInstanceForEditor(editor) {
return this.main.editorsBookmarks.find(b => b.editor.id === editor.id)
}
}
module.exports = BookmarksProvider

View File

@ -1,12 +1,13 @@
const {CompositeDisposable} = require('atom')
const {CompositeDisposable, Emitter} = require('atom')
module.exports =
class Bookmarks {
static deserialize (editor, state) {
static deserialize(editor, state) {
return new Bookmarks(editor, editor.getMarkerLayer(state.markerLayerId))
}
constructor (editor, markerLayer) {
constructor(editor, markerLayer) {
this.emitter = new Emitter()
this.editor = editor
this.markerLayer = markerLayer || this.editor.addMarkerLayer({persistent: true})
this.decorationLayer = this.editor.decorateMarkerLayer(this.markerLayer, {type: 'line-number', class: 'bookmarked'})
@ -24,23 +25,23 @@ class Bookmarks {
this.disposables.add(this.editor.onDidDestroy(this.destroy.bind(this)))
}
destroy () {
destroy() {
this.deactivate()
this.markerLayer.destroy()
}
deactivate () {
deactivate() {
this.decorationLayer.destroy()
this.decorationLayerLine.destroy()
this.decorationLayerHighlight.destroy()
this.disposables.dispose()
}
serialize () {
serialize() {
return {markerLayerId: this.markerLayer.id}
}
toggleBookmark () {
toggleBookmark() {
for (const range of this.editor.getSelectedBufferRanges()) {
const bookmarks = this.markerLayer.findMarkers({intersectsRowRange: [range.start.row, range.end.row]})
if (bookmarks && bookmarks.length > 0) {
@ -52,19 +53,34 @@ class Bookmarks {
this.disposables.add(bookmark.onDidChange(({isValid}) => {
if (!isValid) {
bookmark.destroy()
// TODO: If N bookmarks are affected by a buffer change,
// `did-change-bookmarks` will be emitted N times. We could
// debounce this if we were willing to go async.
this.emitter.emit('did-change-bookmarks', this.getAllBookmarks())
}
}))
}
this.emitter.emit('did-change-bookmarks', this.getAllBookmarks())
}
}
clearBookmarks () {
getAllBookmarks() {
let markers = this.markerLayer.getMarkers()
return markers
}
onDidChangeBookmarks(callback) {
return this.emitter.on('did-change-bookmarks', callback)
}
clearBookmarks() {
for (const bookmark of this.markerLayer.getMarkers()) {
bookmark.destroy()
}
this.emitter.emit('did-change-bookmarks', [])
}
jumpToNextBookmark () {
jumpToNextBookmark() {
if (this.markerLayer.getMarkerCount() > 0) {
const bufferRow = this.editor.getLastCursor().getMarker().getStartBufferPosition().row
const markers = this.markerLayer.getMarkers().sort((a, b) => a.compare(b))
@ -76,7 +92,7 @@ class Bookmarks {
}
}
jumpToPreviousBookmark () {
jumpToPreviousBookmark() {
if (this.markerLayer.getMarkerCount() > 0) {
const bufferRow = this.editor.getLastCursor().getMarker().getStartBufferPosition().row
const markers = this.markerLayer.getMarkers().sort((a, b) => b.compare(a))
@ -88,7 +104,7 @@ class Bookmarks {
}
}
selectToNextBookmark () {
selectToNextBookmark() {
if (this.markerLayer.getMarkerCount() > 0) {
const bufferRow = this.editor.getLastCursor().getMarker().getStartBufferPosition().row
const markers = this.markerLayer.getMarkers().sort((a, b) => a.compare(b))
@ -103,7 +119,7 @@ class Bookmarks {
}
}
selectToPreviousBookmark () {
selectToPreviousBookmark() {
if (this.markerLayer.getMarkerCount() > 0) {
const bufferRow = this.editor.getLastCursor().getMarker().getStartBufferPosition().row
const markers = this.markerLayer.getMarkers().sort((a, b) => b.compare(a))

View File

@ -2,9 +2,10 @@ const {CompositeDisposable} = require('atom')
const Bookmarks = require('./bookmarks')
const BookmarksView = require('./bookmarks-view')
const BookmarksProvider = require('./bookmarks-provider')
module.exports = {
activate (bookmarksByEditorId) {
activate(bookmarksByEditorId) {
this.bookmarksView = null
this.editorsBookmarks = []
this.disposables = new CompositeDisposable()
@ -44,7 +45,7 @@ module.exports = {
})
},
deactivate () {
deactivate() {
if (this.bookmarksView != null) {
this.bookmarksView.destroy()
this.bookmarksView = null
@ -56,11 +57,16 @@ module.exports = {
this.disposables.dispose()
},
serialize () {
serialize() {
const bookmarksByEditorId = {}
for (let bookmarks of this.editorsBookmarks) {
bookmarksByEditorId[bookmarks.editor.id] = bookmarks.serialize()
}
return bookmarksByEditorId
},
provideBookmarks() {
this.bookmarksProvider ??= new BookmarksProvider(this)
return this.bookmarksProvider
}
}

View File

@ -10,5 +10,14 @@
},
"dependencies": {
"atom-select-list": "^0.7.0"
},
"providedServices": {
"bookmarks": {
"description": "Provides a list of bookmarks to any package that wants to know about them.",
"versions": {
"1.0.0": "provideBookmarks"
}
}
}
}

View File

@ -1,5 +1,5 @@
describe('Bookmarks package', () => {
let [workspaceElement, editorElement, editor, bookmarks] = []
let workspaceElement, editorElement, editor, bookmarks, provider
const bookmarkedRangesForEditor = editor => {
const decorationsById = editor.decorationsStateForScreenRowRange(0, editor.getLastScreenRow())
@ -18,6 +18,7 @@ describe('Bookmarks package', () => {
await atom.workspace.open('sample.js')
bookmarks = (await atom.packages.activatePackage('bookmarks')).mainModule
provider = bookmarks.bookmarksProvider
jasmine.attachToDOM(workspaceElement)
editor = atom.workspace.getActiveTextEditor()
@ -32,17 +33,28 @@ describe('Bookmarks package', () => {
expect(bookmarkedRangesForEditor(editor)).toEqual([])
atom.commands.dispatch(editorElement, 'bookmarks:toggle-bookmark')
expect(bookmarkedRangesForEditor(editor)).toEqual([[[3, 10], [3, 10]]])
let marks = provider.getBookmarksForEditor(editor)
expect(marks.length).toBe(1)
expect(marks.map(m => m.getScreenRange())).toEqual(bookmarkedRangesForEditor(editor))
})
it('removes marker when toggled', () => {
let callback = jasmine.createSpy()
let instance = provider.getInstanceForEditor(editor)
instance.onDidChangeBookmarks(callback)
editor.setCursorBufferPosition([3, 10])
expect(bookmarkedRangesForEditor(editor).length).toBe(0)
atom.commands.dispatch(editorElement, 'bookmarks:toggle-bookmark')
expect(bookmarkedRangesForEditor(editor).length).toBe(1)
expect(callback.callCount).toBe(1)
atom.commands.dispatch(editorElement, 'bookmarks:toggle-bookmark')
expect(bookmarkedRangesForEditor(editor).length).toBe(0)
expect(callback.callCount).toBe(2)
})
})
@ -53,6 +65,8 @@ describe('Bookmarks package', () => {
expect(bookmarkedRangesForEditor(editor)).toEqual([])
atom.commands.dispatch(editorElement, 'bookmarks:toggle-bookmark')
expect(bookmarkedRangesForEditor(editor)).toEqual([[[3, 10], [3, 10]], [[6, 11], [6, 11]]])
let instance = provider.getInstanceForEditor(editor)
expect(instance.getAllBookmarks().length).toBe(2)
})
it('removes multiple markers when toggled', () => {
@ -215,27 +229,39 @@ describe('Bookmarks package', () => {
})
it('clears all bookmarks', () => {
let callback = jasmine.createSpy()
let instance = provider.getInstanceForEditor(editor)
instance.onDidChangeBookmarks(callback)
editor.setCursorBufferPosition([3, 10])
atom.commands.dispatch(editorElement, 'bookmarks:toggle-bookmark')
expect(callback.callCount).toBe(1)
editor.setCursorBufferPosition([5, 0])
atom.commands.dispatch(editorElement, 'bookmarks:toggle-bookmark')
expect(callback.callCount).toBe(2)
atom.commands.dispatch(editorElement, 'bookmarks:clear-bookmarks')
expect(getBookmarkedLineNodes(editorElement).length).toBe(0)
expect(callback.callCount).toBe(3)
})
})
describe('when a bookmark is invalidated', () => {
it('creates a marker when toggled', () => {
let callback = jasmine.createSpy()
let instance = provider.getInstanceForEditor(editor)
instance.onDidChangeBookmarks(callback)
editor.setCursorBufferPosition([3, 10])
expect(bookmarkedRangesForEditor(editor).length).toBe(0)
atom.commands.dispatch(editorElement, 'bookmarks:toggle-bookmark')
expect(bookmarkedRangesForEditor(editor).length).toBe(1)
expect(callback.callCount).toBe(1)
editor.setText('')
expect(bookmarkedRangesForEditor(editor).length).toBe(0)
expect(callback.callCount).toBe(2)
})
})

View File

@ -3,6 +3,7 @@ const SelectListView = require('atom-select-list');
module.exports = class GrammarListView {
constructor() {
this.autoDetect = { name: 'Auto Detect' };
this.selectListView = new SelectListView({
itemsClassList: ['mark-active'],
items: [],
@ -21,12 +22,23 @@ module.exports = class GrammarListView {
if (isTreeSitter(grammar)) {
const parser = document.createElement('span');
let badgeColor = 'badge-success';
let badgeText = 'Tree-sitter';
if (isExperimentalTreeSitterMode()) {
badgeColor = isModernTreeSitter(grammar) ?
'badge-success' : 'badge-warning';
badgeText = isModernTreeSitter(grammar) ?
'Tree-sitter' : 'Legacy Tree-sitter';
}
parser.classList.add(
'grammar-selector-parser',
'badge',
'badge-success'
badgeColor
);
parser.textContent = 'Tree-sitter';
parser.textContent = badgeText;
parser.setAttribute(
'title',
'(Recommended) A faster parser with improved syntax highlighting & code navigation support.'
@ -56,6 +68,7 @@ module.exports = class GrammarListView {
this.cancel();
}
});
this.selectListView.element.classList.add('grammar-selector');
}
@ -105,6 +118,12 @@ module.exports = class GrammarListView {
return grammar !== atom.grammars.nullGrammar && grammar.name;
});
// Don't show modern tree-sitter grammars in the selector unless the user
// has opted into it.
if (!isExperimentalTreeSitterMode()) {
grammars = grammars.filter(grammar => !isModernTreeSitter(grammar));
}
if (atom.config.get('grammar-selector.hideDuplicateTextMateGrammars')) {
const blacklist = new Set();
grammars.forEach(grammar => {
@ -134,15 +153,36 @@ module.exports = class GrammarListView {
}
};
function getLanguageModeConfig() {
let isTreeSitterMode = atom.config.get('core.useTreeSitterParsers');
let isExperimental = atom.config.get('core.useExperimentalModernTreeSitter');
if (!isTreeSitterMode) return 'textmate';
return isExperimental ? 'wasm-tree-sitter' : 'node-tree-sitter';
}
function isExperimentalTreeSitterMode() {
return getLanguageModeConfig() === 'wasm-tree-sitter';
}
function isTreeSitter(grammar) {
return isOldTreeSitter(grammar) || isModernTreeSitter(grammar);
}
function isModernTreeSitter(grammar) {
return grammar.constructor.name === 'WASMTreeSitterGrammar';
}
function isOldTreeSitter(grammar) {
return grammar.constructor.name === 'TreeSitterGrammar';
}
function compareGrammarType(a, b) {
if (isTreeSitter(a)) {
return -1;
} else if (isTreeSitter(b)) {
return 1;
}
return 0;
return getGrammarScore(a) - getGrammarScore(b);
}
function getGrammarScore(grammar) {
let languageParser = getLanguageModeConfig();
if (isModernTreeSitter(grammar)) { return -2; }
if (isOldTreeSitter(grammar)) { return -1; }
return languageParser === 'textmate' ? -3 : 0;
}

View File

@ -1,6 +1,13 @@
const path = require('path');
const SelectListView = require('atom-select-list');
function setConfigForLanguageMode(mode) {
let useTreeSitterParsers = mode !== 'textmate';
let useExperimentalModernTreeSitter = mode === 'wasm-tree-sitter';
atom.config.set('core.useTreeSitterParsers', useTreeSitterParsers);
atom.config.set('core.useExperimentalModernTreeSitter', useExperimentalModernTreeSitter);
}
describe('GrammarSelector', () => {
let [editor, textGrammar, jsGrammar] = [];
@ -24,6 +31,8 @@ describe('GrammarSelector', () => {
jsGrammar = atom.grammars.grammarForScopeName('source.js');
expect(jsGrammar).toBeTruthy();
expect(editor.getGrammar()).toBe(jsGrammar);
setConfigForLanguageMode('textmate');
});
describe('when grammar-selector:show is triggered', () =>
@ -56,16 +65,30 @@ describe('GrammarSelector', () => {
expect(editor.getGrammar()).toBe(textGrammar);
}));
describe('when auto-detect is selected', () =>
it('restores the auto-detected grammar on the editor', async () => {
describe('when auto-detect is selected', () => {
it('restores the auto-detected grammar on the editor (when language parser is textmate)', async () => {
let grammarView = await getGrammarView(editor);
grammarView.props.didConfirmSelection(textGrammar);
expect(editor.getGrammar()).toBe(textGrammar);
grammarView = await getGrammarView(editor);
grammarView.props.didConfirmSelection(grammarView.items[0]);
expect(editor.getGrammar()).toBe(jsGrammar);
}));
let currentGrammar = editor.getGrammar();
expect(currentGrammar.scopeName).toBe('source.js');
expect(currentGrammar.constructor.name).toBe('Grammar');
});
it('restores the auto-detected grammar on the editor (when language parser is node-tree-sitter)', async () => {
setConfigForLanguageMode('node-tree-sitter');
let grammarView = await getGrammarView(editor);
grammarView.props.didConfirmSelection(textGrammar);
expect(editor.getGrammar()).toBe(textGrammar);
grammarView = await getGrammarView(editor);
grammarView.props.didConfirmSelection(grammarView.items[0]);
let currentGrammar = editor.getGrammar();
expect(currentGrammar.scopeName).toBe('source.js');
expect(currentGrammar.constructor.name).toBe('TreeSitterGrammar');
});
});
describe("when the editor's current grammar is the null grammar", () =>
it('displays Auto Detect as the selected grammar', async () => {
@ -201,7 +224,38 @@ describe('GrammarSelector', () => {
}
});
it('shows both if false', async () => {
it('shows both if false (in proper order when language parser is node-tree-sitter)', async () => {
await atom.packages.activatePackage('language-c'); // punctuation making it sort wrong
setConfigForLanguageMode('node-tree-sitter');
atom.config.set(
'grammar-selector.hideDuplicateTextMateGrammars',
false
);
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 Tree-sitter
expect(grammar.constructor.name).toBe('TreeSitterGrammar');
cppCount++;
} else if (cppCount === 1) {
// immediate next grammar should be the TextMate version
expect(name).toBe('C++');
expect(grammar.constructor.name).toBe('Grammar');
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
});
it('shows both 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',
@ -215,11 +269,13 @@ describe('GrammarSelector', () => {
const grammar = listItems[i];
const name = grammar.name;
if (cppCount === 0 && name === 'C++') {
expect(grammar.constructor.name).toBe('TreeSitterGrammar'); // first C++ entry should be Tree-sitter
// first C++ entry should be TextMate
expect(grammar.constructor.name).toBe('Grammar');
cppCount++;
} else if (cppCount === 1) {
// immediate next grammar should be the Tree-sitter version
expect(name).toBe('C++');
expect(grammar.constructor.name).toBe('Grammar'); // immediate next grammar should be the TextMate version
expect(grammar.constructor.name).toBe('TreeSitterGrammar');
cppCount++;
} else {
expect(name).not.toBe('C++'); // there should not be any other C++ grammars
@ -228,8 +284,10 @@ describe('GrammarSelector', () => {
expect(cppCount).toBe(2); // ensure we actually saw both grammars
});
});
describe('for every Tree-sitter grammar', () => {
it('adds a label to identify it as Tree-sitter', async () => {
const grammarView = await getGrammarView(editor);
@ -266,6 +324,130 @@ describe('GrammarSelector', () => {
expect(grammarTile.destroy).toHaveBeenCalled();
}));
});
// TODO: These tests will need to be altered when we remove legacy
// tree-sitter altogether.
describe('when language parser is "wasm-tree-sitter"', () => {
beforeEach(() => {
setConfigForLanguageMode('wasm-tree-sitter');
});
describe('when grammar-selector:show is triggered', () => {
it('displays a list of all the available grammars', async () => {
const grammarView = (await getGrammarView(editor)).element;
// -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
);
expect(grammarView.querySelectorAll('li')[0].textContent).toBe(
'Auto Detect'
);
expect(grammarView.textContent.includes('source.a')).toBe(false);
grammarView
.querySelectorAll('li')
.forEach(li =>
expect(li.textContent).not.toBe(atom.grammars.nullGrammar.name)
);
// Ensure we're showing and labelling tree-sitter grammars…
expect(grammarView.textContent.includes('Tree-sitter')).toBe(true);
// …and old tree-sitter grammars.
expect(grammarView.textContent.includes('Legacy Tree-sitter')).toBe(true);
});
});
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
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}`);
});
// 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'));
}
}
});
it('shows both if false', async () => {
await atom.packages.activatePackage('language-c'); // punctuation making it sort wrong
atom.config.set(
'grammar-selector.hideDuplicateTextMateGrammars',
false
);
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++') {
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'); // second C++ entry should be Legacy 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(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 () => {
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(true);
}
if (item.constructor.name === 'TreeSitterGrammar') {
expect(
element.childNodes[1].childNodes[0].classList.contains('badge-warning')
).toBe(true);
} else if (item.constructor.name === 'WASMTreeSitterGrammar') {
expect(
element.childNodes[1].childNodes[0].classList.contains('badge-success')
).toBe(true);
}
}
});
});
});
});
function getTooltipText(element) {
@ -274,7 +456,11 @@ function getTooltipText(element) {
}
async function getGrammarView(editor) {
let timeout = setTimeout(() => {
throw new Error('Timeout');
}, 5000);
atom.commands.dispatch(editor.getElement(), 'grammar-selector:show');
await SelectListView.getScheduler().getNextUpdatePromise();
clearTimeout(timeout);
return atom.workspace.getModalPanels()[0].getItem();
}

View File

@ -0,0 +1 @@
source "/Users/andrew/Code/JavaScript/emsdk/emsdk_env.sh"

View File

@ -0,0 +1 @@
nodejs 18.14.0

View File

@ -0,0 +1,13 @@
name: 'C'
scopeName: 'source.c'
type: 'modern-tree-sitter'
parser: 'tree-sitter-c'
injectionRegex: '^(c|C)$'
treeSitter:
grammar: 'tree-sitter-c/tree-sitter-c.wasm'
highlightsQuery: 'tree-sitter-c/highlights.scm'
localsQuery: 'tree-sitter-c/locals.scm'
foldsQuery: 'tree-sitter-c/folds.scm'
indentsQuery: 'tree-sitter-c/indents.scm'

View File

@ -0,0 +1,13 @@
name: 'C++'
scopeName: 'source.cpp'
type: 'modern-tree-sitter'
parser: 'tree-sitter-cpp'
injectionRegex: '^(cpp|CPP|cc|CC)$'
treeSitter:
grammar: 'tree-sitter-cpp/tree-sitter-cpp.wasm'
highlightsQuery: 'tree-sitter-cpp/highlights.scm'
localsQuery: 'tree-sitter-cpp/locals.scm'
foldsQuery: 'tree-sitter-cpp/folds.scm'
indentsQuery: 'tree-sitter-cpp/indents.scm'

View File

@ -0,0 +1,24 @@
; When we've got
;
; if (foo) {
; // something
; } else {
; // something else
; }
;
; we want the folds to work a little differently so that collapsing the `if`
; fold doesn't interfere with our ability to collapse the `else` fold.
(if_statement
consequence: (compound_statement) @fold
(#set! fold.adjustToEndOfPreviousRow true))
[
(field_declaration_list)
(enumerator_list)
(compound_statement)
] @fold
; Divided folds for preprocessor statements because they can't reliably be
; expressed with simple folds.
["#ifndef" "#ifdef" "#elif" "#else" "#if"] @fold.start
["#elif" "#else" "#endif"] @fold.end

View File

@ -0,0 +1,328 @@
; PREPROCESSOR
; ============
[
"#if"
"#ifdef"
"#ifndef"
"#endif"
"#elif"
"#else"
] @keyword.control.directive.conditional.c
"#define" @keyword.control.directive.define.c
"#include" @keyword.control.directive.include.c
; This will match if the more specific rules above haven't matched. The
; anonymous nodes will match under ideal conditions, but might not be present
; if the parser is flummoxed.
((preproc_directive) @keyword.control.directive.c
(#set! test.shy true))
((preproc_ifdef
(identifier) @entity.name.function.preprocessor.c
(#match? @entity.name.function.preprocessor.c "[a-zA-Z_$][\\w$]*")))
(preproc_function_def
(identifier) @entity.name.function.preprocessor.c
(#set! test.final true))
(system_lib_string) @string.quoted.other.lt-gt.include.c
((system_lib_string) @punctuation.definition.string.begin.c
(#set! adjust.endAfterFirstMatchOf "^<"))
((system_lib_string) @punctuation.definition.string.end.c
(#set! adjust.startBeforeFirstMatchOf ">$"))
; TYPES
; =====
; WORKAROUND: If we're in an error state, don't trust the parser's designation
; of `type_identifier`. Someone's probably just typing on a new line.
(ERROR
(type_identifier) @_IGNORE_
(#set! test.final true))
(primitive_type) @storage.type.builtin.c
(type_identifier) @storage.type.other.c
[
"enum"
"long"
"short"
"signed"
"struct"
"typedef"
"union"
"unsigned"
] @storage.type.c
[
"const"
"extern"
"inline"
"register"
"restrict"
"static"
"volatile"
] @storage.modifier.c
((primitive_type) @support.type.stdint.c
(#match? @support.type.stdint.c "^(int8_t|int16_t|int32_t|int64_t|uint8_t|uint16_t|uint32_t|uint64_t|int_least8_t|int_least16_t|int_least32_t|int_least64_t|uint_least8_t|uint_least16_t|uint_least32_t|uint_least64_t|int_fast8_t|int_fast16_t|int_fast32_t|int_fast64_t|uint_fast8_t|uint_fast16_t|uint_fast32_t|uint_fast64_t|intptr_t|uintptr_t|intmax_t|intmax_t|uintmax_t|uintmax_t)$"))
; CAVEAT: tree-sitter-c doesn't identify placeholders like `%c` in strings.
; Candidate for an injection grammar.
(string_literal "\"") @string.quoted.double.c
(string_literal
"\"" @punctuation.definition.string.begin.c
(#set! test.onlyIfFirst true))
(string_literal
"\"" @punctuation.definition.string.end.c
(#set! test.onlyIfLast true))
(char_literal "'") @string.quoted.single.c
(char_literal
"'" @punctuation.definition.string.begin.c
(#set! test.onlyIfFirst true))
(char_literal
"'" @punctuation.definition.string.end.c
(#set! test.onlyIfLast true))
(string_literal (escape_sequence) @constant.character.escape.c)
(char_literal (escape_sequence) @constant.character.escape.c)
; VARIABLES
; =========
; Declarations and assignments
; ----------------------------
; The "x" in `int x`;
(declaration
declarator: (identifier) @variable.declaration.c)
; The "x" in `int x = y`;
(init_declarator
declarator: (identifier) @variable.declaration.c)
(field_declaration
(field_identifier) @variable.declaration.c)
(field_declaration
(pointer_declarator
(field_identifier) @variable.declaration.c))
(field_declaration
(array_declarator
(field_identifier) @variable.declaration.c))
(init_declarator
(pointer_declarator
(identifier) @variable.declaration.c))
(assignment_expression
left: (identifier) @variable.other.assignment.c)
; Function parameters
; -------------------
(preproc_params
(identifier) @variable.parameter.preprocessor.c)
; The "foo" in `const char foo` within a parameter list.
(parameter_declaration
declarator: (identifier) @variable.parameter.c)
; The "foo" in `const char *foo` within a parameter list.
(parameter_declaration
declarator: (pointer_declarator
declarator: (identifier) @variable.parameter.c))
; The "foo" in `const char foo[]` within a parameter list.
(parameter_declaration
declarator: (array_declarator
declarator: (identifier) @variable.parameter.c))
; The "argv" in `char* argv[]` within a parameter list.
(parameter_declaration
declarator: (pointer_declarator
declarator: (array_declarator
declarator: (identifier) @variable.parameter.c)))
; The "size" in `finfo->size`.
(field_expression
"->"
field: (field_identifier) @variable.other.member.c)
; FUNCTIONS
; =========
(function_declarator
(identifier) @entity.name.function.c)
(call_expression
(identifier) @support.function.c99.c
; Regex copied from the TM grammar.
(#match? @support.function.c99.c "^(_Exit|(?:nearbyint|nextafter|nexttoward|netoward|nan)[fl]?|a(?:cos|sin)h?[fl]?|abort|abs|asctime|assert|atan(?:[h2]?[fl]?)?|atexit|ato[ifl]|atoll|bsearch|btowc|cabs[fl]?|cacos|cacos[fl]|cacosh[fl]?|calloc|carg[fl]?|casinh?[fl]?|catanh?[fl]?|cbrt[fl]?|ccosh?[fl]?|ceil[fl]?|cexp[fl]?|cimag[fl]?|clearerr|clock|clog[fl]?|conj[fl]?|copysign[fl]?|cosh?[fl]?|cpow[fl]?|cproj[fl]?|creal[fl]?|csinh?[fl]?|csqrt[fl]?|ctanh?[fl]?|ctime|difftime|div|erfc?[fl]?|exit|fabs[fl]?|exp(?:2[fl]?|[fl]|m1[fl]?)?|fclose|fdim[fl]?|fe[gs]et(?:env|exceptflag|round)|feclearexcept|feholdexcept|feof|feraiseexcept|ferror|fetestexcept|feupdateenv|fflush|fgetpos|fgetw?[sc]|floor[fl]?|fmax?[fl]?|fmin[fl]?|fmod[fl]?|fopen|fpclassify|fprintf|fputw?[sc]|fread|free|freopen|frexp[fl]?|fscanf|fseek|fsetpos|ftell|fwide|fwprintf|fwrite|fwscanf|genv|get[sc]|getchar|gmtime|gwc|gwchar|hypot[fl]?|ilogb[fl]?|imaxabs|imaxdiv|isalnum|isalpha|isblank|iscntrl|isdigit|isfinite|isgraph|isgreater|isgreaterequal|isinf|isless(?:equal|greater)?|isw?lower|isnan|isnormal|isw?print|isw?punct|isw?space|isunordered|isw?upper|iswalnum|iswalpha|iswblank|iswcntrl|iswctype|iswdigit|iswgraph|isw?xdigit|labs|ldexp[fl]?|ldiv|lgamma[fl]?|llabs|lldiv|llrint[fl]?|llround[fl]?|localeconv|localtime|log[2b]?[fl]?|log1[p0][fl]?|longjmp|lrint[fl]?|lround[fl]?|malloc|mbr?len|mbr?towc|mbsinit|mbsrtowcs|mbstowcs|memchr|memcmp|memcpy|memmove|memset|mktime|modf[fl]?|perror|pow[fl]?|printf|puts|putw?c(?:har)?|qsort|raise|rand|remainder[fl]?|realloc|remove|remquo[fl]?|rename|rewind|rint[fl]?|round[fl]?|scalbl?n[fl]?|scanf|setbuf|setjmp|setlocale|setvbuf|signal|signbit|sinh?[fl]?|snprintf|sprintf|sqrt[fl]?|srand|sscanf|strcat|strchr|strcmp|strcoll|strcpy|strcspn|strerror|strftime|strlen|strncat|strncmp|strncpy|strpbrk|strrchr|strspn|strstr|strto[kdf]|strtoimax|strtol[dl]?|strtoull?|strtoumax|strxfrm|swprintf|swscanf|system|tan|tan[fl]|tanh[fl]?|tgamma[fl]?|time|tmpfile|tmpnam|tolower|toupper|trunc[fl]?|ungetw?c|va_arg|va_copy|va_end|va_start|vfw?printf|vfw?scanf|vprintf|vscanf|vsnprintf|vsprintf|vsscanf|vswprintf|vswscanf|vwprintf|vwscanf|wcrtomb|wcscat|wcschr|wcscmp|wcscoll|wcscpy|wcscspn|wcsftime|wcslen|wcsncat|wcsncmp|wcsncpy|wcspbrk|wcsrchr|wcsrtombs|wcsspn|wcsstr|wcsto[dkf]|wcstoimax|wcstol[dl]?|wcstombs|wcstoull?|wcstoumax|wcsxfrm|wctom?b|wmem(?:set|chr|cpy|cmp|move)|wprintf|wscanf)$")
(#set! test.final true))
; The "foo" in `thing->troz->foo(...)`.
(call_expression
(field_expression
field: (field_identifier) @support.other.function.c)
(#set! test.final true))
(call_expression
(identifier) @support.other.function.c
(#set! test.final true))
; NUMBERS
; =======
(number_literal) @constant.numeric.c
; CONSTANTS
; =========
[
(null)
(true)
(false)
] @constant.language._TYPE_.c
((identifier) @constant.c
(#match? @constant.c "[_A-Z][_A-Z0-9]*$"))
; COMMENTS
; ========
; Match // comments.
((comment) @comment.line.double-slash.c
(#match? @comment.line.double-slash.c "^\\s*//"))
((comment) @punctuation.definition.comment.c
(#match? @comment.line.double-slash.c "^\\s*//")
(#set! adjust.startAndEndAroundFirstMatchOf "//"))
; Match /* */ comments.
((comment) @comment.block.c
(#match? @comment.block.c "^/\\*"))
((comment) @punctuation.definition.comment.begin.c
(#match? @punctuation.definition.comment.begin.c "^/\\*")
(#set! adjust.startAndEndAroundFirstMatchOf "^/\\*"))
((comment) @punctuation.definition.comment.end.c
(#match? @punctuation.definition.comment.end.c "\\*/$")
(#set! adjust.startAndEndAroundFirstMatchOf "\\*/$"))
[
"break"
"case"
"continue"
"default"
"do"
"else"
"for"
"goto"
"if"
"return"
"switch"
"while"
] @keyword.control._TYPE_.c
; OPERATORS
; =========
(pointer_declarator "*" @keyword.operator.pointer.c)
(abstract_pointer_declarator "*" @keyword.operator.pointer.c)
(pointer_expression "*" @keyword.operator.pointer.c)
"sizeof" @keyword.operator.sizeof.c
(pointer_expression "&" @keyword.operator.pointer.c)
"=" @keyword.operator.assignment.c
[
"%="
"+="
"-="
"*="
"/="
"&="
"^="
"<<="
">>="
"|="
] @keyword.operator.assignment.compound.c
(binary_expression
["==" "!=" ">" "<" ">=" "<="] @keyword.operator.comparison.c)
(binary_expression
["&" "|" "^" "~" "<<" ">>"]
@keyword.operator.bitwise.c)
"++" @keyword.operator.increment.c
"--" @keyword.operator.decrement.c
(binary_expression ["+" "-" "*" "/" "%"] @keyword.operator.arithmetic.c)
(unary_expression ["+" "-" "!"] @keyword.operator.unary.c)
(conditional_expression
["?" ":"] @keyword.operator.ternary.c)
["||" "&&"] @keyword.operator.logical.c
(field_expression "." @keyword.operator.accessor.dot.c)
(preproc_params "..." @keyword.operator.ellipsis.c)
; PUNCTUATION
; ===========
";" @punctuation.terminator.statement.c
"," @punctuation.separator.comma.c
"->" @punctuation.separator.pointer-access.c
(parameter_list
"(" @punctuation.definition.parameters.begin.bracket.round.c
")" @punctuation.definition.parameters.end.bracket.round.c
(#set! test.final true))
(parenthesized_expression
"(" @punctuation.definition.expression.begin.bracket.round.c
")" @punctuation.definition.expression.end.bracket.round.c
(#set! test.final true))
(if_statement
condition: (parenthesized_expression
"(" @punctuation.definition.expression.begin.bracket.round.c
")" @punctuation.definition.expression.end.bracket.round.c
(#set! test.final true)))
"{" @punctuation.definition.block.begin.bracket.curly.c
"}" @punctuation.definition.block.end.bracket.curly.c
"(" @punctuation.definition.begin.bracket.round.c
")" @punctuation.definition.end.bracket.round.c
"[" @punctuation.definition.array.begin.bracket.square.c
"]" @punctuation.definition.array.end.bracket.square.c
; TODO:
;
; * TM-style grammar has a lot of `mac-classic` scopes. I doubt they'd be
; present if this wasn't converted from a TextMate grammar, so I'm leaving
; them out for now.
;

View File

@ -0,0 +1,20 @@
["{" "(" "["] @indent
["}" ")" "]"] @dedent
; `switch` statements have a couple schools of thought, indentation-wise, and
; we might have to make this configurable somehow.
(switch_statement
body: (compound_statement "}" @match
(#set! test.onlyIfLast true))
(#set! indent.matchIndentOf parent.startPosition))
; 'case' and 'default' need to be indented one level more than their containing
; `switch`.
(["case" "default"] @match
(#set! indent.matchIndentOf parent.parent.startPosition)
(#set! indent.offsetIndent 1))
["case" "default"] @indent

View File

@ -0,0 +1 @@
;

View File

@ -0,0 +1,24 @@
; When we've got
;
; if (foo) {
; // something
; } else {
; // something else
; }
;
; we want the folds to work a little differently so that collapsing the `if`
; fold doesn't interfere with our ability to collapse the `else` fold.
(if_statement
consequence: (compound_statement) @fold
(#set! fold.adjustToEndOfPreviousRow true))
[
(field_declaration_list)
(enumerator_list)
(compound_statement)
] @fold
; Divided folds for preprocessor statements because they can't reliably be
; expressed with simple folds.
["#ifndef" "#ifdef" "#elif" "#else" "#if"] @fold.start
["#elif" "#else" "#endif"] @fold.end

View File

@ -0,0 +1,397 @@
; PREPROCESSOR
; ============
[
"#if"
"#ifdef"
"#ifndef"
"#endif"
"#elif"
"#else"
] @keyword.control.directive.conditional.cpp
"#define" @keyword.control.directive.define.cpp
"#include" @keyword.control.directive.include.cpp
; This will match if the more specific rules above haven't matched. The
; anonymous nodes will match under ideal conditions, but might not be present
; if the parser is flummoxed.
((preproc_directive) @keyword.control.directive.c
(#set! test.shy true))
((preproc_ifdef
(identifier) @entity.name.function.preprocessor.c
(#match? @entity.name.function.preprocessor.c "[a-zA-Z_$][\\w$]*")))
(preproc_function_def
(identifier) @entity.name.function.preprocessor.c
(#set! test.final true))
(preproc_function_def
(identifier) @entity.name.function.preprocessor.cpp
(#set! test.final true)
)
(system_lib_string) @string.quoted.other.lt-gt.include.c
((system_lib_string) @punctuation.definition.string.begin.c
(#set! adjust.endAfterFirstMatchOf "^<"))
((system_lib_string) @punctuation.definition.string.end.c
(#set! adjust.startBeforeFirstMatchOf ">$"))
; TYPES
; =====
; WORKAROUND: If we're in an error state, don't trust the parser's designation
; of `type_identifier`. Someone's probably just typing on a new line.
(ERROR
(type_identifier) @_IGNORE_
(#set! test.final true))
(primitive_type) @storage.type.builtin.cpp
(class_specifier
(type_identifier) @entity.name.class.cpp
(#set! test.final true))
(type_identifier) @storage.type.other.cpp
; (struct_specifier) @storage.type.cpp
[
"enum"
"long"
"short"
"signed"
"struct"
"typedef"
"union"
"unsigned"
"template"
] @storage.type.cpp
[
"const"
"extern"
"inline"
"register"
"restrict"
"static"
"volatile"
"private"
"protected"
"public"
"friend"
"explicit"
"virtual"
"override"
"final"
"noexcept"
] @storage.modifier.cpp
(
(primitive_type) @support.type.stdint.cpp
(#match? @support.type.stdint.cpp "^(int8_t|int16_t|int32_t|int64_t|uint8_t|uint16_t|uint32_t|uint64_t|int_least8_t|int_least16_t|int_least32_t|int_least64_t|uint_least8_t|uint_least16_t|uint_least32_t|uint_least64_t|int_fast8_t|int_fast16_t|int_fast32_t|int_fast64_t|uint_fast8_t|uint_fast16_t|uint_fast32_t|uint_fast64_t|intptr_t|uintptr_t|intmax_t|intmax_t|uintmax_t|uintmax_t)$")
)
"typename" @storage.modifier.typename.cpp
; FUNCTIONS
; =========
(function_declarator
(identifier) @entity.name.function.cpp)
(function_declarator
(field_identifier) @entity.name.function.method.cpp)
(call_expression
(identifier) @support.function.c99.cpp
; Regex copied from the TM grammar.
(#match? @support.function.c99.cpp "^(_Exit|(?:nearbyint|nextafter|nexttoward|netoward|nan)[fl]?|a(?:cos|sin)h?[fl]?|abort|abs|asctime|assert|atan(?:[h2]?[fl]?)?|atexit|ato[ifl]|atoll|bsearch|btowc|cabs[fl]?|cacos|cacos[fl]|cacosh[fl]?|calloc|carg[fl]?|casinh?[fl]?|catanh?[fl]?|cbrt[fl]?|ccosh?[fl]?|ceil[fl]?|cexp[fl]?|cimag[fl]?|clearerr|clock|clog[fl]?|conj[fl]?|copysign[fl]?|cosh?[fl]?|cpow[fl]?|cproj[fl]?|creal[fl]?|csinh?[fl]?|csqrt[fl]?|ctanh?[fl]?|ctime|difftime|div|erfc?[fl]?|exit|fabs[fl]?|exp(?:2[fl]?|[fl]|m1[fl]?)?|fclose|fdim[fl]?|fe[gs]et(?:env|exceptflag|round)|feclearexcept|feholdexcept|feof|feraiseexcept|ferror|fetestexcept|feupdateenv|fflush|fgetpos|fgetw?[sc]|floor[fl]?|fmax?[fl]?|fmin[fl]?|fmod[fl]?|fopen|fpclassify|fprintf|fputw?[sc]|fread|free|freopen|frexp[fl]?|fscanf|fseek|fsetpos|ftell|fwide|fwprintf|fwrite|fwscanf|genv|get[sc]|getchar|gmtime|gwc|gwchar|hypot[fl]?|ilogb[fl]?|imaxabs|imaxdiv|isalnum|isalpha|isblank|iscntrl|isdigit|isfinite|isgraph|isgreater|isgreaterequal|isinf|isless(?:equal|greater)?|isw?lower|isnan|isnormal|isw?print|isw?punct|isw?space|isunordered|isw?upper|iswalnum|iswalpha|iswblank|iswcntrl|iswctype|iswdigit|iswgraph|isw?xdigit|labs|ldexp[fl]?|ldiv|lgamma[fl]?|llabs|lldiv|llrint[fl]?|llround[fl]?|localeconv|localtime|log[2b]?[fl]?|log1[p0][fl]?|longjmp|lrint[fl]?|lround[fl]?|malloc|mbr?len|mbr?towc|mbsinit|mbsrtowcs|mbstowcs|memchr|memcmp|memcpy|memmove|memset|mktime|modf[fl]?|perror|pow[fl]?|printf|puts|putw?c(?:har)?|qsort|raise|rand|remainder[fl]?|realloc|remove|remquo[fl]?|rename|rewind|rint[fl]?|round[fl]?|scalbl?n[fl]?|scanf|setbuf|setjmp|setlocale|setvbuf|signal|signbit|sinh?[fl]?|snprintf|sprintf|sqrt[fl]?|srand|sscanf|strcat|strchr|strcmp|strcoll|strcpy|strcspn|strerror|strftime|strlen|strncat|strncmp|strncpy|strpbrk|strrchr|strspn|strstr|strto[kdf]|strtoimax|strtol[dl]?|strtoull?|strtoumax|strxfrm|swprintf|swscanf|system|tan|tan[fl]|tanh[fl]?|tgamma[fl]?|time|tmpfile|tmpnam|tolower|toupper|trunc[fl]?|ungetw?c|va_arg|va_copy|va_end|va_start|vfw?printf|vfw?scanf|vprintf|vscanf|vsnprintf|vsprintf|vsscanf|vswprintf|vswscanf|vwprintf|vwscanf|wcrtomb|wcscat|wcschr|wcscmp|wcscoll|wcscpy|wcscspn|wcsftime|wcslen|wcsncat|wcsncmp|wcsncpy|wcspbrk|wcsrchr|wcsrtombs|wcsspn|wcsstr|wcsto[dkf]|wcstoimax|wcstol[dl]?|wcstombs|wcstoull?|wcstoumax|wcsxfrm|wctom?b|wmem(?:set|chr|cpy|cmp|move)|wprintf|wscanf)$")
(#set! test.final true))
; The "foo" in `thing->troz->foo(...)`.
(call_expression
(field_expression
field: (field_identifier) @support.other.function.cpp)
(#set! test.final true))
(call_expression
(identifier) @support.other.function.cpp
(#set! test.final true))
; STRINGS
; =======
; CAVEAT: tree-sitter-c doesn't identify placeholders like `%c` in strings.
; Candidate for an injection grammar.
(string_literal "\"") @string.quoted.double.cpp
(string_literal
"\"" @punctuation.definition.string.begin.cpp
(#set! test.onlyIfFirst true))
(string_literal
"\"" @punctuation.definition.string.end.cpp
(#set! test.onlyIfLast true))
(char_literal "'") @string.quoted.single.cpp
(char_literal
"'" @punctuation.definition.string.begin.cpp
(#set! test.onlyIfFirst true))
(char_literal
"'" @punctuation.definition.string.end.cpp
(#set! test.onlyIfLast true))
(string_literal (escape_sequence) @constant.character.escape.cpp)
(char_literal (escape_sequence) @constant.character.escape.cpp)
; VARIABLES
; =========
(this) @variable.language.this.cpp
; Declarations and assignments
; ----------------------------
; The "x" in `int x`;
(declaration
declarator: (identifier) @variable.declaration.cpp)
; The "x" in `int x = y`;
(init_declarator
declarator: (identifier) @variable.declaration.cpp)
(field_declaration
(field_identifier) @variable.declaration.cpp)
(field_declaration
(pointer_declarator
(field_identifier) @variable.declaration.cpp))
(field_declaration
(array_declarator
(field_identifier) @variable.declaration.cpp))
(init_declarator
(pointer_declarator
(identifier) @variable.declaration.cpp))
(assignment_expression
left: (identifier) @variable.other.assignment.cpp)
(reference_declarator
(identifier) @variable.declaration.cpp)
; Function parameters
; -------------------
(preproc_params
(identifier) @variable.parameter.preprocessor.cpp)
(parameter_declaration
declarator: (identifier) @variable.parameter.cpp)
(parameter_declaration
declarator: (pointer_declarator
declarator: (identifier) @variable.parameter.cpp))
; The "foo" in `const char foo[]` within a parameter list.
(parameter_declaration
declarator: (array_declarator
declarator: (identifier) @variable.parameter.cpp))
; The "argv" in `char* argv[]` within a parameter list.
(parameter_declaration
declarator: (pointer_declarator
declarator: (array_declarator
declarator: (identifier) @variable.parameter.cpp)))
; The "size" in `finfo->size`.
(field_expression
"->"
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*$"))
; NUMBERS
; =======
(number_literal) @constant.numeric.cpp
; CONSTANTS
; =========
[
(null)
(true)
(false)
(nullptr)
] @constant.language._TYPE_.cpp
((identifier) @constant.cpp
(#match? @constant.cpp "[_A-Z][_A-Z0-9]*$"))
; COMMENTS
; ========
; Match // comments.
((comment) @comment.line.double-slash.cpp
(#match? @comment.line.double-slash.cpp "^\\s*//"))
((comment) @punctuation.definition.comment.cpp
(#match? @comment.line.double-slash.cpp "^\\s*//")
(#set! adjust.startAndEndAroundFirstMatchOf "//"))
; Match /* */ comments.
((comment) @comment.block.cpp
(#match? @comment.block.cpp "^/\\*"))
((comment) @punctuation.definition.comment.begin.cpp
(#match? @punctuation.definition.comment.begin.cpp "^/\\*")
(#set! adjust.startAndEndAroundFirstMatchOf "^/\\*"))
((comment) @punctuation.definition.comment.end.cpp
(#match? @punctuation.definition.comment.end.cpp "\\*/$")
(#set! adjust.startAndEndAroundFirstMatchOf "\\*/$"))
; KEYWORDS
; ========
[
"break"
"case"
"continue"
"default"
"do"
"else"
"for"
"goto"
"if"
"return"
"switch"
"while"
"new"
"delete"
] @keyword.control._TYPE_.cpp
[
"catch"
"operator"
"try"
"throw"
"using"
"namespace"
] @keyword.control._TYPE_.cpp
; OPERATORS
; =========
(pointer_declarator "*" @keyword.operator.pointer.cpp)
(abstract_pointer_declarator "*" @keyword.operator.pointer.cpp)
(pointer_expression "*" @keyword.operator.pointer.cpp)
"sizeof" @keyword.operator.sizeof.cpp
(pointer_expression "&" @keyword.operator.pointer.cpp)
(reference_declarator "&" @keyword.operator.pointer.cpp)
"=" @keyword.operator.assignment.cpp
[
"%="
"+="
"-="
"*="
"/="
"&="
"^="
"<<="
">>="
"|="
] @keyword.operator.assignment.compound.cpp
(binary_expression
["==" "!=" ">" "<" ">=" "<="] @keyword.operator.comparison.cpp)
["&&" "||"] @keyword.operator.logical.cpp
"++" @keyword.operator.increment.cpp
"--" @keyword.operator.decrement.cpp
(binary_expression
["&" "|" "^" "~" "<<" ">>"]
@keyword.operator.bitwise.cpp)
(binary_expression
["<" ">"] @keyword.operator.comparison.cpp)
(binary_expression
["+" "-" "*" "/" "%"] @keyword.operator.arithmetic.cpp)
(unary_expression
["+" "-" "!"] @keyword.operator.unary.cpp)
(conditional_expression
["?" ":"] @keyword.operator.ternary.cpp)
(qualified_identifier
"::" @keyword.operator.accessor.namespace.cpp)
(field_expression "." @keyword.operator.accessor.dot.cpp)
(preproc_params "..." @keyword.operator.ellipsis.cpp)
; PUNCTUATION
; ===========
";" @punctuation.terminator.statement.cpp
"," @punctuation.separator.comma.cpp
"->" @keyword.operator.accessor.cpp
(parameter_list
"(" @punctuation.definition.parameters.begin.bracket.round.cpp
")" @punctuation.definition.parameters.end.bracket.round.cpp
(#set! test.final true))
(parenthesized_expression
"(" @punctuation.definition.expression.begin.bracket.round.cpp
")" @punctuation.definition.expression.end.bracket.round.cpp
(#set! test.final true))
(if_statement
condition: (condition_clause
"(" @punctuation.definition.expression.begin.bracket.round.cpp
")" @punctuation.definition.expression.end.bracket.round.cpp
(#set! test.final true)))
"{" @punctuation.definition.block.begin.bracket.curly.cpp
"}" @punctuation.definition.block.end.bracket.curly.cpp
"(" @punctuation.definition.begin.bracket.round.cpp
")" @punctuation.definition.end.bracket.round.cpp
"[" @punctuation.definition.array.begin.bracket.square.cpp
"]" @punctuation.definition.array.end.bracket.square.cpp
; TODO:
;
; * TM-style grammar has a lot of `mac-classic` scopes. I doubt they'd be
; present if this wasn't converted from a TextMate grammar, so I'm leaving
; them out for now.
;

View File

@ -0,0 +1,32 @@
["{" "(" "["] @indent
["}" ")" "]"] @dedent
; With access specifiers like…
;
; class Foo {
; public:
; void bar() {}
; }
;
; …prevailing style is to dedent the specifier so it matches the indentation of
; the line above.
(access_specifier) @dedent @indent
; `switch` statements have a couple schools of thought, indentation-wise, and
; we might have to make this configurable somehow.
(switch_statement
body: (compound_statement "}" @match
(#set! test.onlyIfLast true))
(#set! indent.matchIndentOf parent.startPosition))
; 'case' and 'default' need to be indented one level more than their containing
; `switch`.
(["case" "default"] @match
(#set! indent.matchIndentOf parent.parent.startPosition)
(#set! indent.offsetIndent 1))
["case" "default"] @indent

View File

@ -0,0 +1 @@
;

View File

@ -10,7 +10,7 @@ describe "Language-C", ->
grammar = null
beforeEach ->
atom.config.set('core.useTreeSitterParsers', false)
atom.config.set 'core.useTreeSitterParsers', false
waitsForPromise ->
atom.packages.activatePackage('language-c')

View File

@ -0,0 +1,43 @@
'name': 'Clojure'
'scopeName': 'source.clojure'
'type': 'modern-tree-sitter'
'fileTypes': [
'boot'
'clj'
'clj.hl'
'cljc'
'cljs'
'cljs.hl'
'cljx'
'clojure'
'edn'
'org'
'bb'
'joke'
'joker'
]
'firstLineMatch': '''(?x)
# Hashbang
^\\#!.*(?:\\s|\\/)
boot
(?:$|\\s)
|
# Modeline
(?i:
# Emacs
-\\*-(?:\\s*(?=[^:;\\s]+\\s*-\\*-)|(?:.*?[;\\s]|(?<=-\\*-))mode\\s*:\\s*)
clojure(script)?
(?=[\\s;]|(?<![-*])-\\*-).*?-\\*-
|
# Vim
(?:(?:\\s|^)vi(?:m[<=>]?\\d+|m)?|\\sex)(?=:(?=\\s*set?\\s[^\\n:]+:)|:(?!\\s*set?\\s))(?:(?:\\s|\\s*:\\s*)\\w*(?:\\s*=(?:[^\\n\\\\\\s]|\\\\.)*)?)*[\\s:](?:filetype|ft|syntax)\\s*=
clojure
(?=\\s|:|$)
)
'''
treeSitter:
grammar: 'ts/grammar.wasm'
highlightsQuery: 'ts/highlights.scm'
# localsQuery: 'ts/locals.scm'
foldsQuery: 'ts/folds.scm'
indentsQuery: 'ts/indents.scm'

View File

@ -0,0 +1,7 @@
[
(list_lit)
(vec_lit)
(map_lit)
(anon_fn_lit)
(set_lit)
] @fold

Binary file not shown.

View File

@ -0,0 +1,58 @@
(quoting_lit
value: (list_lit
"(" @punctuation.section.expression.begin
.
(sym_lit) @meta.symbol (#set! final "true")))
;; Collections
(anon_fn_lit
"(" @punctuation.section.expression.begin
.
(sym_lit) @entity.name.function @meta.expression
")" @punctuation.section.expression.end)
(list_lit
"(" @punctuation.section.expression.begin
.
(sym_lit) @entity.name.function @meta.expression
")" @punctuation.section.expression.end)
(vec_lit
"[" @punctuation.section.vector.begin
"]" @punctuation.section.vector.end) @meta.vector
(map_lit
"{" @punctuation.section.map.begin
"}" @punctuation.section.map.end) @meta.map
(set_lit
("#" "{") @punctuation.section.set.begin
"}" @punctuation.section.set.end) @meta.map
; Includes
((sym_name) @meta.symbol (#eq? @meta.symbol "import")) @keyword.control
((sym_name) @meta.symbol (#eq? @meta.symbol "require")) @keyword.control
((sym_name) @meta.symbol (#eq? @meta.symbol "use")) @keyword.control
(list_lit
"(" @punctuation.section.expression.begin
.
((sym_lit) @meta.symbol (#eq? @meta.symbol "ns")) @keyword.control @meta.definition.global
.
(sym_lit) @meta.definition.global @entity.global
")" @punctuation.section.expression.end)
(list_lit
"(" @punctuation.section.expression.begin
.
((sym_lit) @meta.symbol (#match? @meta.symbol "^def")) @keyword.control
.
(sym_lit) @meta.definition.global @entity.global
")" @punctuation.section.expression.end)
(sym_lit) @meta.symbol
(kwd_lit) @constant.keyword
(str_lit) @string.quoted.double
(num_lit) @constant.numeric
(comment) @comment.line.semicolon
(dis_expr) @comment.block.clojure

View File

@ -0,0 +1,34 @@
(ns foobar)
; <- punctuation.section.expression.begin
; ^ meta.definition.global
; ^ entity.global
; ^ punctuation.section.expression.end
(defn foobar [a b]
; <- keyword.control
; ^ entity.global
; ^ meta.definition.global
; ^ punctuation.section.vector.begin
; ^ meta.vector
; ^ meta.symbol
; ^ punctuation.section.vector.end
(+ a b 10 20))
;^ meta.expression
;^ entity.name.function
; ^ constant.numeric
(def a "A STRING")
; <- keyword.control
; ^ entity.global
; ^ string.quoted.double
#{'asd}
; <- punctuation.section.set.begin
; ^ meta.symbol
; ^ punctuation.section.set.end
{:key "value"}
; <- punctuation.section.map.begin
; ^ constant.keyword
; ^ meta.map
; ^ punctuation.section.map.end

View File

@ -0,0 +1,34 @@
(defn a [a b]
; <- fold_begin.paren
; ^ fold_new_position.paren
(+ a b)
[1
; <- fold_begin.vector
; ^ fold_new_position.vector
2
3]
; ^ fold_end.vector
{:a 10
; <- fold_begin.map
; ^ fold_new_position.map
:b 20
:c [1
; ^ fold_begin.inner_vector
; ^ fold_new_position.inner_vector
2
3]})
; ^ fold_end.paren
; ^ fold_end.map
; ^ fold_end.inner_vector
#(inner
; <- fold_begin.anon
; ^ fold_new_position.anon
"function"
#{:with
; <- fold_begin.set
; ^ fold_new_position.set
:inner
:set})
; ^ fold_end.anon
; ^ fold_end.set

View File

@ -0,0 +1,6 @@
#_
(+ 1 2)
; <- comment.block
'(a b 1 2)
; ^ !entity.name.function

View File

@ -0,0 +1,36 @@
/* global runGrammarTests, runFoldsTests */
const path = require('path');
function setConfigForLanguageMode(mode) {
let useTreeSitterParsers = mode !== 'textmate';
let useExperimentalModernTreeSitter = mode === 'wasm-tree-sitter';
atom.config.set('core.useTreeSitterParsers', useTreeSitterParsers);
atom.config.set('core.useExperimentalModernTreeSitter', useExperimentalModernTreeSitter);
}
describe('Clojure grammars', () => {
beforeEach(async () => {
await atom.packages.activatePackage('language-clojure');
});
it('tokenizes the editor using TextMate parser', async () => {
setConfigForLanguageMode('textmate');
await runGrammarTests(path.join(__dirname, 'fixtures', 'tokens.clj'), /;/)
});
it('tokenizes the editor using node tree-sitter parser the same as TextMate', async () => {
setConfigForLanguageMode('wasm-tree-sitter');
await runGrammarTests(path.join(__dirname, 'fixtures', 'tokens.clj'), /;/)
});
it('tokenizes the editor using node tree-sitter parser (specific rules)', async () => {
setConfigForLanguageMode('wasm-tree-sitter');
await runGrammarTests(path.join(__dirname, 'fixtures', 'tree-sitter-tokens.clj'), /;/)
});
it('folds Clojure code', async () => {
setConfigForLanguageMode('wasm-tree-sitter');
await runFoldsTests(path.join(__dirname, 'fixtures', 'tree-sitter-folds.clj'), /;/)
});
});

View File

@ -0,0 +1,16 @@
name: 'CSS'
scopeName: 'source.css'
type: 'modern-tree-sitter'
parser: 'tree-sitter-css'
fileTypes: [
'css'
]
treeSitter:
grammar: 'tree-sitter/tree-sitter-css.wasm'
highlightsQuery: 'tree-sitter/queries/highlights.scm'
foldsQuery: 'tree-sitter/queries/folds.scm'
indentsQuery: 'tree-sitter/queries/indents.scm'
injectionRegex: '^(css|CSS)$'

View File

@ -0,0 +1,2 @@
(block) @fold

View File

@ -0,0 +1,273 @@
; WORKAROUND:
;
; When you're typing a new property name inside of a list, tree-sitter-css will
; assume the thing you're typing is a descendant selector tag name until you
; get to the colon. This prevents it from highlighting the incomplete line like
; a selector tag name.
(descendant_selector
(tag_name) @_IGNORE_
(#set! test.final true))
(ERROR
(attribute_name) @_IGNORE_
(#set! test.final true))
((ERROR
(attribute_name) @invalid.illegal)
(#set! test.final true))
; WORKAROUND:
;
; `:hover` and other pseudo-classes don't highlight correctly inside a media
; query (https://github.com/tree-sitter/tree-sitter-css/issues/28)
(
(ERROR) @entity.other.attribute-name.pseudo-class.css
(#match? @entity.other.attribute-name.pseudo-class.css "^:[\\w-]+$")
)
; WORKAROUND:
;
; In `::after`, the "after" has a node type of `tag_name`. We want to catch it
; here so that it doesn't get scoped like an HTML tag name in a selector.
; Scope the entire `::after` range as one unit.
((pseudo_element_selector)
@entity.other.attribute-name.pseudo-element.css
(#set! adjust.startAt lastChild.previousSibling.startPosition)
(#set! adjust.endAt lastChild.endPosition))
; Claim this range and block it from being scoped as a tag name.
(pseudo_element_selector
(tag_name) @_IGNORE_
(#set! test.onlyIfLast true)
(#set! test.final true))
; COMMENTS
; ========
(comment) @comment.block.css
; Scope the block-comment delimiters (`/*` and `*/`).
((comment) @punctuation.definition.comment.begin.css
(#set! adjust.startAndEndAroundFirstMatchOf "^/\\*"))
((comment) @punctuation.definition.comment.end.css
(#set! adjust.startAndEndAroundFirstMatchOf "\\*/$"))
; SELECTORS
; =========
; (selectors "," @punctuation.separator.list.comma.css)
; The "div" in `div.foo {`.
(tag_name) @entity.name.tag.css
; The "foo" in `div[attr=foo] {`.
(attribute_selector (plain_value) @string.unquoted.css)
[
(child_selector ">")
(sibling_selector "~")
(adjacent_sibling_selector "+")
] @keyword.operator.combinator.css
; The "#foo" in `div#foo {`.
(id_selector
"#" @punctuation.definition.entity.id.css) @entity.other.attribute-name.id.css
; KNOWN ISSUE: Namespace selectors like `svg|link` are not supported. See:
; https://github.com/tree-sitter/tree-sitter-css/issues/33
;(namespace_name) @entity.other.namespace-prefix.css
; The '.' in `.foo`.
(class_selector
"." @punctuation.definition.entity.css)
; The '.foo' in `.foo`.
((class_selector) @entity.other.attribute-name.class.css
(#set! adjust.startAt lastChild.previousSibling.startPosition))
; Pseudo-classes without arguments: the ":first-of-type" in `li:first-of-type`.
((pseudo_class_selector (class_name) (arguments) .) @entity.other.attribute-name.pseudo-class.css
(#set! adjust.startAt lastChild.previousSibling.previousSibling.startPosition)
(#set! adjust.endAt lastChild.previousSibling.endPosition)
(#set! test.final true))
; Pseudo-classes with arguments: the ":nth-of-type" in `li:nth-of-type(2n-1)`.
((pseudo_class_selector (class_name) .) @entity.other.attribute-name.pseudo-class.css
(#set! adjust.startAt lastChild.previousSibling.startPosition)
(#set! adjust.endAt lastChild.endPosition))
(arguments
"(" @punctuation.definition.arguments.begin.bracket.round.css
")" @punctuation.definition.arguments.end.bracket.round.css)
(attribute_selector
"[" @punctuation.definition.entity.begin.bracket.square.css
(attribute_name) @entity.other.attribute-name.css
"]" @punctuation.definition.entity.end.bracket.square.css)
(attribute_selector
["=" "^=" "$=" "~=" "|="] @keyword.operator.pattern.css)
; VARIABLES
; =========
(declaration
(property_name) @variable.other.assignment.css
(#match? @variable.other.assignment.css "^--" )
(#set! test.final true))
; PROPERTIES
; ==========
; TODO: Is it worth it to try to maintain a list of recognized property names?
; Would be useful to know if you've typo'd something, but it would be a
; maintenance headache.
(declaration
(property_name) @support.type.property-name.css)
; VALUES
; ======
; Strings
; -------
((string_value) @string.quoted.double.css
(#match? @string.quoted.double.css "^\"")
(#match? @string.quoted.double.css "\"$"))
((string_value) @string.quoted.single.css
(#match? @string.quoted.single.css "^'")
(#match? @string.quoted.single.css "'$"))
((string_value) @puncutation.definition.string.begin.css
(#set! adjust.startAndEndAroundFirstMatchOf "^[\"']"))
((string_value) @puncutation.definition.string.end.css
(#set! adjust.startAndEndAroundFirstMatchOf "[\"']$"))
; Property value constants
; ------------------------
((plain_value) @support.constant.property-value.css
(#match? @support.constant.property-value.css "^(above|absolute|active|add|additive|after-edge|alias|all|all-petite-caps|all-scroll|all-small-caps|alpha|alphabetic|alternate|alternate-reverse|always|antialiased|auto|auto-pos|available|avoid|avoid-column|avoid-page|avoid-region|backwards|balance|baseline|before-edge|below|bevel|bidi-override|blink|block|block-axis|block-start|block-end|bold|bolder|border|border-box|both|bottom|bottom-outside|break-all|break-word|bullets|butt|capitalize|caption|cell|center|central|char|circle|clip|clone|close-quote|closest-corner|closest-side|col-resize|collapse|color|color-burn|color-dodge|column|column-reverse|common-ligatures|compact|condensed|contain|content|content-box|contents|context-menu|contextual|copy|cover|crisp-edges|crispEdges|crosshair|cyclic|dark|darken|dashed|decimal|default|dense|diagonal-fractions|difference|digits|disabled|disc|discretionary-ligatures|distribute|distribute-all-lines|distribute-letter|distribute-space|dot|dotted|double|double-circle|downleft|downright|e-resize|each-line|ease|ease-in|ease-in-out|ease-out|economy|ellipse|ellipsis|embed|end|evenodd|ew-resize|exact|exclude|exclusion|expanded|extends|extra-condensed|extra-expanded|fallback|farthest-corner|farthest-side|fill|fill-available|fill-box|filled|fit-content|fixed|flat|flex|flex-end|flex-start|flip|flow-root|forwards|freeze|from-image|full-width|geometricPrecision|georgian|grab|grabbing|grayscale|grid|groove|hand|hanging|hard-light|help|hidden|hide|historical-forms|historical-ligatures|horizontal|horizontal-tb|hue|icon|ideograph-alpha|ideograph-numeric|ideograph-parenthesis|ideograph-space|ideographic|inactive|infinite|inherit|initial|inline|inline-axis|inline-block|inline-end|inline-flex|inline-grid|inline-list-item|inline-start|inline-table|inset|inside|inter-character|inter-ideograph|inter-word|intersect|invert|isolate|isolate-override|italic|jis04|jis78|jis83|jis90|justify|justify-all|kannada|keep-all|landscape|large|larger|left|light|lighten|lighter|line|line-edge|line-through|linear|linearRGB|lining-nums|list-item|local|loose|lowercase|lr|lr-tb|ltr|luminance|luminosity|main-size|mandatory|manipulation|manual|margin-box|match-parent|match-source|mathematical|max-content|medium|menu|message-box|middle|min-content|miter|mixed|move|multiply|n-resize|narrower|ne-resize|nearest-neighbor|nesw-resize|newspaper|no-change|no-clip|no-close-quote|no-common-ligatures|no-contextual|no-discretionary-ligatures|no-drop|no-historical-ligatures|no-open-quote|no-repeat|none|nonzero|normal|not-allowed|nowrap|ns-resize|numbers|numeric|nw-resize|nwse-resize|oblique|oldstyle-nums|open|open-quote|optimizeLegibility|optimizeQuality|optimizeSpeed|optional|ordinal|outset|outside|over|overlay|overline|padding|padding-box|page|painted|pan-down|pan-left|pan-right|pan-up|pan-x|pan-y|paused|petite-caps|pixelated|plaintext|pointer|portrait|pre|pre-line|pre-wrap|preserve-3d|progress|progressive|proportional-nums|proportional-width|proximity|radial|recto|region|relative|remove|repeat|repeat-[xy]|reset-size|reverse|revert|ridge|right|rl|rl-tb|round|row|row-resize|row-reverse|row-severse|rtl|ruby|ruby-base|ruby-base-container|ruby-text|ruby-text-container|run-in|running|s-resize|saturation|scale-down|screen|scroll|scroll-position|se-resize|semi-condensed|semi-expanded|separate|sesame|show|sideways|sideways-left|sideways-lr|sideways-right|sideways-rl|simplified|slashed-zero|slice|small|small-caps|small-caption|smaller|smooth|soft-light|solid|space|space-around|space-between|space-evenly|spell-out|square|sRGB|stacked-fractions|start|static|status-bar|swap|step-end|step-start|sticky|stretch|strict|stroke|stroke-box|style|sub|subgrid|subpixel-antialiased|subtract|super|sw-resize|symbolic|table|table-caption|table-cell|table-column|table-column-group|table-footer-group|table-header-group|table-row|table-row-group|tabular-nums|tb|tb-rl|text|text-after-edge|text-before-edge|text-bottom|text-top|thick|thin|titling-caps|top|top-outside|touch|traditional|transparent|triangle|ultra-condensed|ultra-expanded|under|underline|unicase|unset|upleft|uppercase|upright|use-glyph-orientation|use-script|verso|vertical|vertical-ideographic|vertical-lr|vertical-rl|vertical-text|view-box|visible|visibleFill|visiblePainted|visibleStroke|w-resize|wait|wavy|weight|whitespace|wider|words|wrap|wrap-reverse|x|x-large|x-small|xx-large|xx-small|y|zero|zoom-in|zoom-out)$"))
; All property values that have special meaning in `font-family`.
; TODO: Restrict these to be meaningful only when the property name is font-related?
((plain_value) @support.constant.property-value.font-name.css
(#match? @support.constant.property-value.font-name.css "^(serif|sans-serif|monospace|cursive|fantasy|system-ui|ui-serif|ui-sans-serif|ui-monospace|ui-rounded|emoji|math|fangsong)$"))
; All property values that have special meaning in `list-style-type`.
; TODO: Restrict these to be meaningful only when the property name is `list-style-type`?
((plain_value) @support.constant.property-value.list-style-type.css
(#match? @support.constant.property-value.list-style-type.css "^(arabic-indic|armenian|bengali|cambodian|circle|cjk-decimal|cjk-earthly-branch|cjk-heavenly-stem|cjk-ideographic|decimal|decimal-leading-zero|devanagari|disc|disclosure-closed|disclosure-open|ethiopic-halehame-am|ethiopic-halehame-ti-e[rt]|ethiopic-numeric|georgian|gujarati|gurmukhi|hangul|hangul-consonant|hebrew|hiragana|hiragana-iroha|japanese-formal|japanese-informal|kannada|katakana|katakana-iroha|khmer|korean-hangul-formal|korean-hanja-formal|korean-hanja-informal|lao|lower-alpha|lower-armenian|lower-greek|lower-latin|lower-roman|malayalam|mongolian|myanmar|oriya|persian|simp-chinese-formal|simp-chinese-informal|square|tamil|telugu|thai|tibetan|trad-chinese-formal|trad-chinese-informal|upper-alpha|upper-armenian|upper-latin|upper-roman|urdu)$"))
; Numbers & units
; ---------------
; This node type appears to always be a hex color.
(color_value) @constant.other.color.rgb-value.hex.css
[(integer_value) (float_value)] @constant.numeric.css
; All unit types with valid scope names.
((unit) @keyword.other.unit._TEXT_.css
(#match? @keyword.other.unit._TEXT_.css "^(deg|grad|rad|turn|ch|cm|em|ex|fr|in|mm|mozmm|pc|pt|px|q|rem|vh|vmax|vmin|vw|dpi|dpcm|dpps|s|ms)$"))
((unit) @keyword.other.unit.percentage.css
(#eq? @keyword.other.unit.percentage.css "%"))
; The magic color value `currentColor`.
((plain_value) @support.constant.color.current.css
(#eq? @support.constant.color.current.css "currentColor"))
; Match the TM bundle's special treatment of named colors.
((plain_value) @support.constant.color.w3c-standard-color-name.css
(#match? @support.constant.color.w3c-standard-color-name.css "^(aqua|black|blue|fuchsia|gray|green|lime|maroon|navy|olive|orange|purple|red|silver|teal|white|yellow)$"))
((plain_value) @support.constant.color.w3c-extended-color-name.css
(#match? @support.constant.color.w3c-extended-color-name.css "^(aliceblue|antiquewhite|aquamarine|azure|beige|bisque|blanchedalmond|blueviolet|brown|burlywood|cadetblue|chartreuse|chocolate|coral|cornflowerblue|cornsilk|crimson|cyan|darkblue|darkcyan|darkgoldenrod|darkgray|darkgreen|darkgrey|darkkhaki|darkmagenta|darkolivegreen|darkorange|darkorchid|darkred|darksalmon|darkseagreen|darkslateblue|darkslategray|darkslategrey|darkturquoise|darkviolet|deeppink|deepskyblue|dimgray|dimgrey|dodgerblue|firebrick|floralwhite|forestgreen|gainsboro|ghostwhite|gold|goldenrod|greenyellow|grey|honeydew|hotpink|indianred|indigo|ivory|khaki|lavender|lavenderblush|lawngreen|lemonchiffon|lightblue|lightcoral|lightcyan|lightgoldenrodyellow|lightgray|lightgreen|lightgrey|lightpink|lightsalmon|lightseagreen|lightskyblue|lightslategray|lightslategrey|lightsteelblue|lightyellow|limegreen|linen|magenta|mediumaquamarine|mediumblue|mediumorchid|mediumpurple|mediumseagreen|mediumslateblue|mediumspringgreen|mediumturquoise|mediumvioletred|midnightblue|mintcream|mistyrose|moccasin|navajowhite|oldlace|olivedrab|orangered|orchid|palegoldenrod|palegreen|paleturquoise|palevioletred|papayawhip|peachpuff|peru|pink|plum|powderblue|rebeccapurple|rosybrown|royalblue|saddlebrown|salmon|sandybrown|seagreen|seashell|sienna|skyblue|slateblue|slategray|slategrey|snow|springgreen|steelblue|tan|thistle|tomato|transparent|turquoise|violet|wheat|whitesmoke|yellowgreen)$"))
((plain_value) @invalid.deprecated.color.system.css
(#match? @invalid.deprecated.color.system.css "^(ActiveBorder|ActiveCaption|AppWorkspace|Background|ButtonFace|ButtonHighlight|ButtonShadow|ButtonText|CaptionText|GrayText|Highlight|HighlightText|InactiveBorder|InactiveCaption|InactiveCaptionText|InfoBackground|InfoText|Menu|MenuText|Scrollbar|ThreeDDarkShadow|ThreeDFace|ThreeDHighlight|ThreeDLightShadow|ThreeDShadow|Window|WindowFrame|WindowText)$"))
; FUNCTIONS
; (call_expression
; (function_name) @support.function.var.css
; (#eq? @support.function.var.css "var")
; )
((function_name) @support.function.var.css
(arguments (plain_value) @variable.css)
(#eq? @support.function.var.css "var"))
((function_name) @support.function._TEXT_.css
; Because we just handled it above.
(#not-eq? @support.function._TEXT_.css "var"))
((function_name) @_IGNORE_
(arguments (plain_value) @string.unquoted.css)
(#eq? @_IGNORE_ "url"))
; AT-RULES
; ========
"@media" @keyword.control.at-rule.media.css
"@import" @keyword.control.at-rule.import.css
"@charset" @keyword.control.at-rule.charset.css
"@namespace" @keyword.control.at-rule.namespace.css
"@supports" @keyword.control.at-rule.supports.css
"@keyframes" @keyword.control.at-rule.keyframes.css
; The parser is permissive and supports at-rule keywords that don't currently
; exist, so we'll set a fallback scope for those.
((at_keyword) @keyword.control.at-rule.other.css
(#set! test.shy true))
[(to) (from)] @keyword.control._TYPE_.css
(important) @keyword.control.important.css
(keyword_query) @support.constant.css
(feature_name) @support.constant.css
; OPERATORS
; =========
; Used in `@media` queries.
["and" "not" "only" "or"] @keyword.operator.logical._TYPE_.css
; Used in `calc()` and elsewhere.
(binary_expression ["+" "-" "*" "/"] @keyword.operator.arithmetic.css)
; PUNCTUATION
; ===========
"{" @punctuation.brace.curly.begin.css
"}" @punctuation.brace.curly.end.css
";" @punctuation.terminator.rule.css
"," @punctuation.separator.list.comma.css
(pseudo_class_selector
[":" "::"] @punctuation.definition.entity.css)
(":" @punctuation.separator.key-value.css
(#set! test.shy true))
; SECTIONS
; ========
(rule_set (block) @meta.block.inside-selector.css)
((block) @meta.block.css
(#set! test.shy true))
(selectors) @meta.selector.css

View File

@ -0,0 +1,8 @@
[
"{"
] @indent
[
"}"
] @dedent

View File

@ -0,0 +1,37 @@
exports.activate = () => {
atom.grammars.addInjectionPoint('source.css', {
type: 'comment',
language: () => 'todo',
content: (node) => node
});
atom.grammars.addInjectionPoint('source.css', {
type: 'comment',
language: () => 'hyperlink',
content: (node) => node
});
atom.grammars.addInjectionPoint('source.css', {
type: 'string_value',
language: () => 'hyperlink',
content: (node) => node
});
// Catch things like
//
// @import url(https://www.example.com/style.css);
//
// where the URL is unquoted.
atom.grammars.addInjectionPoint('source.css', {
type: 'call_expression',
language: () => 'hyperlink',
content: (node) => {
let functionName = node.descendantsOfType('function_value')[0]?.text;
if (!functionName === 'url') { return null; }
return node.descendantsOfType('plain_value');
}
});
};

View File

@ -7,8 +7,9 @@
"version": "0.45.4",
"engines": {
"atom": "*",
"node": "*"
"node": ">=14"
},
"main": "lib/main",
"repository": "https://github.com/pulsar-edit/pulsar",
"license": "MIT",
"dependencies": {

View File

@ -1,6 +1,6 @@
'.source.css':
'core':
'useTreeSitterParsers': false
'languageParser': 'textmate'
'editor':
'commentStart': '/*'
'commentEnd': '*/'

View File

@ -2,7 +2,7 @@ describe 'CSS grammar', ->
grammar = null
beforeEach ->
atom.config.set('core.useTreeSitterParsers', false)
atom.config.set 'core.useTreeSitterParsers', false
waitsForPromise ->
atom.packages.activatePackage('language-css')

View File

@ -0,0 +1,17 @@
name: 'Go'
scopeName: 'source.go'
type: 'modern-tree-sitter'
parser: 'tree-sitter-go'
fileTypes: [
'go'
]
comments:
start: '// '
treeSitter:
grammar: 'tree-sitter-go/tree-sitter-go.wasm'
highlightsQuery: 'tree-sitter-go/highlights.scm'
foldsQuery: 'tree-sitter-go/folds.scm'
indentsQuery: 'tree-sitter-go/indents.scm'

View File

@ -0,0 +1,9 @@
; This is enough to get all if/else if/else blocks folding without getting in
; each others' way.
(if_statement (block) @fold
(#set! fold.adjustToEndOfPreviousRow true))
(block) @fold
(import_spec_list) @fold
(parameter_list) @fold

View File

@ -0,0 +1,285 @@
; CAVEATS:
; * No support for string placeholders in `Printf` functions (injection
; candidate?)
; COMMENTS
; ========
((comment) @comment.line.double-slash.go
(#match? @comment.line.double-slash.go "^\/\/")
(#set! test.final true))
((comment) @punctuation.definition.comment.go
(#match? @punctuation.definition.comment.go "^\/\/")
(#set! adjust.startAndEndAroundFirstMatchOf "^\/\/"))
((comment) @comment.block.go
(#match? @comment.block.go "^\\/\\*"))
((comment) @punctuation.definition.comment.begin.go
(#match? @punctuation.definition.comment.begin.go "^\\/\\*")
(#set! adjust.startAndEndAroundFirstMatchOf "^\\/\\*"))
((comment) @punctuation.definition.comment.end.go
(#match? @punctuation.definition.comment.end.go "\\*\\/$")
(#set! adjust.startAndEndAroundFirstMatchOf "\\*\\/$"))
; TYPES
; =====
(type_declaration
(type_spec
name: (type_identifier) @entity.name.type.go)
(#set! test.final true))
(type_identifier) @storage.type.other.go
[
"func"
"import"
"package"
] @keyword._TYPE_.go
"type" @storage.type.go
[
"struct"
] @storage.type._TYPE_.go
(struct_type
(field_declaration_list
(field_declaration
(field_identifier) @entity.other.attribute-name.go)))
(keyed_element
(field_identifier) @entity.other.attribute-name.go
.
":" @punctuation.separator.key-value.go)
[
"break"
"case"
"continue"
"default"
"defer"
"else"
"fallthrough"
"for"
"go"
"goto"
"if"
"range"
"return"
"select"
"switch"
] @keyword.control._TYPE_.go
(function_declaration (identifier) @entity.name.function.go)
(call_expression
(identifier) @support.function.builtin.go
(#match? @support.function.builtin.go "^(?:append|cap|close|complex|copy|delete|imag|len|make|new|panic|print|println|real|recover)$")
(#set! test.final true))
(call_expression
(identifier) @support.other.function.go)
(call_expression
(selector_expression
field: (field_identifier) @support.other.function.go))
; OBJECTS
; =======
(selector_expression
operand: (selector_expression
operand: (identifier) @support.other.object.go
field: (field_identifier) @support.other.object.go))
; TODO: If we do this, then we have to do chaining (`a.b.c.Close()`) and that
; won't be fun.
(selector_expression
operand: (identifier) @support.other.object.go)
; PACKAGES
; ========
(package_clause
(package_identifier) @entity.name.package.go
(#set! test.final true))
(package_identifier) @support.object.package.go
; STRINGS
; =======
((interpreted_string_literal "\"") @string.quoted.double.go)
(interpreted_string_literal
"\"" @punctuation.definition.string.begin.go
(#set! test.onlyIfFirst true))
(interpreted_string_literal
"\"" @punctuation.definition.string.end.go
(#set! test.onlyIfLast true))
(escape_sequence) @constant.character.escape.go
(rune_literal) @string.quoted.single.rune.go
(raw_string_literal) @string.quoted.raw.go
((raw_string_literal)
@punctuation.definition.string.begin.go
(#set! adjust.startAndEndAroundFirstMatchOf "^`"))
((raw_string_literal)
@punctuation.definition.string.end.go
(#set! adjust.startAndEndAroundFirstMatchOf "`$"))
; NUMBERS
; =======
(int_literal) @constant.numeric.integer.go
; VARIABLES
; =========
[
"var"
"const"
] @storage.modifier._TYPE_.go
(parameter_declaration (identifier) @variable.parameter.go)
(variadic_parameter_declaration (identifier) @variable.parameter.go)
(range_clause
(expression_list
(identifier) @variable.parameter.range.go))
(const_spec
name: (identifier) @variable.other.assignment.const.go)
(var_spec
name: (identifier) @variable.other.assignment.var.go)
; (var_declaration
; (expression_list
; (identifier) @variable.other.assignment.go))
(short_var_declaration
(expression_list
(identifier) @variable.other.assignment.var.go))
(assignment_statement
(expression_list
(identifier) @variable.other.assigment.go))
; CONSTANTS
; =========
[
(true)
(false)
(nil)
] @constant.language._TYPE_.go
((identifier) @constant.language.iota.go
(#eq? @constant.language.iota.go "iota"))
; OPERATORS
; =========
(binary_expression
[
"=="
"!="
">"
"<"
] @keyword.operator.comparison.go)
[
"="
":="
] @keyword.operator.assignment.go
[
"+="
"-="
"|="
"^="
"*="
"/="
"%="
"<<="
">>="
"&^="
"&="
] @keyword.operator.assignment.compound.go
"<-" @keyword.operator.channel.go
"++" @keyword.operator.increment.go
"--" @keyword.operator.decrement.go
(binary_expression
["+" "-" "*" "/" "%"] @keyword.operator.arithmetic.go)
(binary_expression
["&" "|" "^" "&^" "<<" ">>"] @keyword.operator.arithmetic.bitwise.go)
(binary_expression ["&&" "||"] @keyword.operator.logical.go)
(variadic_parameter_declaration "..." @keyword.operator.ellipsis.go)
(pointer_type "*" @keyword.operator.address.go)
(unary_expression ["&" "*"] @keyword.operator.address.go)
(unary_expression "!" @keyword.operator.unary.go)
"." @keyword.operator.accessor.js
; PUNCTUATION
; ===========
";" @punctuation.terminator.go
"," @punctuation.separator.comma.go
":" @punctuation.separator.colon.go
(parameter_list
"(" @punctuation.definition.parameters.begin.bracket.round.go
")" @punctuation.definition.parameters.end.bracket.round.go
(#set! test.final true))
(composite_literal
body: (literal_value
"{" @punctuation.definition.struct.begin.bracket.curly.go
"}" @punctuation.definition.struct.end.bracket.curly.go
(#set! test.final true)))
"{" @punctuation.definition.begin.bracket.curly.go
"}" @punctuation.definition.end.bracket.curly.go
"(" @punctuation.definition.group.begin.bracket.round.go
")" @punctuation.definition.group.end.bracket.round.go
"[" @punctuation.definition.begin.bracket.square.go
"]" @punctuation.definition.end.bracket.square.go
; META
; ====
(function_declaration
(block) @meta.block.function.go
(#set! test.final true))
(block) @meta.block.go
(import_spec) @meta.import-specifier.go
(import_spec_list) @meta.import-specifier.list.go

View File

@ -0,0 +1,9 @@
[
"{"
"("
] @indent
[
"}"
")"
] @dedent

View File

@ -8,7 +8,7 @@
"license": "MIT",
"engines": {
"atom": "*",
"node": "*"
"node": ">=14"
},
"repository": "https://github.com/pulsar-edit/pulsar",
"dependencies": {

View File

@ -2,7 +2,7 @@ describe 'Go grammar', ->
grammar = null
beforeEach ->
atom.config.set('core.useTreeSitterParsers', false)
atom.config.set 'core.useTreeSitterParsers', false
waitsForPromise ->
atom.packages.activatePackage('language-go')

View File

@ -5,7 +5,8 @@ describe 'Go settings', ->
editor.destroy()
beforeEach ->
atom.config.set('core.useTreeSitterParsers', false)
atom.config.set 'core.useTreeSitterParsers', false
waitsForPromise ->
atom.workspace.open().then (o) ->

View File

@ -0,0 +1,17 @@
name: 'EJS'
scopeName: 'text.html.ejs'
type: 'modern-tree-sitter'
parser: 'tree-sitter-embedded-template'
fileTypes: [
'ejs'
'html.ejs'
]
injectionRegex: '^(ejs|EJS)$'
treeSitter:
grammar: 'tree-sitter-embedded-template/tree-sitter-embedded-template.wasm'
highlightsQuery: 'tree-sitter-embedded-template/ejs/highlights.scm'
foldsQuery: 'tree-sitter-embedded-template/ejs/folds.scm'
indentsQuery: 'tree-sitter-embedded-template/ejs/indents.scm'

View File

@ -0,0 +1,17 @@
name: 'ERB'
scopeName: 'text.html.erb'
type: 'modern-tree-sitter'
parser: 'tree-sitter-embedded-template'
fileTypes: [
'erb'
'html.erb'
]
injectionRegex: '^(erb|ERB)$'
treeSitter:
grammar: 'tree-sitter-embedded-template/tree-sitter-embedded-template.wasm'
highlightsQuery: 'tree-sitter-embedded-template/erb/highlights.scm'
foldsQuery: 'tree-sitter-embedded-template/erb/folds.scm'
indentsQuery: 'tree-sitter-embedded-template/erb/indents.scm'

View File

@ -0,0 +1,22 @@
name: 'HTML'
scopeName: 'text.html.basic'
type: 'modern-tree-sitter'
parser: 'tree-sitter-html'
injectionRegex: '(HTML|html|Html)$'
treeSitter:
grammar: 'tree-sitter-html/tree-sitter-html.wasm'
highlightsQuery: 'tree-sitter-html/highlights.scm'
foldsQuery: 'tree-sitter-html/folds.scm'
indentsQuery: 'tree-sitter-html/indents.scm'
fileTypes: [
'htm'
'html'
'shtml'
]
comments:
start: '<!-- '
end: ' -->'

View File

@ -0,0 +1,30 @@
; We compare these to `firstChild.endPosition` because directives have a habit
; of consuming all their preceding whitespace, including newlines.
([(comment_directive) (directive) (output_directive)] @meta.embedded.block.ejs
(#set! adjust.startBeforeFirstMatchOf "\\S")
(#set! test.onlyIfNotEndsOnSameRowAs firstChild.endPosition))
([(comment_directive) (directive) (output_directive)] @meta.embedded.line.ejs
(#set! adjust.startBeforeFirstMatchOf "\\S")
(#set! test.onlyIfEndsOnSameRowAs firstChild.endPosition))
(comment_directive) @comment.block.ejs
; (output_directive
; ["<%=" "%>"] @keyword.control.directive.output)
;
(directive
["<%" "<%-" "<%_"] @keyword.directive.begin.ejs
(#set! adjust.startBeforeFirstMatchOf "\\S"))
(directive
["_%>" "%>" "-%>"] @keyword.directive.end.ejs)
; BUG: Some `directive` nodes are actually represented as `output_directive`
; nodes.
(output_directive
["<%=" "<%" "<%-" "<%_" "<%%"] @keyword.directive.begin.ejs
(#set! adjust.startBeforeFirstMatchOf "\\S"))
(output_directive
["%>" "-%>" "_%>"] @keyword.directive.end.ejs)

View File

@ -0,0 +1,33 @@
; ((directive
; "%>" @_IGNORE_
; (#match! @_IGNORE_ "^$")
; ) @_IGNORE_
; (#set! test.final true))
; We compare these to `firstChild.endPosition` because directives have a habit
; of consuming all their preceding whitespace, including newlines.
([(comment_directive) (directive) (output_directive)] @meta.embedded.block.erb
(#set! adjust.startBeforeFirstMatchOf "\\S")
(#set! test.onlyIfNotEndsOnSameRowAs firstChild.endPosition))
([(comment_directive) (directive) (output_directive)] @meta.embedded.line.erb
(#set! adjust.startBeforeFirstMatchOf "\\S")
(#set! test.onlyIfEndsOnSameRowAs firstChild.endPosition))
(comment_directive) @comment.block.erb
(directive
["<%" "<%-"] @keyword.directive.begin.erb
(#set! adjust.startBeforeFirstMatchOf "\\S"))
(directive
["%>" "-%>"] @keyword.directive.end.erb)
; BUG: Some `directive` nodes are actually represented as `output_directive`
; nodes.
(output_directive
["<%=" "<%" "<%-"] @keyword.directive.begin.erb
(#set! adjust.startBeforeFirstMatchOf "\\S"))
(output_directive
["%>" "-%>"] @keyword.directive.end.erb)

View File

@ -0,0 +1,15 @@
; (element) @fold
; (style_element) @fold
; (script_element) @fold
[
(element)
(script_element)
(style_element)
] @fold
((comment) @fold
(#set! fold.endAt endPosition)
(#set! fold.offsetEnd -3))

View File

@ -0,0 +1,137 @@
; CAVEATS
; =======
;
; * To recognize HTML entities, we're temporarily using a fork until
; tree-sitter-html#50 lands. That gets us entity recognition in text, but not
; in attribute values. We could fix that by writing a small parser to inject
; into attribute values, but we could also just hold off for now.
;
(doctype) @meta.tag.doctype.html
(doctype
"<!" @punctuation.definition.tag.begin.html
"doctype" @entity.name.tag.doctype.html
">" @punctuation.definition.tag.end.html) @meta.tag.doctype.html
(style_element
(raw_text) @meta.embedded.block.css.html)
(script_element
(raw_text) @meta.embedded.block.js.html)
; COMMENTS
; ========
(comment) @comment.block.html
((comment) @punctuation.definition.comment.begin.html
(#set! adjust.startAndEndAroundFirstMatchOf "^<!--"))
((comment) @punctuation.definition.comment.end.html
(#set! adjust.startAndEndAroundFirstMatchOf "-->$"))
; SUPPORT
; =======
(start_tag
(tag_name) @entity.name.tag.structure._TEXT_.html
(#match? @entity.name.tag.structure._TEXT_.html "^(body|head|html|BODY|HEAD|HTML)$")
(#set! test.final true))
(end_tag
(tag_name) @entity.name.tag.structure._TEXT_.html
(#match? @entity.name.tag.structure._TEXT_.html "^(body|head|html|BODY|HEAD|HTML)$")
(#set! test.final true))
(start_tag
(tag_name) @entity.name.tag.block._TEXT_.html
(#match? @entity.name.tag.block._TEXT_.html "^(address|blockquote|dd|div|section|article|aside|header|footer|nav|menu|dl|dt|fieldset|form|frame|frameset|h1|h2|h3|h4|h5|h6|iframe|noframes|object|ol|p|ul|applet|center|dir|hr|pre|ADDRESS|BLOCKQUOTE|DD|DIV|SECTION|ARTICLE|ASIDE|HEADER|FOOTER|NAV|MENU|DL|DT|FIELDSET|FORM|FRAME|FRAMESET|H1|H2|H3|H4|H5|H6|IFRAME|NOFRAMES|OBJECT|OL|P|UL|APPLET|CENTER|DIR|HR|PRE)$")
(#set! test.final true))
(end_tag
(tag_name) @entity.name.tag.block._TEXT_.html
(#match? @entity.name.tag.block._TEXT_.html "^(address|blockquote|dd|div|section|article|aside|header|footer|nav|menu|dl|dt|fieldset|form|frame|frameset|h1|h2|h3|h4|h5|h6|iframe|noframes|object|ol|p|ul|applet|center|dir|hr|pre|ADDRESS|BLOCKQUOTE|DD|DIV|SECTION|ARTICLE|ASIDE|HEADER|FOOTER|NAV|MENU|DL|DT|FIELDSET|FORM|FRAME|FRAMESET|H1|H2|H3|H4|H5|H6|IFRAME|NOFRAMES|OBJECT|OL|P|UL|APPLET|CENTER|DIR|HR|PRE)$")
(#set! test.final true))
(start_tag
(tag_name) @entity.name.tag.inline._TEXT_.html
(#match? @entity.name.tag.inline._TEXT_.html "^(a|abbr|acronym|area|b|base|basefont|bdo|big|br|button|caption|cite|code|col|colgroup|del|dfn|em|font|head|html|i|img|input|ins|isindex|kbd|label|legend|li|link|map|meta|noscript|optgroup|option|param|q|s|samp|script|select|small|span|strike|strong|style|sub|sup|table|tbody|td|textarea|tfoot|th|thead|title|tr|tt|u|var|A|ABBR|ACRONYM|AREA|B|BASE|BASEFONT|BDO|BIG|BR|BUTTON|CAPTION|CITE|CODE|COL|COLGROUP|DEL|DFN|EM|FONT|HEAD|HTML|I|IMG|INPUT|INS|ISINDEX|KBD|LABEL|LEGEND|LI|LINK|MAP|META|NOSCRIPT|OPTGROUP|OPTION|PARAM|Q|S|SAMP|SCRIPT|SELECT|SMALL|SPAN|STRIKE|STRONG|STYLE|SUB|SUP|TABLE|TBODY|TD|TEXTAREA|TFOOT|TH|THEAD|TITLE|TR|TT|U|VAR)$")
(#set! test.final true))
(end_tag
(tag_name) @entity.name.tag.inline._TEXT_.html
(#match? @entity.name.tag.inline._TEXT_.html "^(a|abbr|acronym|area|b|base|basefont|bdo|big|br|button|caption|cite|code|col|colgroup|del|dfn|em|font|head|html|i|img|input|ins|isindex|kbd|label|legend|li|link|map|meta|noscript|optgroup|option|param|q|s|samp|script|select|small|span|strike|strong|style|sub|sup|table|tbody|td|textarea|tfoot|th|thead|title|tr|tt|u|var|A|ABBR|ACRONYM|AREA|B|BASE|BASEFONT|BDO|BIG|BR|BUTTON|CAPTION|CITE|CODE|COL|COLGROUP|DEL|DFN|EM|FONT|HEAD|HTML|I|IMG|INPUT|INS|ISINDEX|KBD|LABEL|LEGEND|LI|LINK|MAP|META|NOSCRIPT|OPTGROUP|OPTION|PARAM|Q|S|SAMP|SCRIPT|SELECT|SMALL|SPAN|STRIKE|STRONG|STYLE|SUB|SUP|TABLE|TBODY|TD|TEXTAREA|TFOOT|TH|THEAD|TITLE|TR|TT|U|VAR)$")
(#set! test.final true))
; ELEMENTS
; ========
; Tag names
; ---------
(start_tag
"<" @punctuation.definition.tag.begin.html
">" @punctuation.definition.tag.end.html)
; Fallback for any tag that didn't get scoped in the Support section above.
(start_tag
(tag_name) @entity.name.tag.html)
(end_tag
"</" @punctuation.definition.tag.begin.html
">" @punctuation.definition.tag.end.html)
(end_tag
(tag_name) @entity.name.tag.html)
(self_closing_tag
"<" @punctuation.definition.tag.begin.html
(tag_name) @entity.name.tag.html
"/>" @punctuation.definition.tag.end.html)
; Invalid tag names
; -----------------
(erroneous_end_tag) @entity.name.tag.html
(erroneous_end_tag_name) @invalid.illegal.erroneous-end-tag-name.html
; Attributes
; ----------
(attribute "=" @punctuation.separator.key-value.html)
(attribute_name) @entity.other.attribute-name
; Single- and double-quotes around attribute values.
((quoted_attribute_value ["\"" "'"] @punctuation.definition.string.begin.html)
(#set! test.onlyIfFirst true))
((quoted_attribute_value ["\"" "'"] @punctuation.definition.string.end.html)
(#set! test.onlyIfLast true))
; If this matches, the value is double-quoted.
(quoted_attribute_value "\"") @string.quoted.double.html
; If this matches, the value is single-quoted.
(quoted_attribute_value "'") @string.quoted.single.html
; Prevent quoted attribute values from having `string.unquoted` applied.
(quoted_attribute_value
(attribute_value) @_IGNORE_
(#set! test.final true))
; The "foo" in `<div class=foo>`.
; Because of the preceding rule, if this matches and passes all tests, the
; value must be unquoted.
(attribute_value) @string.unquoted.html
; MISC
; ====
(entity) @constant.character.entity.html

View File

@ -0,0 +1,9 @@
((start_tag) @indent
; Only indent if this isn't a self-closing tag.
(#not-match? @indent "^<(?:area|base|br|col|embed|hr|img|input|keygen|link|meta|param|source|track|wbr)\\s"))
; `end_tag` will still match when only `</div` is present. Without enforcing
; the presence of `>`, the dedent happens too soon.
((end_tag) @dedent
(#match? @dedent ">$"))

View File

@ -19,9 +19,43 @@ exports.activate = function() {
}
});
const TODO_PATTERN = /\b(TODO|FIXME|CHANGED|XXX|IDEA|HACK|NOTE|REVIEW|NB|BUG|QUESTION|COMBAK|TEMP|DEBUG|OPTIMIZE|WARNING)\b/;
const HYPERLINK_PATTERN = /\bhttps?:/
atom.grammars.addInjectionPoint('text.html.basic', {
type: 'comment',
language: (node) => {
return TODO_PATTERN.test(node.text) ? 'todo' : undefined;
},
content: (node) => node,
languageScope: null
});
atom.grammars.addInjectionPoint('text.html.basic', {
type: 'comment',
language: (node) => {
return HYPERLINK_PATTERN.test(node.text) ? 'hyperlink' : undefined;
},
content: (node) => node,
languageScope: null
});
atom.grammars.addInjectionPoint('text.html.basic', {
type: 'attribute_value',
language: (node) => {
return HYPERLINK_PATTERN.test(node.text) ? 'hyperlink' : undefined;
},
content: (node) => node,
languageScope: null
});
// TODO: Inject hyperlink grammar into plain text?
// EMBEDDED
atom.grammars.addInjectionPoint('text.html.ejs', {
type: 'template',
language(node) {
language() {
return 'javascript';
},
content(node) {
@ -32,7 +66,7 @@ exports.activate = function() {
atom.grammars.addInjectionPoint('text.html.ejs', {
type: 'template',
language(node) {
language() {
return 'html';
},
content(node) {
@ -42,7 +76,7 @@ exports.activate = function() {
atom.grammars.addInjectionPoint('text.html.erb', {
type: 'template',
language(node) {
language() {
return 'ruby';
},
content(node) {
@ -53,7 +87,7 @@ exports.activate = function() {
atom.grammars.addInjectionPoint('text.html.erb', {
type: 'template',
language(node) {
language() {
return 'html';
},
content(node) {

View File

@ -8,7 +8,7 @@
],
"engines": {
"atom": "*",
"node": "*"
"node": ">=14"
},
"repository": "https://github.com/pulsar-edit/pulsar",
"license": "MIT",

View File

@ -5,7 +5,8 @@ describe 'TextMate HTML grammar', ->
grammar = null
beforeEach ->
atom.config.set('core.useTreeSitterParsers', false)
atom.config.set 'core.useTreeSitterParsers', false
waitsForPromise ->
atom.packages.activatePackage('language-html')

View File

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

View File

@ -0,0 +1,9 @@
# name: 'Hyperlink'
scopeName: 'text.hyperlink'
type: 'modern-tree-sitter'
parser: 'tree-sitter-hyperlink'
injectionRegex: 'hyperlink'
treeSitter:
grammar: 'ts/tree-sitter-hyperlink.wasm'
highlightsQuery: 'ts/highlights.scm'

View File

@ -0,0 +1,2 @@
(url) @markup.underline.link.hyperlink

View File

@ -4,7 +4,7 @@
"description": "Hyperlink colorization in Atom",
"engines": {
"atom": "*",
"node": "*"
"node": ">=14"
},
"repository": "https://github.com/pulsar-edit/pulsar",
"license": "MIT"

View File

@ -0,0 +1,17 @@
name: 'Java'
scopeName: 'source.java'
type: 'modern-tree-sitter'
parser: 'tree-sitter-java'
fileTypes: [
'java'
]
comments:
start: '// '
treeSitter:
grammar: 'tree-sitter-java/tree-sitter-java.wasm'
highlightsQuery: 'tree-sitter-java/highlights.scm'
indentsQuery: 'tree-sitter-java/indents.scm'
foldsQuery: 'tree-sitter-java/folds.scm'

View File

@ -0,0 +1,16 @@
((comment) @fold
(#set! fold.endAt endPosition)
(#set! fold.adjustToEndOfPreviousRow true))
[
(class_body)
(enum_body)
(interface_body)
(annotation_type_body)
(module_body)
(constructor_body)
(block)
(switch_block)
(annotation_argument_list)
(formal_parameters)
] @fold

View File

@ -0,0 +1,401 @@
; COMMENTS
; ========
((comment) @comment.line.double-slash.java
(#match? @comment.line.double-slash.java "^//"))
((comment) @punctuation.definition.comment.java
(#match? @punctuation.definition.comment.java "^//")
(#set! adjust.startAndEndAroundFirstMatchOf "^//"))
((comment) @comment.block.documentation.javadoc.java
(#match? @comment.block.documentation.javadoc.java "^/\\*\\*")
(#set! test.final true)
(#set! highlight.invalidateOnChange true))
((comment) @comment.block.java
(#match? @comment.block.java "^/\\*")
(#set! highlight.invalidateOnChange true))
((comment) @punctuation.definition.comment.begin.java
(#match? @punctuation.definition.comment.begin.java "^/\\*\\*?")
(#set! adjust.startAndEndAroundFirstMatchOf "^/\\*"))
((comment) @punctuation.definition.comment.end.java
(#match? @punctuation.definition.comment.end.java "\\*/$")
(#set! adjust.startAndEndAroundFirstMatchOf "\\*/$"))
; OBJECTS
; =======
(class_declaration
"class" @storage.modifier.class.java
name: (_) @entity.name.type.class.java)
(superclass
"extends" @storage.modifier.extends.java
(type_identifier) @entity.other.inherited-class.java
(#set! test.final true))
(class_declaration body: (_) @meta.class.body.java)
; INTERFACES
; ==========
(interface_declaration
"interface" @storage.modifier.interface
name: (identifier) @entity.name.type.interface.java)
(interface_declaration body: (_) @meta.interface.body.java)
(annotation_type_declaration
"@interface"
@keyword.other.interface.annotated.java
name: (_) @entity.name.type.interface.annotated.java)
(annotation_type_declaration body: (_) @meta.interface.annotated.body.java)
(annotation_type_element_declaration
name: (_) @entity.name.function.interface.java)
(extends_interfaces "extends" @storage.modifier.extends.java)
(extends_interfaces
(interface_type_list
(type_identifier) @entity.other.inherited-class.java)
(#set! test.final true))
(super_interfaces "implements" @storage.modifier.implements.java)
(super_interfaces
(interface_type_list
(type_identifier) @entity.other.inherited-class.java)
(#set! test.final true))
(static_initializer "static" @storage.modifier.static.java)
; ANNOTATIONS
; ===========
(marker_annotation
"@" @punctuation.definition.annotation.java) @meta.declaration.annotation.java
; Cover both the '@' and the 'Foo' in `@Foo`.
((marker_annotation) @storage.type.annotation.java @support.type.annotation.java
(#set! adjust.endAt firstChild.nextSibling.endPosition))
(annotation
"@" @punctuation.definition.annotation.java) @meta.declaration.annotation.java
; Cover both the '@' and the 'Foo' in `@Foo`.
((annotation) @storage.type.annotation.java @support.type.annotation.java
(#set! adjust.endAt firstChild.nextSibling.endPosition))
(element_value_pair key: (_) @variable.other.annotation.element.java)
; TYPES
; =====
(object_creation_expression (type_identifier)
@support.other.class.java
(#set! test.final true))
; WORKAROUND: This matches often when the user is typing, so we shouldn't
; highlight it until we know for sure what it is.
(ERROR
(type_identifier) @_IGNORE_
(#set! test.final true))
; WORKAROUND: A chain like `System.out.println` shouldn't match at all until
; it's out of an ERROR state; this should catch all references no matter how
; long the chain is.
(scoped_type_identifier
(type_identifier) @_IGNORE
(#set! test.onlyIfDescendantOfType ERROR)
(#set! test.final true))
(type_identifier) @storage.type.java
(type_parameter (identifier) @storage.type.java)
(void_type) @storage.type.void.java
(integral_type) @storage.type.integral.java
(floating_point_type) @storage.type.float.java
(boolean_type) @storage.type.boolean.java
[
"public"
"private"
"protected"
"abstract"
"static"
"final"
"strictfp"
"synchronized"
"native"
"transient"
"volatile"
] @storage.modifier._TYPE_.java
(type_arguments (wildcard "?" @storage.type.generic.wildcard.java))
(type_arguments (wildcard "extends" @storage.modifier.extends.java))
(type_arguments (wildcard (super) @storage.modifier.super.java))
(type_bound "extends" @storage.modifier.extends)
(type_bound "&" @punctuation.separator.types.java)
; ENTITIES
; ========
(enum_declaration "enum" @storage.type.enum.java)
(enum_declaration
name: (identifier) @entity.name.type.enum.java)
(enum_declaration (enum_body) @meta.enum.body.java)
(enum_constant
name: (identifier) @constant.other.enum.java)
; FUNCTIONS
; =========
(method_declaration (identifier) @entity.name.function.method.java)
(method_declaration (block) @meta.method.body.java)
(constructor_declaration (identifier) @entity.name.function.method.constructor.java)
(constructor_body) @meta.method.constructor.body.java
(throws "throws" @storage.modifier.throws.java)
(method_invocation (identifier)
@support.other.function.java)
(field_access (identifier) @constant.other.java
(#match? @constant.other.java "^[A-Z][A-Z0-9_\\$]+$")
(#set! test.final true))
(field_access
object: (identifier) @support.other.class.java
(#match? @support.other.class.java "^[A-Z]")
(#set! test.final true))
(field_access
field: (identifier) @support.other.class.java
(#match? @support.other.class.java "^[A-Z]")
(#set! test.final true))
(method_invocation (identifier) @constant.other.java
(#match? @constant.other.java "^[A-Z][A-Z0-9_\\$]+$")
(#set! test.final true))
; VARIABLES
; =========
[(this) (super)] @variable.language._TYPE_.java
(spread_parameter "..." @keyword.operator.ellipsis.java)
(formal_parameter (identifier) @variable.parameter.method.java)
(lambda_expression
parameters: (identifier) @variable.parameter.lambda.java)
(lambda_expression
parameters: (inferred_parameters
(identifier) @variable.parameter.lambda.java))
(variable_declarator
name: (identifier) @variable.other.assignment.java)
; PACKAGES
; ========
(package_declaration) @meta.package.java
"package" @keyword.other.package.java
(import_declaration) @meta.import.java
(import_declaration
["import"] @keyword.other._TYPE_.java)
(import_declaration (asterisk) @variable.language.wildcard.java)
; STRINGS
; =======
(character_literal) @string.quoted.single.java
((character_literal) @punctuation.definition.string.begin.java
(#set! adjust.startAndEndAroundFirstMatchOf "^'"))
((character_literal) @punctuation.definition.string.end.java
(#set! adjust.startAndEndAroundFirstMatchOf "'$"))
(string_literal) @string.quoted.double.java
((string_literal) @punctuation.definition.string.begin.java
(#set! adjust.startAndEndAroundFirstMatchOf "^\""))
((string_literal) @punctuation.definition.string.end.java
(#set! adjust.startAndEndAroundFirstMatchOf "\"$"))
; CAVEAT: Parser doesn't recognize escape sequences in strings.
; KEYWORDS
; ========
[
"assert"
"break"
"case"
"catch"
"continue"
"default"
"do"
"else"
"finally"
"for"
"if"
"new"
"return"
"switch"
"throw"
"try"
"while"
] @keyword.control._TYPE_.java
; MISCELLANEOUS
; =============
; (lambda_expression "->" @storage.type.function.arrow.java)
(catch_type "|" @punctuation.separator.catch.java)
; CONSTANTS
; =========
[
(true)
(false)
] @constant.language.boolean._TYPE_.java
(null_literal) @constant.language.null.java
; NUMBERS
; =======
(decimal_integer_literal) @constant.numeric.integer.decimal.java
(hex_integer_literal) @constant.numeric.integer.hexadecimal.java
(octal_integer_literal) @constant.numeric.integer.octal.java
(binary_integer_literal) @constant.numeric.integer.binary.java
(decimal_floating_point_literal) @constant.numeric.float.decimal.java
(hex_floating_point_literal) @constant.numeric.float.hexadecimal.java
; OPERATORS
; =========
(ternary_expression ["?" ":"] @keyword.operator.ternary.java)
"instanceof" @keyword.operator.instanceof.java
"=" @keyword.operator.assignment.java
[
"+="
"-="
"*="
"/="
"%="
"&="
"|="
"^="
"<<="
">>="
">>>="
] @keyword.operator.assignment.compound.java
(binary_expression
["&&" "||"] @keyword.operator.logical.java)
(unary_expression "!" @keyword.operator.logical.java)
(binary_expression
[
"=="
"!="
"<="
">="
">"
"<"
] @keyword.operator.comparison.java)
(binary_expression
[
"-"
"+"
"*"
"/"
"%"
] @keyword.operator.arithmetic.java)
(binary_expression
["&" "|" "^" "~" "<<" ">>" ">>>"] @keyword.operator.bitwise.java)
"." @keyword.operator.accessor.dot.java
"::" @keyword.operator.accessor.method-reference.java
; PUNCTUATION
; ===========
";" @punctuation.terminator.statement.java
"," @punctuation.separator.comma.java
"->" @punctuation.separator.lambda.java
(if_statement
condition: (parenthesized_expression
"(" @punctuation.definition.expression.begin.bracket.round.java
")" @punctuation.definition.expression.end.bracket.round.java
(#set! test.final true)))
(formal_parameters
"(" @punctuation.definition.parameters.begin.bracket.round.java
")" @punctuation.definition.parameters.end.bracket.round.java
(#set! test.final true))
(argument_list
"(" @punctuation.definition.arguments.begin.bracket.round.java
")" @punctuation.definition.arguments.end.bracket.round.java
(#set! test.final true))
"{" @punctuation.definition.block.begin.bracket.curly.java
"}" @punctuation.definition.block.end.bracket.curly.java
"(" @punctuation.definition.expression.begin.bracket.round.java
")" @punctuation.definition.expression.end.bracket.round.java
"[" @punctuation.definition.array.begin.bracket.square.java
"]" @punctuation.definition.array.end.bracket.square.java
(type_arguments "<" @punctuation.definition.type.begin.bracket.angle.java)
(type_arguments ">" @punctuation.definition.type.end.bracket.angle.java)
(type_parameters "<" @punctuation.definition.type.begin.bracket.angle.java)
(type_parameters ">" @punctuation.definition.type.end.bracket.angle.java)

View File

@ -0,0 +1,25 @@
; The closing brace of a switch statement's body should match the indentation of the line where the switch statement starts.
(switch_statement
body: (switch_block "}" @match
(#set! test.onlyIfLast true))
(#set! indent.matchIndentOf parent.parent.startPosition))
; 'case' and 'default' need to be indented one level more than their containing
; `switch`.
(["case" "default"] @match
(#set! indent.matchIndentOf parent.parent.startPosition)
(#set! indent.offsetIndent 1))
[
"{"
"("
"["
] @indent
[
"}"
")"
"]"
] @dedent
["case" "default"] @indent

View File

@ -0,0 +1,3 @@
exports.activate = () => {
// TODO: Injections for language-todo and language-hyperlink.
};

View File

@ -1,10 +1,11 @@
{
"name": "language-java",
"main": "lib/main",
"version": "0.32.1",
"description": "Java language support in Atom",
"engines": {
"atom": "*",
"node": "*"
"node": ">=14"
},
"repository": "https://github.com/pulsar-edit/pulsar",
"license": "MIT",

View File

@ -2,7 +2,8 @@ describe 'Java grammar', ->
grammar = null
beforeEach ->
atom.config.set('core.useTreeSitterParsers', false)
atom.config.set 'core.useTreeSitterParsers', false
waitsForPromise ->
atom.packages.activatePackage('language-java')

View File

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

View File

@ -2,7 +2,8 @@ describe 'Unified expression language grammar', ->
grammar = null
beforeEach ->
atom.config.set('core.useTreeSitterParsers', false)
atom.config.set 'core.useTreeSitterParsers', false
waitsForPromise ->
atom.packages.activatePackage('language-java')

View File

@ -1,3 +1,7 @@
# "?." means "?." or "."
# Even though sometimes it doesn't seem like using the chaining operator would make any sense, it's still supported.
# Example: Math?.PI
# Negative look-behinds for "." should cover "?."
'scopeName': 'source.js'
'fileTypes': [
'js'
@ -284,8 +288,8 @@
]
}
{
# [.]foo = function...
'begin': '(?=(\\.)?[a-zA-Z_$][\\w$]*\\s*=\\s*(\\basync\\b\\s*)?\\bfunction\\b)'
# [?.]foo = function...
'begin': '(?=(\\?\\.|\\.)?[a-zA-Z_$][\\w$]*\\s*=\\s*(\\basync\\b\\s*)?\\bfunction\\b)'
'end': '(?<=})'
'patterns': [
{
@ -300,10 +304,14 @@
'name': 'meta.function.js'
'patterns': [
{
'match': '(\\.)?([a-zA-Z_$][\\w$]*)\\s*(=)\\s*'
'match': '(\\?\\.|\\.)?([a-zA-Z_$][\\w$]*)\\s*(=)\\s*'
'captures':
'1':
'name': 'meta.delimiter.method.period.js'
'patterns': [
{
'include': '#period_or_illegal_optional_method'
}
]
'2':
'name': 'entity.name.function.js'
'3':
@ -533,10 +541,10 @@
]
}
{
# [.]foo = ... => ...
# [?.]foo = ... => ...
'begin': '''(?x)
(?=
(\\.)?[a-zA-Z_$][\\w$]*
(\\?\\.|\\.)?[a-zA-Z_$][\\w$]*
\\s*(=)\\s*
((\\(([^\\(\\)]*)?\\))|[\\w$]+)
\\s*=>
@ -546,8 +554,8 @@
(?<=})|
((?!
\\s*{|
\\G(\\.)?[a-zA-Z_$][\\w$]*\\s*(=)\\s*\\(|
\\G(\\.)?[a-zA-Z_$][\\w$]*\\s*(=)\\s*[\\w$]+|
\\G(\\?\\.|\\.)?[a-zA-Z_$][\\w$]*\\s*(=)\\s*\\(|
\\G(\\?\\.|\\.)?[a-zA-Z_$][\\w$]*\\s*(=)\\s*[\\w$]+|
\\s*/\\*|\\s*//
)(?=\\s*\\S))
'''
@ -564,10 +572,14 @@
'name': 'meta.function.arrow.js'
'patterns': [
{
'match': '\\G(\\.)?([a-zA-Z_$][\\w$]*)\\s*(=)'
'match': '\\G(\\?\\.|\\.)?([a-zA-Z_$][\\w$]*)\\s*(=)'
'captures':
'1':
'name': 'meta.delimiter.method.period.js'
'patterns': [
{
'include': '#period_or_illegal_optional_method'
}
]
'2':
'name': 'entity.name.function.js'
'3':
@ -720,7 +732,7 @@
'name': 'meta.class.js'
}
{
'match': '(new)\\s+([\\w$]+[\\w.$]*)'
'match': '(new)\\s+([\\w$]+(?:\\??\\.[\\w$]*)*)'
'name': 'meta.class.instance.constructor.js'
'captures':
'1':
@ -729,8 +741,7 @@
'name': 'entity.name.type.instance.js'
'patterns': [
{
'match': '\\.'
'name': 'meta.delimiter.property.period.js'
'include': '#period_or_illegal_optional_property'
}
]
}
@ -743,8 +754,10 @@
'name': 'entity.name.type.object.console.js'
'end': '''(?x)
(?<=\\)) | (?=
(?! (\\s*//)|(\\s*/\\*)|(\\s*(\\.)\\s*
(assert|clear|debug|error|info|log|profile|profileEnd|time|timeEnd|warn)
(?! (\\s*//)|(\\s*/\\*)|(\\s*(\\?\\.|\\.)\\s*
(assert|clear|count|countReset|debug|dir|dirxml|error|group|groupCollapsed|
groupEnd|info|log|profile|profileEnd|table|time|timeEnd|timeLog|trace|
timeStamp|warn)
\\s*\\(
)) \\s*\\S
)
@ -754,10 +767,14 @@
'include': '#comments'
}
{
'begin': '\\s*(\\.)\\s*(\\w+)\\s*(?=\\()'
'begin': '\\s*(\\?\\.|\\.)\\s*(\\w+)\\s*(?=\\()'
'beginCaptures':
'1':
'name': 'meta.delimiter.method.period.js'
'patterns': [
{
'include': '#period_or_optional_method'
}
]
'2':
'name': 'support.function.console.js'
'end': '(?<=\\))'
@ -770,6 +787,45 @@
}
]
}
{
# JSON
'begin': '(?<![\\w$])JSON(?![\\w$]|\\s*:)'
'beginCaptures':
'0':
'name': 'support.class.json.js'
'end': '''(?x)
(?<=\\)) | (?=
(?! (\\s*//)|(\\s*/\\*)|(\\s*(\\?\\.|\\.)\\s*
(parse|stringify)
\\s*\\(
)) \\s*\\S
)
'''
'patterns': [
{
'include': '#comments'
}
{
'begin': '\\s*(\\?\\.|\\.)\\s*(\\w+)\\s*(?=\\()'
'beginCaptures':
'1':
'patterns': [
{
'include': '#period_or_optional_method'
}
]
'2':
'name': 'support.function.json.js'
'end': '(?<=\\))'
'name': 'meta.method-call.js'
'patterns': [
{
'include': '#arguments'
}
]
}
]
}
{
# Math
'begin': '(?<![\\w$])Math(?![\\w$]|\\s*:)'
@ -779,7 +835,7 @@
'end': '''(?x)
(?<=E|LN10|LN2|LOG10E|LOG2E|PI|SQRT1_2|SQRT2|\\)
) | (?=
(?! (\\s*//)|(\\s*/\\*)|(\\s*\\.\\s* (
(?! (\\s*//)|(\\s*/\\*)|(\\s*(\\?\\.|\\.)\\s* (
((abs|acos|acosh|asin|asinh|atan|atan2|atanh|cbrt|ceil|clz32|cos|cosh|exp|
expm1|floor|fround|hypot|imul|log|log10|log1p|log2|max|min|pow|random|
round|sign|sin|sinh|sqrt|tan|tanh|trunc)\\s*\\(
@ -793,10 +849,14 @@
}
{
# Math.random()
'begin': '\\s*(\\.)\\s*(\\w+)\\s*(?=\\()'
'begin': '\\s*(\\?\\.|\\.)\\s*(\\w+)\\s*(?=\\()'
'beginCaptures':
'1':
'name': 'meta.delimiter.method.period.js'
'patterns': [
{
'include': '#period_or_optional_method'
}
]
'2':
'name': 'support.function.math.js'
'end': '(?<=\\))'
@ -809,10 +869,14 @@
}
{
# Math.PI
'match': '\\s*(\\.)\\s*(\\w+)\\b'
'match': '\\s*(\\?\\.|\\.)\\s*(\\w+)\\b'
'captures':
'1':
'name': 'meta.delimiter.property.period.js'
'patterns': [
{
'include': '#period_or_optional_property'
}
]
'2':
'name': 'support.constant.property.math.js'
}
@ -826,7 +890,7 @@
'name': 'support.class.promise.js'
'end': '''(?x)
(?<=\\)) | (?=
(?! (\\s*//)|(\\s*/\\*)|(\\s*\\.\\s*(all|race|reject|resolve)\\s*\\() )\\s*\\S
(?! (\\s*//)|(\\s*/\\*)|(\\s*(\\?\\.|\\.)\\s*(all|allSettled|any|race|reject|resolve)\\s*\\() )\\s*\\S
)
'''
'patterns': [
@ -835,10 +899,14 @@
}
{
# Promise.all()
'begin': '\\s*(\\.)\\s*(\\w+)\\s*(?=\\()'
'begin': '\\s*(\\?\\.|\\.)\\s*(\\w+)\\s*(?=\\()'
'beginCaptures':
'1':
'name': 'meta.delimiter.method.period.js'
'patterns': [
{
'include': '#period_or_optional_method'
}
]
'2':
'name': 'support.function.promise.js'
'end': '(?<=\\))'
@ -941,6 +1009,7 @@
'name': 'meta.control.yield.js'
}
{
# This is "..." so don't need to check "?."
'match': '(?:(?<=\\.{3})|(?<!\\.))\\b(await)(?!\\s*:)\\b'
'name': 'keyword.control.js'
}
@ -955,6 +1024,8 @@
'match': '(?<!\\.)\\b(delete|in|of|instanceof|new|typeof|void)(?!\\s*:)\\b'
'name': 'keyword.operator.$1.js'
}
# By default, three dots = spread
# `keyword.operator.rest.js` is only done in specific cases in functions
{
'match': '\\.\\.\\.'
'name': 'keyword.operator.spread.js'
@ -973,23 +1044,28 @@
}
{
'match': '''(?x) (?<!\\$) \\b
(AggregateError|Array|ArrayBuffer|Atomics|Boolean|DataView|Date|Error|EvalError|Float32Array|Float64Array
|Function|Generator|GeneratorFunction|Int16Array|Int32Array|Int8Array|InternalError|Intl|JSON|Map|Number
(AggregateError|Array|ArrayBuffer|AsyncFunction|AsyncGeneratorFunction|Atomics|BigInt|BigInt64Array
|BigUInt64Array|Boolean|DataView|Date|Error|EvalError|FinalizationRegistry|Float32Array|Float64Array
|Function|Generator|GeneratorFunction|Int16Array|Int32Array|Int8Array|InternalError|Intl|Map|Number
|Object|Proxy|RangeError|ReferenceError|Reflect|RegExp|Set|SharedArrayBuffer|SIMD|String|Symbol|SyntaxError
|TypeError|Uint16Array|Uint32Array|Uint8Array|Uint8ClampedArray|URIError|WeakMap|WeakSet)
|TypedArray|TypeError|Uint16Array|Uint32Array|Uint8Array|Uint8ClampedArray|URIError|WeakMap|WeakRef|WeakSet)
\\b
'''
'name': 'support.class.js'
}
{
'match': '''(?x) (\\.) \\s* (?:
'match': '''(?x) (\\?\\.|\\.) \\s* (?:
(constructor|length|prototype) |
(EPSILON|MAX_SAFE_INTEGER|MAX_VALUE|MIN_SAFE_INTEGER|MIN_VALUE|NEGATIVE_INFINITY|POSITIVE_INFINITY)
)\\b
'''
'captures':
'1':
'name': 'meta.delimiter.property.period.js'
'patterns': [
{
'include': '#period_or_optional_property'
}
]
'2':
'name': 'support.variable.property.js'
'3':
@ -1067,7 +1143,7 @@
'name': 'support.class.dom.js'
}
{
'match': '''(?x) (\\.) \\s*
'match': '''(?x) (\\?\\.|\\.) \\s*
(?:
(ATTRIBUTE_NODE|CDATA_SECTION_NODE|COMMENT_NODE|DOCUMENT_FRAGMENT_NODE|DOCUMENT_NODE|DOCUMENT_TYPE_NODE
|DOMSTRING_SIZE_ERR|ELEMENT_NODE|ENTITY_NODE|ENTITY_REFERENCE_NODE|HIERARCHY_REQUEST_ERR|INDEX_SIZE_ERR
@ -1105,7 +1181,11 @@
'''
'captures':
'1':
'name': 'meta.delimiter.property.period.js'
'patterns': [
{
'include': '#period_or_optional_property'
}
]
'2':
'name': 'support.constant.dom.js'
'3':
@ -1138,7 +1218,7 @@
]
}
{
'begin': '\\?'
'begin': '(?<!\\?)\\?(?!\\.|\\?)'
'beginCaptures':
'0':
'name': 'keyword.operator.ternary.js'
@ -1189,10 +1269,6 @@
'match': ','
'name': 'meta.delimiter.object.comma.js'
}
{
'match': '\\.'
'name': 'meta.delimiter.method.period.js'
}
{
# Allows the special return snippet to fire.
'match': '({)(})'
@ -1233,6 +1309,7 @@
]
}
{
# This is inconsistent with the () and {}, however the current tests *rely* on this
'match': '\\[|\\]'
'name': 'meta.brace.square.js'
}
@ -1314,13 +1391,25 @@
'operators':
'patterns': [
{
'match': '%=|\\+=|-=|\\*=|(?<!\\()/='
# %= **= *= /= += -=
'match': '%=|\\*\\*=|\\*=|(?<!\\()/=|\\+=|-='
'name': 'keyword.operator.assignment.compound.js'
}
{
'match': '&=|\\^=|<<=|>>=|>>>=|\\|='
# &&= ||= ??=
'match': '&&=|\\|\\|=|\\?\\?='
'name': 'keyword.operator.assignment.compound.logical.js'
}
{
'match': '&=|\\^=|\\|='
'name': 'keyword.operator.assignment.compound.bitwise.js'
}
{
'match': '<<=|>>=|>>>='
'name': 'keyword.operator.assignment.compound.bitwise.shift.js'
}
# Bitwise shift to match >> instead of >
# Then comparison to match != instead of !
{
'match': '<<|>>>|>>'
'name': 'keyword.operator.bitwise.shift.js'
@ -1330,7 +1419,8 @@
'name': 'keyword.operator.comparison.js'
}
{
'match': '&&|!!|!|\\|\\|'
# One of: && !! ! || ??
'match': '&&|!!|!|\\|\\||\\?\\?'
'name': 'keyword.operator.logical.js'
}
{
@ -1350,7 +1440,7 @@
'name': 'keyword.operator.increment.js'
}
{
'match': '%|\\*|/|-|\\+'
'match': '%|\\*\\*|\\*|/|-|\\+'
'name': 'keyword.operator.js'
}
]
@ -1447,11 +1537,17 @@
]
}
{
'begin': '(Relay\\.QL|gql)\\s*(`)'
'begin': '(Relay(\\?\\.|\\.)QL|gql)\\s*(`)'
'beginCaptures':
'1':
'name': 'entity.name.function.js'
'2':
'patterns': [
{
'include': '#period_or_optional_method'
}
]
'3':
'name': 'punctuation.definition.string.begin.js'
'end': '`'
'endCaptures':
@ -1559,7 +1655,38 @@
'match': '(\\.\\.\\.)([a-zA-Z_$][\\w$]*)'
'captures':
'1':
'name': 'keyword.operator.spread.js'
'name': 'keyword.operator.rest.js'
'2':
'name': 'variable.parameter.rest.function.js'
}
{
'include': '$self'
}
{
'match': '[a-zA-Z_$][\\w$]*'
'name': 'variable.parameter.function.js'
}
]
}
{
# Or starting with optional chaining
'begin': '(\\?\\.)(\\()'
'beginCaptures':
'1':
'name': 'meta.delimiter.method.optional.js'
'2':
'name': 'punctuation.definition.parameters.begin.bracket.round.js'
'end': '\\)'
'endCaptures':
'0':
'name': 'punctuation.definition.parameters.end.bracket.round.js'
'name': 'meta.parameters.js'
'patterns': [
{
'match': '(\\.\\.\\.)([a-zA-Z_$][\\w$]*)'
'captures':
'1':
'name': 'keyword.operator.rest.js'
'2':
'name': 'variable.parameter.rest.function.js'
}
@ -1662,104 +1789,19 @@
'method_calls':
'patterns': [
{
# .methodCall(arg1, "arg2", [...])
'begin': '(\\.)\\s*([\\w$]+)\\s*(?=\\()'
# [?].methodCall(arg1, "arg2", [...])
'begin': '(\\?\\.|\\.)\\s*([\\w$]+)\\s*(?=\\()'
'beginCaptures':
'1':
'name': 'meta.delimiter.method.period.js'
'patterns': [
{
'include': '#period_or_optional_method'
}
]
'2':
'patterns': [
{
'match': '''(?x)
\\bon(Rowsinserted|Rowsdelete|Rowenter|Rowexit|Resize|Resizestart|Resizeend|Reset|
Readystatechange|Mouseout|Mouseover|Mousedown|Mouseup|Mousemove|
Before(cut|deactivate|unload|update|paste|print|editfocus|activate)|
Blur|Scrolltop|Submit|Select|Selectstart|Selectionchange|Hover|Help|
Change|Contextmenu|Controlselect|Cut|Cellchange|Clock|Close|Deactivate|
Datasetchanged|Datasetcomplete|Dataavailable|Drop|Drag|Dragstart|Dragover|
Dragdrop|Dragenter|Dragend|Dragleave|Dblclick|Unload|Paste|Propertychange|Error|
Errorupdate|Keydown|Keyup|Keypress|Focus|Load|Activate|Afterupdate|Afterprint|Abort)\\b
'''
'name': 'support.function.event-handler.js'
}
{
'match': '''(?x)
\\b(catch|finally|then|shift|showModelessDialog|showModalDialog|showHelp|scroll|scrollX|scrollByPages|
scrollByLines|scrollY|scrollTo|stop|strike|sizeToContent|sidebar|signText|sort|
sup|sub|substr|substring|splice|split|send|set(Milliseconds|Seconds|Minutes|Hours|
Month|Year|FullYear|Date|UTC(Milliseconds|Seconds|Minutes|Hours|Month|FullYear|Date)|
Time|Hotkeys|Cursor|ZOptions|Active|Resizable|RequestHeader)|search|slice|
savePreferences|small|home|handleEvent|navigate|char|charCodeAt|charAt|concat|
contextual|confirm|compile|clear|captureEvents|call|createStyleSheet|createPopup|
createEventObject|to(GMTString|UTCString|String|Source|UpperCase|LowerCase|LocaleString)|
test|taint|taintEnabled|indexOf|italics|disableExternalCapture|dump|detachEvent|unshift|
untaint|unwatch|updateCommands|join|javaEnabled|pop|push|plugins.refresh|paddings|parse|stringify|
print|prompt|preference|enableExternalCapture|exec|execScript|valueOf|UTC|find|file|
fileModifiedDate|fileSize|fileCreatedDate|fileUpdatedDate|fixed|fontsize|fontcolor|
forward|fromCharCode|watch|link|load|lastIndexOf|anchor|attachEvent|atob|apply|alert|
abort|routeEvents|resize|resizeBy|resizeTo|recalc|returnValue|replace|reverse|reload|
releaseCapture|releaseEvents|go|get(Milliseconds|Seconds|Minutes|Hours|Month|Day|Year|FullYear|
Time|Date|TimezoneOffset|UTC(Milliseconds|Seconds|Minutes|Hours|Day|Month|FullYear|Date)|
Attention|Selection|ResponseHeader|AllResponseHeaders)|moveBy|moveBelow|moveTo|
moveToAbsolute|moveAbove|mergeAttributes|match|margins|btoa|big|bold|borderWidths|blink|back)\\b
'''
'name': 'support.function.js'
}
{
'match': '''(?x)
\\b(acceptNode|add|addEventListener|addTextTrack|adoptNode|after|animate|append|
appendChild|appendData|before|blur|canPlayType|captureStream|
caretPositionFromPoint|caretRangeFromPoint|checkValidity|clear|click|
cloneContents|cloneNode|cloneRange|close|closest|collapse|
compareBoundaryPoints|compareDocumentPosition|comparePoint|contains|
convertPointFromNode|convertQuadFromNode|convertRectFromNode|createAttribute|
createAttributeNS|createCaption|createCDATASection|createComment|
createContextualFragment|createDocument|createDocumentFragment|
createDocumentType|createElement|createElementNS|createEntityReference|
createEvent|createExpression|createHTMLDocument|createNodeIterator|
createNSResolver|createProcessingInstruction|createRange|createShadowRoot|
createTBody|createTextNode|createTFoot|createTHead|createTreeWalker|delete|
deleteCaption|deleteCell|deleteContents|deleteData|deleteRow|deleteTFoot|
deleteTHead|detach|disconnect|dispatchEvent|elementFromPoint|elementsFromPoint|
enableStyleSheetsForSet|entries|evaluate|execCommand|exitFullscreen|
exitPointerLock|expand|extractContents|fastSeek|firstChild|focus|forEach|get|
getAll|getAnimations|getAttribute|getAttributeNames|getAttributeNode|
getAttributeNodeNS|getAttributeNS|getBoundingClientRect|getBoxQuads|
getClientRects|getContext|getDestinationInsertionPoints|getElementById|
getElementsByClassName|getElementsByName|getElementsByTagName|
getElementsByTagNameNS|getItem|getNamedItem|getSelection|getStartDate|
getVideoPlaybackQuality|has|hasAttribute|hasAttributeNS|hasAttributes|
hasChildNodes|hasFeature|hasFocus|importNode|initEvent|insertAdjacentElement|
insertAdjacentHTML|insertAdjacentText|insertBefore|insertCell|insertData|
insertNode|insertRow|intersectsNode|isDefaultNamespace|isEqualNode|
isPointInRange|isSameNode|item|key|keys|lastChild|load|lookupNamespaceURI|
lookupPrefix|matches|move|moveAttribute|moveAttributeNode|moveChild|
moveNamedItem|namedItem|nextNode|nextSibling|normalize|observe|open|
parentNode|pause|play|postMessage|prepend|preventDefault|previousNode|
previousSibling|probablySupportsContext|queryCommandEnabled|
queryCommandIndeterm|queryCommandState|queryCommandSupported|queryCommandValue|
querySelector|querySelectorAll|registerContentHandler|registerElement|
registerProtocolHandler|releaseCapture|releaseEvents|remove|removeAttribute|
removeAttributeNode|removeAttributeNS|removeChild|removeEventListener|
removeItem|replace|replaceChild|replaceData|replaceWith|reportValidity|
requestFullscreen|requestPointerLock|reset|scroll|scrollBy|scrollIntoView|
scrollTo|seekToNextFrame|select|selectNode|selectNodeContents|set|setAttribute|
setAttributeNode|setAttributeNodeNS|setAttributeNS|setCapture|
setCustomValidity|setEnd|setEndAfter|setEndBefore|setItem|setNamedItem|
setRangeText|setSelectionRange|setSinkId|setStart|setStartAfter|setStartBefore|
slice|splitText|stepDown|stepUp|stopImmediatePropagation|stopPropagation|
submit|substringData|supports|surroundContents|takeRecords|terminate|toBlob|
toDataURL|toggle|toString|values|write|writeln)\\b
'''
'name': 'support.function.dom.js'
}
{
'match': "[a-zA-Z_$][\\w$]*"
'name': 'entity.name.function.js'
}
{
'match': '\\d[\\w$]*'
'name': 'invalid.illegal.identifier.js'
'include': '#method_names'
}
]
'end': '(?<=\\))'
@ -1770,6 +1812,142 @@
}
]
}
{
# [?].methodCall?.(arg1, "arg2", [...])
'begin': '(\\?\\.|\\.)\\s*([\\w$]+)\\s*(\\?\\.)\\s*(?=\\()'
'beginCaptures':
'1':
'patterns': [
{
'include': '#period_or_optional_method'
}
]
'2':
'patterns': [
{
'include': '#method_names'
}
]
'3':
'name': 'meta.delimiter.method.optional.js'
'end': '(?<=\\))'
'name': 'meta.method-call.js'
'patterns': [
{
'include': '#arguments'
}
]
}
{
# [array]?.(arg1, "arg2", [...])
# The array itself is not in the method-call scope, maybe later
'begin': '(?<=\\])\\s*(\\?\\.)\\s*(?=\\()'
'beginCaptures':
'1':
'name': 'meta.delimiter.method.optional.js'
'end': '(?<=\\))'
'name': 'meta.method-call.js'
'patterns': [
{
'include': '#arguments'
}
]
}
]
'method_names':
'patterns': [
{
'match': '''(?x)
\\bon(Rowsinserted|Rowsdelete|Rowenter|Rowexit|Resize|Resizestart|Resizeend|Reset|
Readystatechange|Mouseout|Mouseover|Mousedown|Mouseup|Mousemove|
Before(cut|deactivate|unload|update|paste|print|editfocus|activate)|
Blur|Scrolltop|Submit|Select|Selectstart|Selectionchange|Hover|Help|
Change|Contextmenu|Controlselect|Cut|Cellchange|Clock|Close|Deactivate|
Datasetchanged|Datasetcomplete|Dataavailable|Drop|Drag|Dragstart|Dragover|
Dragdrop|Dragenter|Dragend|Dragleave|Dblclick|Unload|Paste|Propertychange|Error|
Errorupdate|Keydown|Keyup|Keypress|Focus|Load|Activate|Afterupdate|Afterprint|Abort)\\b
'''
'name': 'support.function.event-handler.js'
}
{
'match': '''(?x)
\\b(catch|finally|then|shift|showModelessDialog|showModalDialog|showHelp|scroll|scrollX|scrollByPages|
scrollByLines|scrollY|scrollTo|stop|strike|sizeToContent|sidebar|signText|sort|
sup|sub|substr|substring|splice|split|send|set(Milliseconds|Seconds|Minutes|Hours|
Month|Year|FullYear|Date|UTC(Milliseconds|Seconds|Minutes|Hours|Month|FullYear|Date)|
Time|Hotkeys|Cursor|ZOptions|Active|Resizable|RequestHeader)|search|slice|
savePreferences|small|home|handleEvent|navigate|char|charCodeAt|charAt|concat|
contextual|confirm|compile|clear|captureEvents|call|createStyleSheet|createPopup|
createEventObject|to(GMTString|UTCString|String|Source|UpperCase|LowerCase|LocaleString)|
test|taint|taintEnabled|indexOf|italics|disableExternalCapture|dump|detachEvent|unshift|
untaint|unwatch|updateCommands|join|javaEnabled|pop|push|plugins.refresh|paddings|parse|stringify|
print|prompt|preference|enableExternalCapture|exec|execScript|valueOf|UTC|find|file|
fileModifiedDate|fileSize|fileCreatedDate|fileUpdatedDate|fixed|fontsize|fontcolor|
forward|fromCharCode|watch|link|load|lastIndexOf|anchor|attachEvent|atob|apply|alert|
abort|routeEvents|resize|resizeBy|resizeTo|recalc|returnValue|replace|reverse|reload|
releaseCapture|releaseEvents|go|get(Milliseconds|Seconds|Minutes|Hours|Month|Day|Year|FullYear|
Time|Date|TimezoneOffset|UTC(Milliseconds|Seconds|Minutes|Hours|Day|Month|FullYear|Date)|
Attention|Selection|ResponseHeader|AllResponseHeaders)|moveBy|moveBelow|moveTo|
moveToAbsolute|moveAbove|mergeAttributes|match|margins|btoa|big|bold|borderWidths|blink|back)\\b
'''
'name': 'support.function.js'
}
{
'match': '''(?x)
\\b(acceptNode|add|addEventListener|addTextTrack|adoptNode|after|animate|append|
appendChild|appendData|before|blur|canPlayType|captureStream|
caretPositionFromPoint|caretRangeFromPoint|checkValidity|clear|click|
cloneContents|cloneNode|cloneRange|close|closest|collapse|
compareBoundaryPoints|compareDocumentPosition|comparePoint|contains|
convertPointFromNode|convertQuadFromNode|convertRectFromNode|createAttribute|
createAttributeNS|createCaption|createCDATASection|createComment|
createContextualFragment|createDocument|createDocumentFragment|
createDocumentType|createElement|createElementNS|createEntityReference|
createEvent|createExpression|createHTMLDocument|createNodeIterator|
createNSResolver|createProcessingInstruction|createRange|createShadowRoot|
createTBody|createTextNode|createTFoot|createTHead|createTreeWalker|delete|
deleteCaption|deleteCell|deleteContents|deleteData|deleteRow|deleteTFoot|
deleteTHead|detach|disconnect|dispatchEvent|elementFromPoint|elementsFromPoint|
enableStyleSheetsForSet|entries|evaluate|execCommand|exitFullscreen|
exitPointerLock|expand|extractContents|fastSeek|firstChild|focus|forEach|get|
getAll|getAnimations|getAttribute|getAttributeNames|getAttributeNode|
getAttributeNodeNS|getAttributeNS|getBoundingClientRect|getBoxQuads|
getClientRects|getContext|getDestinationInsertionPoints|getElementById|
getElementsByClassName|getElementsByName|getElementsByTagName|
getElementsByTagNameNS|getItem|getNamedItem|getSelection|getStartDate|
getVideoPlaybackQuality|has|hasAttribute|hasAttributeNS|hasAttributes|
hasChildNodes|hasFeature|hasFocus|importNode|initEvent|insertAdjacentElement|
insertAdjacentHTML|insertAdjacentText|insertBefore|insertCell|insertData|
insertNode|insertRow|intersectsNode|isDefaultNamespace|isEqualNode|
isPointInRange|isSameNode|item|key|keys|lastChild|load|lookupNamespaceURI|
lookupPrefix|matches|move|moveAttribute|moveAttributeNode|moveChild|
moveNamedItem|namedItem|nextNode|nextSibling|normalize|observe|open|
parentNode|pause|play|postMessage|prepend|preventDefault|previousNode|
previousSibling|probablySupportsContext|queryCommandEnabled|
queryCommandIndeterm|queryCommandState|queryCommandSupported|queryCommandValue|
querySelector|querySelectorAll|registerContentHandler|registerElement|
registerProtocolHandler|releaseCapture|releaseEvents|remove|removeAttribute|
removeAttributeNode|removeAttributeNS|removeChild|removeEventListener|
removeItem|replace|replaceChild|replaceData|replaceWith|reportValidity|
requestFullscreen|requestPointerLock|reset|scroll|scrollBy|scrollIntoView|
scrollTo|seekToNextFrame|select|selectNode|selectNodeContents|set|setAttribute|
setAttributeNode|setAttributeNodeNS|setAttributeNS|setCapture|
setCustomValidity|setEnd|setEndAfter|setEndBefore|setItem|setNamedItem|
setRangeText|setSelectionRange|setSinkId|setStart|setStartAfter|setStartBefore|
slice|splitText|stepDown|stepUp|stopImmediatePropagation|stopPropagation|
submit|substringData|supports|surroundContents|takeRecords|terminate|toBlob|
toDataURL|toggle|toString|values|write|writeln)\\b
'''
'name': 'support.function.dom.js'
}
{
'match': "[a-zA-Z_$][\\w$]*"
'name': 'entity.name.function.js'
}
{
'match': '\\d[\\w$]*'
'name': 'invalid.illegal.identifier.js'
}
]
'function_calls':
'patterns': [
@ -1804,64 +1982,149 @@
}
]
}
{
# functionCall?.(arg1, "arg2", [...])
'begin': '([\\w$]+)\\s*(\\?\\.)(?=\\()'
'beginCaptures':
'1':
'patterns': [
{
'match': '''(?x)
\\b(isNaN|isFinite|eval|uneval|parseInt|parseFloat|decodeURI|
decodeURIComponent|encodeURI|encodeURIComponent|escape|unescape|
require|set(Interval|Timeout)|clear(Interval|Timeout))\\b
'''
'name': 'support.function.js'
}
{
'match': "[a-zA-Z_$][\\w$]*"
'name': 'entity.name.function.js'
}
{
'match': '\\d[\\w$]*'
'name': 'invalid.illegal.identifier.js'
}
]
'2':
'name': 'meta.delimiter.method.optional.js'
'end': '(?<=\\))'
'name': 'meta.function-call.js'
'patterns': [
{
'include': '#arguments'
}
]
}
]
'objects':
'patterns': [
# "Obj." is already enough to prove objectness,
# however "Obj.[8]" is impossible, and
# "Obj?.()" is a function
{
# OBJ in OBJ.prop, OBJ.methodCall()
'match': '[A-Z][A-Z0-9_$]*(?=\\s*\\.\\s*[a-zA-Z_$]\\w*)'
'match': '[A-Z][A-Z0-9_$]*(?=\\s*\\??\\.\\s*[a-zA-Z_$]\\w*)'
'name': 'constant.other.object.js'
}
{
# obj in obj.prop, obj.methodCall()
'match': '[a-zA-Z_$][\\w$]*(?=\\s*\\.\\s*[a-zA-Z_$]\\w*)'
'match': '[a-zA-Z_$][\\w$]*(?=\\s*\\??\\.\\s*[a-zA-Z_$]\\w*)'
'name': 'variable.other.object.js'
}
{
# OBJ in OBJ[8], OBJ?.[8]
'match': '[A-Z][A-Z0-9_$]*(?=\\s*(\\?\\.)?\\s*\\[)'
'name': 'constant.other.object.js'
}
{
# obj in obj[8], obj?.[8]
'match': '[a-zA-Z_$][\\w$]*(?=\\s*(\\?\\.)?\\s*\\[)'
'name': 'variable.other.object.js'
}
]
'properties':
'patterns': [
# object.prop includes object?.prop
{
# Specifically ?.[], for example divArray?.[8]
'begin': '(\\?\\.)\\s*(\\[)'
'beginCaptures':
'1':
'name': 'meta.delimiter.property.optional.js'
'2':
'name': 'meta.brace.square.js'
'end': '\\]'
'endCaptures':
'0':
'name': 'meta.brace.square.js'
'patterns': [
{
'include': '$self'
}
]
}
{
# PROP1 in obj.PROP1.prop2, func().PROP1.prop2
'match': '(\\.)\\s*([A-Z][A-Z0-9_$]*\\b\\$*)(?=\\s*\\.\\s*[a-zA-Z_$]\\w*)'
'match': '(\\?\\.|\\.)\\s*([A-Z][A-Z0-9_$]*\\b\\$*)(?=\\s*(\\?\\.|\\.)\\s*[a-zA-Z_$]\\w*)'
'captures':
'1':
'name': 'meta.delimiter.property.period.js'
'patterns': [
{
'include': '#period_or_optional_property'
}
]
'2':
'name': 'constant.other.object.property.js'
}
{
# prop1 in obj.prop1.prop2, func().prop1.prop2
'match': '(\\.)\\s*(\\$*[a-zA-Z_$][\\w$]*)(?=\\s*\\.\\s*[a-zA-Z_$]\\w*)'
'match': '(\\?\\.|\\.)\\s*(\\$*[a-zA-Z_$][\\w$]*)(?=\\s*(\\?\\.|\\.)\\s*[a-zA-Z_$]\\w*)'
'captures':
'1':
'name': 'meta.delimiter.property.period.js'
'patterns': [
{
'include': '#period_or_optional_property'
}
]
'2':
'name': 'variable.other.object.property.js'
}
{
# PROP in obj.PROP, func().PROP
'match': '(\\.)\\s*([A-Z][A-Z0-9_$]*\\b\\$*)'
'match': '(\\?\\.|\\.)\\s*([A-Z][A-Z0-9_$]*\\b\\$*)'
'captures':
'1':
'name': 'meta.delimiter.property.period.js'
'patterns': [
{
'include': '#period_or_optional_property'
}
]
'2':
'name': 'constant.other.property.js'
}
{
# prop in obj.prop, func().prop
'match': '(\\.)\\s*(\\$*[a-zA-Z_$][\\w$]*)'
'match': '(\\?\\.|\\.)\\s*(\\$*[a-zA-Z_$][\\w$]*)'
'captures':
'1':
'name': 'meta.delimiter.property.period.js'
'patterns': [
{
'include': '#period_or_optional_property'
}
]
'2':
'name': 'variable.other.property.js'
}
{
# 123illegal in obj.123illegal, func().123illegal
'match': '(\\.)\\s*([0-9][\\w$]*)'
'match': '(\\?\\.|\\.)\\s*([0-9][\\w$]*)'
'captures':
'1':
'name': 'meta.delimiter.property.period.js'
'patterns': [
{
'include': '#period_or_optional_property'
}
]
'2':
'name': 'invalid.illegal.identifier.js'
}
@ -2026,3 +2289,47 @@
]
}
]
'period_or_illegal_optional_property':
'patterns': [
{
'match': '\\.'
'name': 'meta.delimiter.property.period.js'
}
{
'match': '\\?\\.'
'name': 'invalid.illegal.meta.delimiter.property.optional.js'
}
]
'period_or_illegal_optional_method':
'patterns': [
{
'match': '\\.'
'name': 'meta.delimiter.method.period.js'
}
{
'match': '\\?\\.'
'name': 'invalid.illegal.meta.delimiter.method.optional.js'
}
]
'period_or_optional_property':
'patterns': [
{
'match': '\\.'
'name': 'meta.delimiter.property.period.js'
}
{
'match': '\\?\\.'
'name': 'meta.delimiter.property.optional.js'
}
]
'period_or_optional_method':
'patterns': [
{
'match': '\\.'
'name': 'meta.delimiter.method.period.js'
}
{
'match': '\\?\\.'
'name': 'meta.delimiter.method.optional.js'
}
]

View File

@ -0,0 +1,31 @@
name: 'JavaScript'
scopeName: 'source.js'
type: 'modern-tree-sitter'
parser: 'tree-sitter-javascript'
injectionRegex: '^(js|javascript)$'
treeSitter:
grammar: 'ts/grammar.wasm'
highlightsQuery: 'ts/highlights.scm'
localsQuery: 'ts/locals.scm'
foldsQuery: 'ts/folds.scm'
indentsQuery: 'ts/indents.scm'
tagsQuery: 'ts/tags.scm'
firstLineRegex: [
# shebang line
'^#!.*\\b(node)\\r?\\n'
# vim modeline
'vim\\b.*\\bset\\b.*\\b(filetype|ft|syntax)=(js|javascript)'
]
fileTypes: [
'js',
'cjs',
'mjs',
'jsx'
]
comments:
start: '// '

View File

@ -0,0 +1,12 @@
name: 'JSDoc'
scopeName: 'source.jsdoc'
type: 'modern-tree-sitter'
parser: 'tree-sitter-jsdoc'
injectionRegex: '^jsdoc$'
treeSitter:
grammar: 'ts/jsdoc/tree-sitter-jsdoc.wasm'
highlightsQuery: 'ts/jsdoc/highlights.scm'
foldsQuery: 'ts/jsdoc/folds.scm'
indentsQuery: 'ts/jsdoc/indents.scm'

View File

@ -0,0 +1,9 @@
name: 'JavaScript RegExp'
scopeName: 'source.regexp.js'
type: 'modern-tree-sitter'
parser: 'tree-sitter-regex'
injectionRegex: '^(js-regex)$'
treeSitter:
grammar: 'ts/regex/tree-sitter-regex.wasm'
highlightsQuery: 'ts/regex/highlights.scm'

View File

@ -184,9 +184,13 @@ scopes:
'"*="': 'keyword.operator.js'
'"/="': 'keyword.operator.js'
'"%="': 'keyword.operator.js'
'"**="': 'keyword.operator.js'
'"<<="': 'keyword.operator.js'
'">>="': 'keyword.operator.js'
'">>>="': 'keyword.operator.js'
'"&&="': 'keyword.operator.js'
'"||="': 'keyword.operator.js'
'"??="': 'keyword.operator.js'
'"&="': 'keyword.operator.js'
'"^="': 'keyword.operator.js'
'"|="': 'keyword.operator.js'
@ -196,6 +200,7 @@ scopes:
'"*"': 'keyword.operator.js'
'"/"': 'keyword.operator.js'
'"%"': 'keyword.operator.js'
'"**"': 'keyword.operator.js'
'"=="': 'keyword.operator.js'
'"==="': 'keyword.operator.js'
'"!="': 'keyword.operator.js'
@ -204,25 +209,28 @@ scopes:
'"<="': 'keyword.operator.js'
'">"': 'keyword.operator.js'
'"<"': 'keyword.operator.js'
'":"': 'keyword.operator.js'
'"?"': 'keyword.operator.js'
'"&&"': 'keyword.operator.js'
'"||"': 'keyword.operator.js'
'"??"': 'keyword.operator.js'
'"&"': 'keyword.operator.js'
'"~"': 'keyword.operator.js'
'"^"': 'keyword.operator.js'
'"|"': 'keyword.operator.js'
'"~"': 'keyword.operator.js'
'">>"': 'keyword.operator.js'
'">>>"': 'keyword.operator.js'
'"<<"': 'keyword.operator.js'
'"|"': 'keyword.operator.js'
'"++"': 'keyword.operator.js'
'"--"': 'keyword.operator.js'
'"..."': 'keyword.operator.spread.js'
'"?"': 'keyword.operator.js'
'":"': 'keyword.operator.js'
'"in"': 'keyword.operator.in'
'"instanceof"': 'keyword.operator.instanceof'
'"of"': 'keyword.operator.of'
'"new"': 'keyword.operator.new'
'"delete"': 'keyword.operator.delete'
'"void"': 'keyword.operator.void'
'"typeof"': 'keyword.operator.typeof'
'"get"': 'keyword.operator.setter'
@ -254,7 +262,6 @@ scopes:
'"async"': 'keyword.control'
'"await"': 'keyword.control'
'"debugger"': 'keyword.control'
'"delete"': 'keyword.control'
'jsx_attribute > property_identifier': 'entity.other.attribute-name'
'jsx_opening_element > identifier': 'entity.name.tag'

View File

@ -0,0 +1,73 @@
[
(switch_body)
(class_body)
(object)
(formal_parameters)
(template_string)
(named_imports)
] @fold
((arguments) @fold
(#set! fold.adjustToEndOfPreviousRow true))
; When we've got
;
; if (foo) {
; // something
; } else {
; // something else
; }
;
; we want the folds to work a little differently so that collapsing the `if`
; fold doesn't interfere with our ability to collapse the `else` fold.
((if_statement
consequence: (statement_block) @fold)
(#set! fold.adjustToEndOfPreviousRow true))
(else_clause (statement_block) @fold)
(statement_block) @fold
((comment) @fold
(#set! fold.endAt endPosition)
(#set! fold.adjustEndColumn 0))
; When you have…
;
; <Element
; foo="bar"
; baz="thud">
;
; </Element>
;
; …this will put the fold on line 3 and let you fold up the contents separately
; from the opening tag's attributes.
;
(jsx_element
(jsx_opening_element ">" @fold)
(#set! fold.endAt parent.parent.lastChild.startPosition)
(#set! fold.adjustToEndOfPreviousRow true)
)
; When you have…
;
; <Element
; foo="bar"
; baz="thud"
; >
;
; </Element>
;
; …the presence of the `>` on its own line will let you treat the opening tag's
; attributes and the element's contents as separate folds.
;
(jsx_element
(jsx_opening_element) @fold
(#set! fold.endAt lastChild.previousSibling.endPosition))
((jsx_self_closing_element) @fold
; Exclude both the slash and angle bracket `/>` from the fold.
(#set! fold.endAt lastChild.previousSibling.startPosition))

Binary file not shown.

View File

@ -0,0 +1,936 @@
; STRINGS
; =======
; Single-quoted.
(string "'") @string.quoted.single.js
(string
"'" @punctuation.definition.string.begin.js
(#set! test.onlyIfFirst true))
(string
"'" @punctuation.definition.string.end.js
(#set! test.onlyIfLast true))
; Double-quoted.
(string "\"") @string.quoted.double.js
(string
"\"" @punctuation.definition.string.begin.js
(#set! test.onlyIfFirst true))
(string
"\"" @punctuation.definition.string.end.js
(#set! test.onlyIfLast true))
; Template string (backticks).
(template_string) @string.quoted.template.js
(template_string
"`" @punctuation.definition.string.begin.js
(#set! test.onlyIfFirst true))
(template_string
"`" @punctuation.definition.string.end.js
(#set! test.onlyIfLast true))
; Interpolations inside of template strings.
(template_substitution
"${" @punctuation.definition.template-expression.begin.js
"}" @punctuation.definition.template-expression.end.js
) @meta.embedded.line.interpolation.js
(string
(escape_sequence) @constant.character.escape.js)
(template_string
(escape_sequence) @constant.character.escape.js)
; NUMBERS
; =======
(number) @constant.numeric.js
; VARIABLES
; =========
["var" "const" "let"] @storage.type._TYPE_.js
; A simple variable declaration:
; The "foo" in `let foo = true`
(variable_declarator
name: (identifier) @variable.other.assignment.js)
; A reassignment of a variable declared earlier:
; The "foo" in `foo = true`
(assignment_expression
left: (identifier) @variable.other.assignment.js)
; A variable object destructuring:
; The "foo" in `let { foo } = something`
(assignment_expression
left: (member_expression
property: (property_identifier)) @variable.other.assignment.property.js)
; The "foo" in `foo += 1`.
(augmented_assignment_expression
left: (identifier) @variable.other.assignment.js)
; The "foo" in `foo++`.
(update_expression
argument: (identifier) @variable.other.assignment.js)
; `object_pattern` appears to only be encountered in assignment expressions, so
; this won't match other uses of object/prop shorthand.
((object_pattern
(shorthand_property_identifier_pattern) @variable.other.assignment.destructuring.js))
; A variable object destructuring with default value:
; The "foo" in `let { foo = true } = something`
(object_assignment_pattern
(shorthand_property_identifier_pattern) @variable.other.assignment.destructuring.js)
; A variable object alias destructuring:
; The "bar" and "foo" in `let { bar: foo } = something`
(object_pattern
(pair_pattern
; TODO: This arguably isn't an object key.
key: (_) @entity.other.attribute-name.js
value: (identifier) @variable.other.assignment.destructuring.js))
; A variable object alias destructuring with default value:
; The "bar" and "foo" in `let { bar: foo = true } = something`
(object_pattern
(pair_pattern
; TODO: This arguably isn't an object key.
key: (_) @entity.other.attribute-name.js
value: (assignment_pattern
left: (identifier) @variable.other.assignment.destructuring.js)))
; A variable array destructuring:
; The "foo" and "bar" in `let [foo, bar] = something`
(variable_declarator
(array_pattern
(identifier) @variable.other.assignment.destructuring.js))
; A variable declaration in a for…(in|of) loop:
; The "foo" in `for (let foo of bar) {`
(for_in_statement
left: (identifier) @variable.other.assignment.loop.js)
; A variable array destructuring in a for…(in|of) loop:
; The "foo" and "bar" in `for (let [foo, bar] of baz)`
(for_in_statement
left: (array_pattern
(identifier) @variable.other.assignment.loop.js))
; A variable object destructuring in a for…(in|of) loop:
; The "foo" and "bar" in `for (let { foo, bar } of baz)`
(for_in_statement
left: (object_pattern
(shorthand_property_identifier_pattern) @variable.other.assignment.loop.js))
; A variable object destructuring in a for…(in|of) loop:
; The "foo" in `for (let { bar: foo } of baz)`
(for_in_statement
left: (object_pattern
(pair_pattern
key: (_) @entity.other.attribute-name.js
value: (identifier) @variable.other.assignment.loop.js)
(#set! test.final true)))
; The "error" in `} catch (error) {`
(catch_clause
parameter: (identifier) @variable.other.assignment.catch.js)
; Single parameter of an arrow function:
; The "foo" in `(foo => …)`
(arrow_function parameter: (identifier) @variable.parameter.js)
; PARAMETERS
; ----------
(formal_parameters
[
; The "foo" in `function (foo) {`.
(identifier) @variable.parameter.js
; The "foo" and "bar" in `function ([foo, bar]) {`.
(array_pattern
(identifier) @variable.parameter.destructuring.array.js)
(object_pattern
[
; The "foo" in `function ({ key: foo }) {`.
(pair_pattern value: (identifier) @variable.parameter.destructuring.value.js)
; The "key" in `function ({ key: foo }) {`.
(pair_pattern key: (property_identifier) @variable.parameter.destructuring.key.js)
; The "foo" in `function ({ foo }) {`.
(shorthand_property_identifier_pattern) @variable.parameter.destructuring.shorthand.js
])
])
; The "foo" in `function (...foo) {`.
(formal_parameters
(rest_pattern
(identifier) @variable.parameter.js))
; The "foo" in `function (foo = false) {`.
(formal_parameters
(assignment_pattern
(identifier) @variable.parameter.js))
; FUNCTIONS
; =========
; Named function expressions:
; the "foo" in `let bar = function foo () {`
(function
name: (identifier) @entity.name.function.definition.js)
; Function definitions:
; the "foo" in `function foo () {`
(function_declaration
name: (identifier) @entity.name.function.definition.js)
; Named generator function expressions:
; the "foo" in `let bar = function* foo () {`
(generator_function
name: (identifier) @entity.name.function.generator.definition.js)
; Generator function definitions:
; the "foo" in `function* foo () {`
(generator_function_declaration
name: (identifier) @entity.name.function.generator.definition.js)
; Method definitions:
; the "foo" in `foo () {` (inside a class body)
(method_definition
name: (property_identifier) @entity.name.function.method.definition.js)
; Function property assignment:
; The "foo" in `thing.foo = (arg) => {}`
(assignment_expression
left: (member_expression
property: (property_identifier) @entity.name.function.definition.js
(#set! test.final true))
right: [(arrow_function) (function)])
; Function variable assignment:
; The "foo" in `let foo = function () {`
(variable_declarator
name: (identifier) @entity.name.function.definition.js
value: [(function) (arrow_function)])
; Function variable reassignment:
; The "foo" in `foo = function () {`
(assignment_expression
left: (identifier) @function
right: [(function) (arrow_function)])
; Object key-value pair function:
; The "foo" in `{ foo: function () {} }`
(pair
key: (property_identifier) @entity.name.function.method.definition.js
value: [(function) (arrow_function)])
(function "function" @storage.type.function.js)
(function_declaration "function" @storage.type.function.js)
(generator_function "function" @storage.type.function.js)
(generator_function_declaration "function" @storage.type.function.js)
(generator_function "*" @storage.modifier.generator.js)
(generator_function_declaration "*" @storage.modifier.generator.js)
(method_definition "*" @storage.modifier.generator.js)
; SUPPORT
; =======
; Array methods.
(member_expression
object: (identifier) @support.object.builtin.js
(#eq? @support.object.builtin.js "Array")
property: (property_identifier) @support.function.builtin.js
(#match? @support.function.builtin.js "^(from|isArray|of)$")
(#set! test.final true))
; Date methods.
(member_expression
object: (identifier) @support.object.builtin.js
(#eq? @support.object.builtin.js "Date")
property: (property_identifier) @support.function.builtin.js
(#match? @support.function.builtin.js "^(now|parse|UTC)$")
(#set! test.final true))
; JSON methods.
(member_expression
object: (identifier) @support.object.builtin.js
(#eq? @support.object.builtin.js "JSON")
property: (property_identifier) @support.function.builtin.js
(#match? @support.function.builtin.js "^(parse|stringify)$")
(#set! test.final true))
; Math methods.
(member_expression
object: (identifier) @support.object.builtin.js
(#eq? @support.object.builtin.js "Math")
property: (property_identifier) @support.function.builtin.js
(#match? @support.function.builtin.js "^(abs|acos|acosh|asin|asinh|atan|atanh|atan2|cbrt|ceil|clz32|cos|cosh|exp|expm1|floor|fround|hypot|imul|log|log1p|log10|log2|max|min|pow|random|round|sign|sin|sinh|sqrt|tan|tanh|trunc)$")
(#set! test.final true))
; Object methods.
(member_expression
object: (identifier) @support.object.builtin.js
(#eq? @support.object.builtin.js "Object")
property: (property_identifier) @support.function.builtin.js
(#match? @support.function.builtin.js "^(assign|create|defineProperty|defineProperties|entries|freeze|fromEntries|getOwnPropertyDescriptor|getOwnPropertyDescriptors|getOwnPropertyNames|getOwnPropertySymbols|getPrototypeOf|is|isExtensible|isFrozen|isSealed|keys|preventExtensions|seal|setPrototypeOf|values)$")
(#set! test.final true))
; Reflect methods.
(member_expression
object: (identifier) @support.object.builtin.js
(#eq? @support.object.builtin.js "Reflect")
property: (property_identifier) @support.function.builtin.js
(#match? @support.function.builtin.js "^(apply|construct|defineProperty|deleteProperty|get|getOwnPropertyDescriptor|getPrototypeOf|has|isExtensible|ownKeys|preventExtensions|set|setPrototypeOf)$")
(#set! test.final true))
; Intl.X instantiations.
(new_expression
constructor: (member_expression
object: (identifier) @support.object.builtin.js
(#eq? @support.object.builtin.js "Intl")
property: (property_identifier) @support.class.builtin.js
(#match? @support.class.builtin.js "^(Collator|DateTimeFormat|DisplayNames|ListFormat|Locale|NumberFormat|PluralRules|Segmenter)$"))
(#set! test.final true))
; Built-in class instantiations.
(new_expression
constructor: (identifier) @support.class.builtin.instance.js
(#match? @support.class.builtin.instance.js "^(AggregateError|Array|ArrayBuffer|BigInt64Array|BigUint64Array|Boolean|DataView|Date|Error|EvalError|FinalizationRegistry|Float32Array|Float64Array|Function|ImageCapture|Int8Array|Int16Array|Int32Array|Map|Number|Object|Promise|RangeError|ReferenceError|RegExp|Set|String|SyntaxError|TypeError|Uint8Array|Uint8ClampedArray|Uint16Array|Uint32Array|URIError|URL|WeakMap|WeakRef|WeakSet|XMLHttpRequest)$")
(#set! test.final true))
; Built-in constructors that can be invoked without `new`.
(call_expression
(identifier) @support.function.builtin.js
(#match? @support.function.builtin.js "^(AggregateError|Array|ArrayBuffer|Boolean|BigInt|Error|EvalError|Function|Number|Object|Proxy|RangeError|String|Symbol|SyntaxError|URIError)$")
(#set! test.final true))
; Built-in functions.
(call_expression
(identifier) @support.function.builtin.js
(#match? @support.function.builtin.js "^(decodeURI|decodeURIComponent|encodeURI|encodeURIComponent|eval|isFinite|isNaN|parseFloat|parseInt)$")
(#set! test.final true))
; Built-in `console` functions.
(member_expression
object: (identifier) @support.class.builtin.console.js
(#eq? @support.class.builtin.console.js "console")
property: (property_identifier) @support.function.builtin.console.js
(#match? @support.function.builtin.console.js "^(assert|clear|count(Reset)?|debug|dir(xml)?|error|group(End)?info|log|profile(End)?|table|time(End|Log|Stamp)?|trace|warn)$")
(#set! test.final true))
; Static methods of `Promise`.
(member_expression
object: (identifier) @support.class.builtin.js
(#eq? @support.class.builtin.js "Promise")
property: (property_identifier) @support.function.builtin.js
(#match? @support.function.builtin.js "^(all|allSettled|any|race|resolve|reject)$")
(#set! test.final true))
; All “well-known” symbols (as they are referred to in the spec).
(member_expression
object: (identifier) @support.class.builtin.js
property: (property_identifier) @support.property.builtin.js
(#eq? @support.class.builtin.js "Symbol")
(#match? @support.property.builtin.js "^(asyncIterator|hasInstance|isConcatSpreadable|iterator|match|matchAll|replace|search|split|species|toPrimitive|toStringTag|unscopables)$")
(#set! test.final true))
; Static methods of `Symbol`.
(member_expression
object: (identifier) @support.class.builtin.js
(#eq? @support.class.builtin.js "Symbol")
property: (property_identifier) @support.function.builtin.js
(#match? @support.function.builtin.js "^(for|keyFor)$")
(#set! test.final true))
; Other built-in objects.
((identifier) @support.class.builtin.js
(#match? @support.class.builtin.js "^(Symbol)$")
(#set! test.final true))
; Deprecated built-in functions.
(call_expression
(identifier) @invalid.deprecated.function.js
(#match? @invalid.deprecated.function.js "^(escape|unescape)$")
(#set! test.final true))
; Built-in DOM classes.
((identifier) @support.class.builtin.js
(#match? @support.class.builtin.js "^(Document|Element|HTMLElement|HTMLDocument|HTML(Select|BR|HR|LI|Div|Map|Mod|Pre|Area|Base|Body|Data|Font|Form|Head|Html|Link|Menu|Meta|Slot|Span|Time|Audio|DList|Embed|Image|Input|Label|Media|Meter|OList|Param|Quote|Style|Table|Title|Track|UList|Video|Anchor|Button|Canvas|Dialog|IFrame|Legend|Object|Option|Output|Script|Source|Content|Details|Heading|Marquee|Picture|Unknown|DataList|FieldSet|FrameSet|MenuItem|OptGroup|Progress|TableCol|TableRow|Template|TextArea|Paragraph|TableCell|Options|TableCaption|TableSection|FormControls))$")
(#set! test.final true))
; Deprecated built-in DOM classes.
((identifier) @invalid.deprecated.class.js
(#match? @invalid.deprecated.class.js "^(HTMLShadowElement)$")
(#set! test.final true))
; Built-in DOM methods on `document`.
(call_expression
function: (member_expression
object: (identifier) @support.object.builtin.js
(#eq? @support.object.builtin.js "document")
property: (property_identifier) @support.function.method.builtin.js
(#match? @support.function.method.builtin.js "^(adoptNode|append|caretPositionFromPoint|caretRangeFromPoint|createAttribute(?:NS)?|createCDATASection|createComment|createDocumentFragment|createElement(?:NS)?|createEvent|createNodeIterator|createProcessingInstruction|createRange|createTextNode|createTreeWalker|elementFromPoint|elementsFromPoint|exitFullscreen|exitPictureInPicture|exitPointerLock|getAnimations|getElementById|getElementsByClassName|getElementsByTagName(?:NS)?|getSelection|hasStorageAccess|importNode|prepend|querySelector|querySelectorAll|releaseCapture|replaceChildren|requestStorageAccess|createExpression|createNSResolver|evaluate|getElementsByName|hasFocus|write|writeln|open|close)$")
(#set! test.final true)))
; Built-in DOM methods on nodes. These will show up as builtins on _any_ class, but
; they're distinctive enough that we're OK with that possibility.
(call_expression
function: (member_expression
property: (property_identifier) @support.function.method.builtin.js
(#match? @support.function.method.builtin.js "^(addEventListener|appendChild|cloneNode|compareDocumentPosition|contains|getElementsByClassName|getElementsByTagName(?:NS)?|getRootNode|hasChildNodes|insertBefore|isDefaultNamespace|isEqualNode|isSameNode|lookupPrefix|lookupNamespaceURI|normalize|querySelector|querySelectorAll|removeChild|replaceChild|removeEventListener)$")
(#set! test.final true)))
; FUNCTION CALLS
; ==============
; An invocation of any function.
(call_expression
function: (identifier) @support.other.function.js)
; An invocation of any method.
(call_expression
function: (member_expression
property: (property_identifier) @support.other.function.method.js
(#set! test.final true)))
; OBJECTS
; =======
; The "foo" in `foo.bar`.
(member_expression
object: (identifier) @support.other.object.js)
; The "FOO" in `FOO.bar` should also be scoped as a constant.
(member_expression
object: (identifier) @constant.other.object.js
(#match? @constant.other.object.js "^[_A-Z]+$"))
; The "bar" in `foo.bar`, `foo.bar.baz`, and `foo.bar[baz]`.
(member_expression
property: (property_identifier) @support.other.property.js)
; The "BAR" in `foo.BAR` should also be scoped as a constant.
(member_expression
property: (property_identifier) @constant.other.property.js
(#match? @constant.other.property.js "^[_A-Z]+$"))
; The "foo" in `{ foo: true }`.
(pair
key: (property_identifier) @entity.other.attribute-name.js)
; TODO: This is both a key and a value, so opinions may vary on how to treat it.
; The "foo" in `{ foo }`.
(object
(shorthand_property_identifier) @entity.other.attribute-name.shorthand.js)
; CLASSES
; =======
; The "class" in `class Foo {`.
(class_declaration
"class" @storage.type.class.js)
; The "class" in `bar = class Foo {`
(class
"class" @storage.type.class.js)
; The "Foo" in `class Foo {`.
(class_declaration
name: (identifier) @entity.name.type.class.js)
; The "Foo" in `bar = class Foo {`.
(class
name: (identifier) @entity.name.type.class.js)
; The "Bar" in `class Foo extends Bar {`.
(class_heritage
"extends" @storage.modifier.extends.js
(identifier) @entity.other.inherited-class.js)
; The "Foo" in `new Foo()`.
(new_expression
constructor: (identifier) @support.other.class.instance.js)
; A class getter:
; the "get" in `get foo () {...`
(method_definition
"get" @storage.modifier.getter.js)
; A class setter:
; the "set" in `set foo (value) {...`
(method_definition
"set" @storage.modifier.setter.js)
; IMPORTS/EXPORTS
; ===============
; The "Foo" in `import Foo from './bar'`
(import_clause
(identifier) @variable.other.assignment.import.js)
; The "Foo" in `import { Foo } from './bar'`
(import_specifier
(identifier) @variable.other.assignment.import.js)
; The "Foo" in `export { Foo }`
(export_specifier
name: (identifier) @variable.other.assignment.export.js)
; The "default" in `export { Foo as default }`
(export_specifier
alias: (identifier) @keyword.control.default.js
(#eq? @keyword.control.default.js "default"))
; The "default" in `export default Foo`
(export_statement
"default" @keyword.control.default.js)
; The "Foo" in `export Foo`
(export_statement
(identifier) @variable.other.assignment.export.js)
; COMMENTS
; ========
; Line comments. `//`
((comment) @comment.line.double-slash.js
(#match? @comment.line.double-slash.js "^\/\/"))
((comment) @punctuation.definition.comment.js
(#match? @punctuation.definition.comment.js "^\/\/")
(#set! adjust.startAndEndAroundFirstMatchOf "^\/\/"))
; Block comments. `/* */`
((comment) @comment.block.js
(#match? @comment.block.js "^/\\*")
(#match? @comment.block.js "\\*/$"))
((comment) @punctuation.definition.comment.begin.js
(#match? @punctuation.definition.comment.begin.js "^/\\*")
(#set! adjust.startAndEndAroundFirstMatchOf "^/\\*"))
((comment) @punctuation.definition.comment.end.js
(#match? @punctuation.definition.comment.end.js "\\*/$")
(#set! adjust.startAndEndAroundFirstMatchOf "\\*/$"))
(hash_bang_line) @comment.line.shebang.js
((hash_bang_line) @punctuation.definition.comment.js
(#set! adjust.endAfterFirstMatchOf "^#!"))
; KEYWORDS
; ========
[
"catch"
"finally"
"throw"
"try"
] @keyword.control.trycatch._TYPE_.js
[
"return"
"yield"
"continue"
"break"
"switch"
"case"
"default"
] @keyword.control.flow._TYPE_.js
[
"import"
"from"
"export"
"as"
] @keyword.control._TYPE_.js
[
"delete"
"typeof"
"void"
] @keyword.operator.unary._TYPE_.js
[
"if"
"else"
] @keyword.control.conditional._TYPE_.js
"new" @keyword.operator.new.js
[
"do"
"for"
"in"
"of"
"while"
] @keyword.control.loop._TYPE_.js
"with" @keyword.control.with.js @invalid.deprecated.with.js
["async" "static"] @storage.modifier._TYPE_.js
["await"] @keyword.control.await.js
[
"debugger"
] @keyword.other._TYPE_.js
; BUILTINS
; ========
[
(this)
(super)
] @variable.language._TYPE_.js
((identifier) @support.object.builtin._TEXT_.js
(#match? @support.object.builtin._TEXT_.js "^(arguments|module|window|document)$")
(#is-not? local)
(#set! test.final true))
((identifier) @support.object.builtin.filename.js
(#eq? @support.object.builtin.filename.js "__filename")
(#is-not? local)
(#set! test.final true))
((identifier) @support.object.builtin.dirname.js
(#eq? @support.object.builtin.dirname.js "__dirname")
(#is-not? local)
(#set! test.final true))
((identifier) @support.function.builtin.require.js
(#eq? @support.function.builtin.require.js "require")
(#is-not? local)
(#set! test.final true))
[
(null)
(undefined)
] @constant.language._TYPE_.js
[
(true)
(false)
] @constant.language.boolean._TYPE_.js
((identifier) @constant.language.infinity.js
(#eq? @constant.language.infinity.js "Infinity")
(#set! test.final true))
(arrow_function
"=>" @punctuation.function.arrow.js)
; Things that `LOOK_LIKE_CONSTANTS`.
([(property_identifier) (identifier)] @constant.other.js
(#match? @constant.other.js "^[A-Z_][A-Z0-9_]*$")
(#set! test.shy true))
; TODO: What do we do with computed object keys?
;
; { [foo]: "bar" }
;
; If we scope the whole thing as `entity.other.attribute-name`, it arguably
; looks too similar to an ordinary object key. The Babel grammar scopes the
; `[foo]` as an object key, but the `foo` inside it as a variable. But we
; aren't scoping all identifiers as variables, so we don't have that option.
; (pair
; key: (computed_property_name
; ["[""]"] @entity.other.attribute-name.computed.js))
; REGEX
; =====
; NOTE: An injection grammar tokenizes the contents of regular
; expressions. Thus we're highlighting only sparingly here.
(regex) @string.regexp.js
(regex
"/" @punctuation.definition.string.begin.js
(#set! test.onlyIfFirst true))
(regex
"/" @punctuation.definition.string.end.js
(#set! test.onlyIfLast true))
(regex_flags) @keyword.other.js
; JSX
; ===
; The "Foo" in `<Foo />`.
(jsx_self_closing_element
name: (identifier) @entity.name.tag.js
) @meta.tag.js
; The "Foo" in `<Foo>`.
(jsx_opening_element
name: (identifier) @entity.name.tag.js)
; The "Foo" in `</Foo>`.
(jsx_closing_element
"/" @punctuation.definition.tag.end.js
(#set! test.final true)
name: (identifier) @entity.name.tag.js)
; The "bar" in `<Foo bar={true} />`.
(jsx_attribute
(property_identifier) @entity.other.attribute-name.js)
; All JSX expressions/interpolations within braces.
((jsx_expression) @meta.embedded.block.jsx.js
(#match? @meta.embedded.block.jsx.js "\\n")
(#set! test.final true))
(jsx_expression) @meta.embedded.line.jsx.js
(jsx_opening_element
"<" @punctuation.definition.tag.begin.js
">" @punctuation.definition.tag.end.js)
(jsx_closing_element
"<" @punctuation.definition.tag.begin.js
">" @punctuation.definition.tag.end.js)
(jsx_self_closing_element
"<" @punctuation.definition.tag.begin.js
(#set! test.final true))
((jsx_self_closing_element
; The "/>" in `<Foo />`, extended to cover both anonymous nodes at once.
"/") @punctuation.definition.tag.end.js
(#set! adjust.startAt lastChild.previousSibling.startPosition)
(#set! adjust.endAt lastChild.endPosition)
(#set! test.final true))
; OPERATORS
; ==========
"=" @keyword.operator.assignment.js
["&" "|" "<<" ">>" ">>>" "~" "^"] @keyword.operator.bitwise.js
["&&" "||" "&&" "??" "!"] @keyword.operator.logical.js
"..." @keyword.operator.spread.js
["in" "instanceof"] @keyword.operator.expression._TYPE_.js
["==" "===" "!=" "!=="] @keyword.operator.comparison.js
["++" "--"] @keyword.operator.increment.js
(binary_expression
[">" "<" ">=" "<="] @keyword.operator.relational.js)
(binary_expression
["/" "+" "-" "*" "**" "%"] @keyword.operator.arithmetic.js)
(unary_expression ["+" "-"] @keyword.operator.unary.js)
(ternary_expression ["?" ":"] @keyword.operator.ternary.js)
[
"&&="
"||="
"??="
"+="
"-="
"*="
"**="
"/="
"%="
"^="
"&="
"|="
"<<="
">>="
">>>="
] @keyword.operator.assignment.compound.js
; TODO: There might be debate over whether `.` is treated as punctuation or as
; an operator. But the addition of `?.` to the language makes me feel like both
; should be treated as operators just for the benefit of syntax highlighting.
"." @keyword.operator.accessor.js
; The optional chaining accessor is listed in the bundled `highlights.scm` as
; an anonymous node, but it appears not to be implemented that way, so we can't
; use "?." to target it.
(optional_chain) @keyword.operator.accessor.optional-chaining.js
; Optional chaining is illegal…:
; …on the left-hand side of an assignment.
(assignment_expression
left: (_) @_IGNORE_
(#set! prohibitsOptionalChaining true))
; …within a `new` expression.
(new_expression
constructor: (_) @_IGNORE_
(#set! prohibitsOptionalChaining true))
((optional_chain) @invalid.illegal.optional-chain.js
(#set! test.onlyIfDescendantOfNodeWithData prohibitsOptionalChaining))
; PUNCTUATION
; ===========
(formal_parameters
"(" @punctuation.definition.parameters.begin.bracket.round.js
")"@punctuation.definition.parameters.end.bracket.round.js
(#set! test.final true))
(object
"{" @punctuation.definition.object.begin.bracket.curly.js
"}" @punctuation.definition.object.end.bracket.curly.js
(#set! test.final true))
(arguments
"(" @punctuation.definition.arguments.begin.bracket.round.js
")" @punctuation.definition.arguments.end.bracket.round.js
(#set! test.final true))
(computed_property_name
"[" @punctuation.definition.computed-property.begin.bracket.square.js
"]" @punctuation.definition.computed-property.end.bracket.square.js
(#set! test.final true))
(subscript_expression
"[" @punctuation.definition.subscript.begin.bracket.square.js
"]" @punctuation.definition.subscript.end.bracket.square.js
(#set! test.final true))
(array
"[" @punctuation.definition.array.begin.bracket.square.js
"]" @punctuation.definition.array.end.bracket.square.js
(#set! test.final true))
(array_pattern
"[" @punctuation.definition.array.begin.bracket.square.js
"]" @punctuation.definition.array.end.bracket.square.js
(#set! test.final true))
"{" @punctuation.definition.block.begin.bracket.curly.js
"}" @punctuation.definition.block.end.bracket.curly.js
"(" @punctuation.definition.begin.bracket.round.js
")" @punctuation.definition.end.bracket.round.js
"[" @punctuation.definition.begin.bracket.square.js
"]" @punctuation.definition.end.bracket.square.js
(array
"," @punctuation.separator.array.comma.js
(#set! test.final true))
(array_pattern
"," @punctuation.separator.array.comma.js
(#set! test.final true))
(pair
":" @punctuation.separator.key-value.colon.js
(#set! test.final true))
";" @punctuation.terminator.statement.js
"," @punctuation.separator.comma.js
":" @punctuation.separator.colon.js
; META
; ====
; The interiors of functions (useful for snippets and commands).
(method_definition
body: (statement_block) @meta.block.function.js
(#set! test.final true))
(function_declaration
body: (statement_block) @meta.block.function.js
(#set! test.final true))
(generator_function_declaration
body: (statement_block) @meta.block.function.js
(#set! test.final true))
(function
body: (statement_block) @meta.block.function.js
(#set! test.final true))
(generator_function
body: (statement_block) @meta.block.function.js
(#set! test.final true))
; The interior of a class body (useful for snippets and commands).
(class_body) @meta.block.class.js
; All other sorts of blocks.
(statement_block) @meta.block.js
; The inside of a parameter definition list.
((formal_parameters) @meta.parameters.js
(#set! adjust.startAt firstChild.endPosition)
(#set! adjust.endAt lastChild.startPosition))
; The inside of an object literal.
((object) @meta.object.js
(#set! adjust.startAt firstChild.endPosition)
(#set! adjust.endAt lastChild.startPosition))
; MISC
; ====
; A label. Rare, but it can be used to prefix any statement and to control
; which loop is affected in `continue` or `break` statements. Svelte uses them
; for another purpose.
(statement_identifier) @entity.name.label.js
;
; Inside of the parameters of an arrow function, the highlighting of parameters
; can change while the user is typing. For instance, if the user is adding a
; default value to a parameter, the parse tree will go into an error state
; until there are tokens on either side of `=`.
;
; We're trying to catch and minimize that here. This expression catches and
; highlights any parameters preceding the invalid one, but sadly can't reach
; the invalid parameter itself.
;
; This doesn't happen inside functions defined with the `function` annotation,
; probably because the parsing there is much more straightforward.
; ((sequence_expression
; left: (identifier) @variable.parameter.js)
; right: (arrow_function))
; TODO: Any identifier not yet scoped might as well be scoped as a variable,
; but that's an opinionated choice. We might want to make this configurable.
; ((identifier) @variable.other.other.js
; (#set! test.shy true))

View File

@ -0,0 +1,157 @@
; ((template_string) @ignore
; (#set! test.onlyIfNotOnStartingOrEndingRow true))
; STATEMENT BLOCKS
; ================
; More accurate indentation matching for all blocks delimited by braces.
(statement_block "}" @match
(#set! indent.matchIndentOf parent.firstChild.startPosition))
; SWITCH STATEMENTS
; =================
; The closing brace of a switch statement's body should match the indentation
; of the line where the switch statement starts.
(switch_statement
body: (switch_body "}" @match
(#set! test.onlyIfLast true))
(#set! indent.matchIndentOf parent.startPosition))
; 'case' and 'default' need to be indented one level more than their containing
; `switch`. TODO: Might need to make this configurable.
(["case" "default"] @match
(#set! indent.matchIndentOf parent.parent.startPosition)
(#set! indent.offsetIndent 1))
; ONE-LINE CONDITIONALS
; =====================
; An `if` statement without an opening brace should indent the next line…
(if_statement
condition: (parenthesized_expression ")" @indent
(#set! test.onlyIfLastTextOnRow true)))
; (as should a braceless `else`…)
("else" @indent
(#set! test.onlyIfLastTextOnRow true))
; …and keep that indent level if the user types a comment before the
; consequence…
(if_statement
consequence: (empty_statement) @match
(#set! test.onlyIfNotStartsOnSameRowAs parent.startPosition)
(#set! indent.matchIndentOf parent.startPosition)
(#set! indent.offsetIndent 1))
; …and keep that indent level after the user starts typing…
(if_statement
condition: (_) @indent
consequence: [
(expression_statement)
(return_statement)
(continue_statement)
(break_statement)
(throw_statement)
(debugger_statement)
] @match
; When an opening curly brace is unpaired, it might get interpreted as part
; of an `expression_statement`, for some reason.
(#not-match? @match "^\\s*{")
(#set! indent.matchIndentOf parent.startPosition)
(#set! indent.offsetIndent 1))
; …but dedent after exactly one statement.
(if_statement
condition: (_) @indent
consequence: [
(expression_statement)
(return_statement)
(continue_statement)
(break_statement)
(throw_statement)
(debugger_statement)
] @dedent.next
; When an opening curly brace is unpaired, it might get interpreted as part
; of an `expression_statement`, for some reason.
(#not-match? @dedent.next "^\\s*{"))
(else_clause
[
(expression_statement)
(return_statement)
(continue_statement)
(break_statement)
(throw_statement)
(debugger_statement)
] @dedent.next
(#set! test.onlyIfNotStartsOnSameRowAs parent.startPosition))
; HANGING INDENT ON SPLIT LINES
; =============================
; TODO: We might want to make this configurable behavior with the
; `onlyIfConfig` scope test.
; Any of these at the end of a line indicate the next line should be indented…
(["||" "&&" "?"] @indent
(#set! test.onlyIfLastTextOnRow true))
; …and the line after that should be dedented.
(binary_expression
["||" "&&"]
right: (_) @dedent.next
(#set! test.onlyIfNotStartsOnSameRowAs parent.startPosition))
; let foo = this.longTernaryCondition() ?
; consequenceWhichIsItselfRatherLong :
; alternativeThatIsNotBrief;
;
; …followed by a dedent.
(ternary_expression
alternative: (_) @dedent.next
(#set! test.onlyIfNotStartsOnSameRowAs parent.startPosition))
; DEDENT-NEXT IN LIMITED SCENARIOS
; ================================
; Catches unusual hanging-indent scenarios when calling a method, such as:
;
; return this.veryLongMethodNameWithSeveralArgumentsThat(are, too,
; short, forEach, toHave, itsOwn, line);
;
; (arguments ")" @dedent.next
; (#set! test.onlyIfNotStartsOnSameRowAs parent.firstChild.startPosition)
; (#set! test.onlyIfNotFirstTextOnRow true))
; GENERAL
; =======
; Weed out `}`s that should not signal dedents.
(template_substitution "}" @_IGNORE_ (#set! test.final true))
[
"{"
"("
"["
] @indent
[
"}"
")"
"]"
] @dedent
["case" "default"] @indent
; JSX
; ===
(jsx_opening_element ">") @indent
(jsx_closing_element ">") @dedent

View File

@ -0,0 +1 @@
;

View File

@ -0,0 +1,31 @@
; Highlight this comment even if it's not “valid” JSDoc.
((ERROR) @comment.block.documentation.js.jsdoc
(#set! test.onlyIfRoot true))
((ERROR) @punctuation.definition.begin.comment.js.jsdoc
(#set! test.onlyIfRoot true)
(#set! adjust.startAndEndAroundFirstMatchOf "^/\\*\\*"))
((ERROR) @punctuation.definition.end.comment.js.jsdoc
(#set! test.onlyIfRoot true)
(#set! adjust.startAndEndAroundFirstMatchOf "(?:\\*)?\\*/$"))
(document) @comment.block.documentation.js.jsdoc
((document) @punctuation.definition.begin.comment.js.jsdoc
(#set! adjust.startAndEndAroundFirstMatchOf "^/\\*\\*"))
((document) @punctuation.definition.end.comment.js.jsdoc
(#set! adjust.startAndEndAroundFirstMatchOf "(?:\\*)?\\*/$"))
(tag_name) @storage.type.class.jsdoc
((tag (type)) @entity.other.type.instance.jsdoc
; Join the type with its surrounding braces.
(#set! adjust.startAt firstChild.nextSibling.startPosition)
(#set! adjust.endAt firstChild.nextSibling.nextSibling.nextSibling.endPosition))
"{" @punctuation.definition.begin.bracket.curly.jsdoc
"}" @punctuation.definition.end.bracket.curly.jsdoc

View File

@ -0,0 +1 @@
;

Some files were not shown because too many files have changed in this diff Show More