mirror of
https://github.com/pulsar-edit/pulsar.git
synced 2024-08-16 14:40:23 +03:00
Merge remote-tracking branch 'origin/master' into clojure-grammar-enhancements
This commit is contained in:
commit
1027a51811
51
.cirrus.yml
51
.cirrus.yml
@ -1,6 +1,6 @@
|
||||
env:
|
||||
PYTHON_VERSION: 3.12
|
||||
GITHUB_TOKEN: ENCRYPTED[!b0ff4671044672be50914a3a10b49af642bd8e0e681a6f4e5855ec5230a5cf9afbc53d9e90239b8d2c79455f014f383f!]
|
||||
GITHUB_TOKEN: ENCRYPTED[!c394f11378a8bc92ff1b05662ee3e574fc662692e45f0a048aa8cab42fb072b039d83f68fd6953f470af51846063ce46!]
|
||||
# The above token, is a GitHub API Token, that allows us to download RipGrep without concern of API limits
|
||||
|
||||
# linux_task:
|
||||
@ -63,7 +63,7 @@ arm_linux_task:
|
||||
memory: 8G
|
||||
env:
|
||||
USE_SYSTEM_FPM: 'true'
|
||||
ROLLING_UPLOAD_TOKEN: ENCRYPTED[690950798401ec3715e9d20ac29a0859d3c58097038081ff6afeaf4721e661672d34eb952d8a6442bc7410821ab8545a]
|
||||
ROLLING_UPLOAD_TOKEN: ENCRYPTED[f935c396a9f4bca108ec2fdedb00dbc9be2f4c411f100d577acdab42db59ea134be059ce8535396db8222a2b1eb68c27]
|
||||
prepare_script:
|
||||
- apt-get update
|
||||
- export DEBIAN_FRONTEND="noninteractive"
|
||||
@ -85,6 +85,7 @@ arm_linux_task:
|
||||
libasound2-dev
|
||||
libnss3
|
||||
xvfb
|
||||
- gem install dotenv -v '~> 2.8'
|
||||
- gem install fpm
|
||||
- git submodule init
|
||||
- git submodule update
|
||||
@ -122,7 +123,7 @@ silicon_mac_task:
|
||||
only_if: $CIRRUS_CRON != "" || $CIRRUS_TAG != ""
|
||||
skip: $CIRRUS_CHANGE_IN_REPO == $CIRRUS_LAST_GREEN_CHANGE
|
||||
macos_instance:
|
||||
image: ghcr.io/cirruslabs/macos-monterey-xcode:14
|
||||
image: ghcr.io/cirruslabs/macos-ventura-xcode:latest
|
||||
memory: 8G
|
||||
env:
|
||||
CSC_LINK: ENCRYPTED[0078015a03bb6cfdbd80113ae5bbb6f448fd4bbbc40efd81bf2cb1554373046b475a4d7c77e3e3e82ac1ce2f7e3d2da5]
|
||||
@ -130,39 +131,45 @@ silicon_mac_task:
|
||||
APPLEID: ENCRYPTED[549ce052bd5666dba5245f4180bf93b74ed206fe5e6e7c8f67a8596d3767c1f682b84e347b326ac318c62a07c8844a57]
|
||||
APPLEID_PASSWORD: ENCRYPTED[774c3307fd3b62660ecf5beb8537a24498c76e8d90d7f28e5bc816742fd8954a34ffed13f9aa2d1faf66ce08b4496e6f]
|
||||
TEAM_ID: ENCRYPTED[11f3fedfbaf4aff1859bf6c105f0437ace23d84f5420a2c1cea884fbfa43b115b7834a463516d50cb276d4c4d9128b49]
|
||||
ROLLING_UPLOAD_TOKEN: ENCRYPTED[690950798401ec3715e9d20ac29a0859d3c58097038081ff6afeaf4721e661672d34eb952d8a6442bc7410821ab8545a]
|
||||
ROLLING_UPLOAD_TOKEN: ENCRYPTED[f935c396a9f4bca108ec2fdedb00dbc9be2f4c411f100d577acdab42db59ea134be059ce8535396db8222a2b1eb68c27]
|
||||
prepare_script:
|
||||
- brew update
|
||||
- brew install node@16 yarn git python@$PYTHON_VERSION
|
||||
- python3 -m pip install setuptools
|
||||
- brew uninstall node
|
||||
- brew install git python@$PYTHON_VERSION python-setuptools
|
||||
- git submodule init
|
||||
- git submodule update
|
||||
- ln -s /opt/homebrew/bin/python$PYTHON_VERSION /opt/homebrew/bin/python
|
||||
- export PATH="/opt/homebrew/bin:/opt/homebrew/opt/node@16/bin:$PATH"
|
||||
- export PATH="/opt/homebrew/bin:$PATH"
|
||||
- mkdir tj_n && cd tj_n
|
||||
- curl -L https://github.com/tj/n/archive/0ce85771fdff8f4b3e09ade700461b4f58a64444.tar.gz -O
|
||||
- tar xf 0ce85771fdff8f4b3e09ade700461b4f58a64444.tar.gz
|
||||
- sudo bash ./n-0ce85771fdff8f4b3e09ade700461b4f58a64444/bin/n 16
|
||||
- cd ..
|
||||
- sudo npm install -g yarn
|
||||
- sed -i -e "s/[0-9]*-dev/`date -u +%Y%m%d%H`/g" package.json
|
||||
install_script:
|
||||
- export PATH="/opt/homebrew/bin:/opt/homebrew/opt/node@16/bin:$PATH"
|
||||
- export PATH="/opt/homebrew/bin:$PATH"
|
||||
- yarn install --ignore-engines || yarn install --ignore-engines
|
||||
build_script:
|
||||
- export PATH="/opt/homebrew/bin:/opt/homebrew/opt/node@16/bin:$PATH"
|
||||
- export PATH="/opt/homebrew/bin:$PATH"
|
||||
- yarn build
|
||||
- yarn run build:apm
|
||||
build_binary_script:
|
||||
- export PATH="/opt/homebrew/bin:/opt/homebrew/opt/node@16/bin:$PATH"
|
||||
- export PATH="/opt/homebrew/bin:$PATH"
|
||||
- yarn dist || yarn dist
|
||||
rename_binary_script:
|
||||
- export PATH="/opt/homebrew/bin:/opt/homebrew/opt/node@16/bin:$PATH"
|
||||
- export PATH="/opt/homebrew/bin:$PATH"
|
||||
- node script/rename.js "Silicon.Mac"
|
||||
binary_artifacts:
|
||||
path: ./binaries/*
|
||||
test_script:
|
||||
- export PATH="/opt/homebrew/bin:/opt/homebrew/opt/node@16/bin:$PATH"
|
||||
- export PATH="/opt/homebrew/bin:$PATH"
|
||||
- rm -R node_modules/electron; yarn install --check-files
|
||||
- hdiutil mount binaries/*Pulsar*dmg
|
||||
- export BINARY_NAME=`ls /Volumes/Pulsar*/Pulsar.app/Contents/MacOS/Pulsar`
|
||||
- PLAYWRIGHT_JUNIT_OUTPUT_NAME=report.xml npx playwright test --reporter=junit,list
|
||||
rolling_upload_script:
|
||||
- export PATH="/opt/homebrew/bin:/opt/homebrew/opt/node@16/bin:$PATH"
|
||||
- export PATH="/opt/homebrew/bin:$PATH"
|
||||
- cd ./script/rolling-release-scripts
|
||||
- npm install
|
||||
- node ./rolling-release-binary-upload.js cirrus
|
||||
@ -177,7 +184,7 @@ silicon_mac_task:
|
||||
# intel_mac_task:
|
||||
# alias: mac
|
||||
# macos_instance:
|
||||
# image: ghcr.io/cirruslabs/macos-monterey-xcode:14
|
||||
# image: ghcr.io/cirruslabs/macos-ventura-xcode:latest
|
||||
# memory: 8G
|
||||
# env:
|
||||
# CSC_LINK: ENCRYPTED[0078015a03bb6cfdbd80113ae5bbb6f448fd4bbbc40efd81bf2cb1554373046b475a4d7c77e3e3e82ac1ce2f7e3d2da5]
|
||||
@ -190,30 +197,32 @@ silicon_mac_task:
|
||||
# - echo A | softwareupdate --install-rosetta
|
||||
# - arch -x86_64 xcode-select --install
|
||||
# - arch -x86_64 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"
|
||||
# - export PATH="/usr/local/opt/node@16/bin:/usr/local/bin:$PATH"
|
||||
# - export PATH="/usr/local/bin:$PATH"
|
||||
# - arch -x86_64 brew update
|
||||
# - arch -x86_64 brew install node@16 yarn git python@$PYTHON_VERSION
|
||||
# - arch -x86_64 brew uninstall node
|
||||
# - arch -x86_64 brew install node@16 git python@$PYTHON_VERSION python-setuptools
|
||||
# - ln -s /usr/local/bin/python$PYTHON_VERSION /usr/local/bin/python
|
||||
# - npm install -g yarn
|
||||
# - git submodule init
|
||||
# - git submodule update
|
||||
# - sed -i -e "s/[0-9]*-dev/`date -u +%Y%m%d%H`/g" package.json
|
||||
# install_script:
|
||||
# - export PATH="/usr/local/opt/node@16/bin:/usr/local/bin:$PATH"
|
||||
# - export PATH="/usr/local/bin:$PATH"
|
||||
# - arch -x86_64 npx yarn install --ignore-engines || arch -x86_64 npx yarn install --ignore-engines
|
||||
# build_script:
|
||||
# - export PATH="/usr/local/opt/node@16/bin:/usr/local/bin:$PATH"
|
||||
# - export PATH="/usr/local/bin:$PATH"
|
||||
# - arch -x86_64 npx yarn build
|
||||
# - arch -x86_64 yarn run build:apm
|
||||
# build_binary_script:
|
||||
# - export PATH="/usr/local/opt/node@16/bin:/usr/local/bin:$PATH"
|
||||
# - export PATH="/usr/local/bin:$PATH"
|
||||
# - arch -x86_64 npx yarn dist || arch -x86_64 npx yarn dist
|
||||
# rename_binary_script:
|
||||
# - export PATH="/usr/local/opt/node@16/bin:/usr/local/bin:$PATH"
|
||||
# - export PATH="/usr/local/bin:$PATH"
|
||||
# - node script/rename.js "Intel.Mac"
|
||||
# binary_artifacts:
|
||||
# path: ./binaries/*
|
||||
# test_script:
|
||||
# - export PATH="/usr/local/opt/node@16/bin:/usr/local/bin:$PATH"
|
||||
# - export PATH="/usr/local/bin:$PATH"
|
||||
# - rm -R node_modules/electron; yarn install --check-files
|
||||
# - hdiutil mount binaries/*Pulsar*dmg
|
||||
# - export BINARY_NAME=`ls /Volumes/Pulsar*/Pulsar.app/Contents/MacOS/Pulsar`
|
||||
|
2
.eslintignore
Normal file
2
.eslintignore
Normal file
@ -0,0 +1,2 @@
|
||||
*.ts
|
||||
vendor
|
14
.eslintrc.js
14
.eslintrc.js
@ -19,6 +19,20 @@ module.exports = {
|
||||
asyncArrow: "always",
|
||||
named: "never"
|
||||
}],
|
||||
"no-constant-condition": "off",
|
||||
"no-unused-vars": [
|
||||
"warn",
|
||||
{
|
||||
varsIgnorePattern: '^_',
|
||||
argsIgnorePattern: '^_'
|
||||
}
|
||||
],
|
||||
"node/no-missing-require": [
|
||||
"error",
|
||||
{
|
||||
allowModules: ["atom"]
|
||||
}
|
||||
],
|
||||
"node/no-unpublished-require": [
|
||||
"error",
|
||||
{
|
||||
|
130
.github/workflows/build.yml
vendored
130
.github/workflows/build.yml
vendored
@ -24,11 +24,21 @@ jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ ubuntu-20.04, windows-latest, macos-latest ]
|
||||
os: [ ubuntu-latest, macos-latest, windows-latest ]
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
image: "debian:10"
|
||||
fail-fast: false
|
||||
runs-on: ${{ matrix.os }}
|
||||
container: ${{ matrix.image }}
|
||||
outputs:
|
||||
timestamp: ${{ steps.linux-binary-version.outputs.timestamp }}
|
||||
|
||||
steps:
|
||||
- name: Install build dependencies - Linux
|
||||
if: ${{ runner.os == 'Linux' }}
|
||||
run: apt-get update && apt-get install -y git python3 python3-pip make gcc g++ libx11-dev libxkbfile-dev pkg-config libsecret-1-dev rpm xvfb ffmpeg zstd
|
||||
|
||||
- name: Checkout the latest code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
@ -38,6 +48,10 @@ jobs:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
|
||||
- name: Setup Python
|
||||
# actions/setup-python's copy of Python is compiled for too new glibc,
|
||||
# which won't work in a Debian 10 Docker image.
|
||||
# Get Python from apt repos instead on Linux.
|
||||
if: ${{ runner.os != 'Linux' }}
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
@ -48,11 +62,30 @@ jobs:
|
||||
# out of the box. 'setuptools' package provides 'distutils'.
|
||||
run: python3 -m pip install setuptools
|
||||
|
||||
- name: Install Yarn - Linux
|
||||
if: ${{ runner.os == 'Linux' }}
|
||||
run: npm install -g yarn
|
||||
|
||||
- name: Setup Git Submodule
|
||||
if: ${{ runner.os != 'Linux' }}
|
||||
run: |
|
||||
git submodule init
|
||||
git submodule update
|
||||
|
||||
- name: Setup Git Submodule - Linux
|
||||
if: ${{ runner.os == 'Linux' }}
|
||||
run: |
|
||||
git config --global --add safe.directory /__w/pulsar/pulsar
|
||||
git submodule init
|
||||
git submodule update
|
||||
|
||||
- name: Set Timestamp for Binary Version - Linux
|
||||
id: linux-binary-version
|
||||
if: ${{ runner.os == 'Linux' }}
|
||||
# This output is currently only set for the sake of the Rolling binary upload script.
|
||||
# See the "test-and-upload-Linux" job below.
|
||||
run: echo "timestamp=`date -u +%Y%m%d%H`" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Check Pulsar Version
|
||||
if: ${{ runner.os != 'Windows' }}
|
||||
run: sed -i -e "s/[0-9]*-dev/`date -u +%Y%m%d%H`/g" package.json
|
||||
@ -86,6 +119,15 @@ jobs:
|
||||
yarn build
|
||||
yarn run build:apm
|
||||
|
||||
- name: Cache Pulsar dependencies - Linux
|
||||
if: ${{ runner.os == 'Linux' }}
|
||||
uses: actions/cache/save@v3
|
||||
with:
|
||||
path: |
|
||||
node_modules
|
||||
packages
|
||||
key: Linux-dependencies-${{ github.sha }}-${{ github.workflow }}
|
||||
|
||||
# macOS Signing Stuff
|
||||
- name: Build Pulsar Binaries (macOS) (Signed)
|
||||
if: ${{ runner.os == 'macOS' && github.event_name == 'push' }}
|
||||
@ -135,21 +177,19 @@ jobs:
|
||||
if: ${{ runner.os == 'Windows' }}
|
||||
run: node ./script/rename.js "Windows"
|
||||
|
||||
- name: Cache Pulsar Binaries - Linux
|
||||
if: ${{ runner.os == 'Linux' }}
|
||||
uses: actions/cache/save@v3
|
||||
with:
|
||||
path: ./binaries
|
||||
key: pulsar-Linux-Binaries-${{ github.sha }}-${{ github.workflow }}
|
||||
|
||||
- name: Upload Binary Artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ matrix.os }} Binaries
|
||||
path: ./binaries/*
|
||||
|
||||
- name: Test Binary - Linux
|
||||
if: ${{ (runner.os == 'Linux') && env.RUN_LINUX_VT }}
|
||||
run: |
|
||||
rm -R node_modules/electron; yarn install --check-files
|
||||
./binaries/*AppImage --appimage-extract
|
||||
export BINARY_NAME='squashfs-root/pulsar'
|
||||
mkdir -p ./tests/videos
|
||||
Xvfb -screen 0 1024x768x24+32 :99 & nohup ffmpeg -video_size 1024x768 -f x11grab -i :99.0 ./tests/videos/out.mpg & DISPLAY=:99 PLAYWRIGHT_JUNIT_OUTPUT_NAME=report.xml npx playwright test --reporter=junit,list
|
||||
|
||||
- name: Test Binary - Windows
|
||||
if: runner.os == 'Windows' && env.RUN_WINDOWS_VT == true
|
||||
# TODO: Convert script to PowerShell
|
||||
@ -168,7 +208,7 @@ jobs:
|
||||
PLAYWRIGHT_JUNIT_OUTPUT_NAME=report.xml arch -x86_64 npx playwright test --reporter=junit,list
|
||||
|
||||
- name: Add binaries to Rolling Release Repo
|
||||
if: ${{ github.event_name == 'push' }}
|
||||
if: ${{ github.event_name == 'push' && runner.os != 'Linux' }}
|
||||
# We only want to upload rolling binaries if they are a commit to master
|
||||
# Otherwise we want to not upload if it's a PR or manually triggered build
|
||||
run: |
|
||||
@ -177,6 +217,74 @@ jobs:
|
||||
node ./rolling-release-binary-upload.js
|
||||
|
||||
- name: Upload Video Artifacts
|
||||
# Run whether this job passed or failed, unless explicitly cancelled.
|
||||
if: ${{ !cancelled() && runner.os != 'Linux' }}
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ matrix.os }} Videos
|
||||
path: ./tests/videos/**
|
||||
|
||||
|
||||
test-and-upload-Linux:
|
||||
# I couldn't figure out how to get these visual tests to actually run in the
|
||||
# Debian Docker environment. If anyone can make it work, feel free to
|
||||
# re-combine visual testing on Linux back into the main build job above!
|
||||
# - DeeDeeG
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
|
||||
steps:
|
||||
- name: Checkout the latest code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
|
||||
- name: Restore Cached Pulsar Binaries - Linux
|
||||
if: ${{ runner.os == 'Linux' }}
|
||||
uses: actions/cache/restore@v3
|
||||
with:
|
||||
path: ./binaries
|
||||
key: pulsar-Linux-Binaries-${{ github.sha }}-${{ github.workflow }}
|
||||
|
||||
- name: Restore Cached Pulsar dependencies - Linux
|
||||
if: ${{ runner.os == 'Linux' }}
|
||||
uses: actions/cache/restore@v3
|
||||
with:
|
||||
path: |
|
||||
node_modules
|
||||
packages
|
||||
key: Linux-dependencies-${{ github.sha }}-${{ github.workflow }}
|
||||
|
||||
- name: Test Binary - Linux
|
||||
if: ${{ (runner.os == 'Linux') && env.RUN_LINUX_VT }}
|
||||
run: |
|
||||
rm -R node_modules/electron; yarn install --check-files
|
||||
./binaries/*AppImage --appimage-extract
|
||||
export BINARY_NAME='squashfs-root/pulsar'
|
||||
mkdir -p ./tests/videos
|
||||
Xvfb -screen 0 1024x768x24+32 :99 & nohup ffmpeg -video_size 1024x768 -f x11grab -i :99.0 ./tests/videos/out.mpg & DISPLAY=:99 PLAYWRIGHT_JUNIT_OUTPUT_NAME=report.xml npx playwright test --reporter=junit,list
|
||||
|
||||
- name: Check Pulsar Version
|
||||
if: ${{ runner.os != 'Windows' }}
|
||||
run: sed -i -e "s/[0-9]*-dev/${TIMESTAMP}/g" package.json
|
||||
env:
|
||||
TIMESTAMP: ${{needs.build.outputs.timestamp}}
|
||||
|
||||
- name: Add binaries to Rolling Release Repo - Linux
|
||||
if: ${{ github.event_name == 'push' && runner.os == 'Linux' }}
|
||||
# We only want to upload rolling binaries if they are a commit to master
|
||||
# Otherwise we want to not upload if it's a PR or manually triggered build
|
||||
run: |
|
||||
cd ./script/rolling-release-scripts
|
||||
npm install
|
||||
node ./rolling-release-binary-upload.js
|
||||
|
||||
- name: Upload Video Artifacts - Linux
|
||||
# Run whether this job passed or failed, unless explicitly cancelled.
|
||||
if: ${{ !cancelled() && runner.os == 'Linux' }}
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ matrix.os }} Videos
|
||||
|
4
.github/workflows/package-tests-linux.yml
vendored
4
.github/workflows/package-tests-linux.yml
vendored
@ -116,7 +116,9 @@ jobs:
|
||||
- package: "spell-check"
|
||||
- package: "status-bar"
|
||||
- package: "styleguide"
|
||||
# - package: "symbols-view"
|
||||
- package: "symbol-provider-ctags"
|
||||
- package: "symbol-provider-tree-sitter"
|
||||
- package: "symbols-view"
|
||||
- package: "tabs"
|
||||
- package: "timecop"
|
||||
- package: "tree-view"
|
||||
|
177
CHANGELOG.md
177
CHANGELOG.md
@ -18,6 +18,183 @@
|
||||
better support for "def" elements (example - don't syntax `default` or
|
||||
`definition` as a `def`, but highlights `p/defresolver`)
|
||||
|
||||
## 1.115.0
|
||||
|
||||
- Fixed some folds in Ruby like `unless`, some blocks, multiline comments, function calls, and different array syntaxes for strings and keywords.
|
||||
- Improved the accuracy of indentation hinting in modern Tree-sitter grammars, especially in multi-cursor scenarios.
|
||||
- Improved the ability of the user to opt into a specific kind of grammar for a specific language.
|
||||
- Changed the behavior of the `grammar-selector` package so that it will show the user's preferred grammar for a specific language.
|
||||
- Updated to version `0.20.9` of `web-tree-sitter`.
|
||||
- Improved syntax highlighting, indentation, and code folding in various languages, including TypeScript, shell scripts, Ruby, and C/C++.
|
||||
|
||||
### Pulsar
|
||||
- Fixed: Fixed folds for Ruby [@mauricioszabo](https://github.com/pulsar-edit/pulsar/pull/956)
|
||||
- Fixed: Tree-sitter fixes: 1.115 edition [@savetheclocktower](https://github.com/pulsar-edit/pulsar/pull/941)
|
||||
- Updated: cirrus: Update Rolling upload token again [@DeeDeeG](https://github.com/pulsar-edit/pulsar/pull/960)
|
||||
- Fixed: cirrus: Various fixes for macOS Cirrus CI [@DeeDeeG](https://github.com/pulsar-edit/pulsar/pull/961)
|
||||
- Fixed: Fix(fuzzy-finder) fs.lstatSync throws Exception if not a file or dir [@schadomi7](https://github.com/pulsar-edit/pulsar/pull/944)
|
||||
- Updated: CI: Update Rolling upload token for Cirrus CI [@DeeDeeG](https://github.com/pulsar-edit/pulsar/pull/936)
|
||||
- Updated: Cirrus: Install older dotenv gem version ~> 2.8 (< 3) [@DeeDeeG](https://github.com/pulsar-edit/pulsar/pull/937)
|
||||
|
||||
## 1.114.0
|
||||
|
||||
- Prevented an exception raised in the command palette in certain unusual filtering scenarios.
|
||||
- Refrain from rendering anchor icons when showing a package's README file in `settings-view`.
|
||||
- Build Linux binaries on Debian 10, for older glibc and compatibility with older Linux distros.
|
||||
- Fixed a rendering error in `atom.ui.markdown.render` when `disableMode` was set to `"strict"` and the input contained HTML line breaks.
|
||||
- Added support for the semanticolor package in modern tree-sitter grammars.
|
||||
- Added new `--force` flag to `ppm link` command that will uninstall any conflicting package already installed.
|
||||
- Added language entity colors to `syntax-variables.less`.
|
||||
- Numerous Tree-Sitter Grammar syntax highlighting fixes.
|
||||
- Bumped dugite to make the github package compatible with ARM Linux.
|
||||
|
||||
### Pulsar
|
||||
- Fixed: fix(tree-sitter): pass node text to grammar [@claytonrcarter](https://github.com/pulsar-edit/pulsar/pull/860)
|
||||
- Fixed: Fix issue with Markdown rendering after line break in strict mode [@savetheclocktower](https://github.com/pulsar-edit/pulsar/pull/889)
|
||||
- Updated: Update README badges [@Daeraxa](https://github.com/pulsar-edit/pulsar/pull/891)
|
||||
- Updated: Update copyright year to 2024 [@Daeraxa](https://github.com/pulsar-edit/pulsar/pull/870)
|
||||
- Added: CI: build Linux x86-64 binaries on older Linux [@DeeDeeG](https://github.com/pulsar-edit/pulsar/pull/858)
|
||||
- Fixed: Tree-sitter rolling fixes (January edition) [@savetheclocktower](https://github.com/pulsar-edit/pulsar/pull/859)
|
||||
- Fixed: Fix failing spec [@savetheclocktower](https://github.com/pulsar-edit/pulsar/pull/902)
|
||||
- Fixed: [settings-view] Don't display heading anchor icons within a README [@savetheclocktower](https://github.com/pulsar-edit/pulsar/pull/905)
|
||||
- Updated: ppm: Update ppm to commit 241d794f326b63b5abdb9769 [@DeeDeeG](https://github.com/pulsar-edit/pulsar/pull/908)
|
||||
- Fixed: script: Update version check in Rolling release binary upload script to exclude '-dev' versions [@DeeDeeG](https://github.com/pulsar-edit/pulsar/pull/903)
|
||||
- Fixed: CI: Fix tag Linux binaries are uploaded to for Rolling [@DeeDeeG](https://github.com/pulsar-edit/pulsar/pull/901)
|
||||
- Fixed: [command-palette] Guard against failure to highlight a match [@savetheclocktower](https://github.com/pulsar-edit/pulsar/pull/913)
|
||||
- Fixed: `symbols-view` rolling fixes [@savetheclocktower](https://github.com/pulsar-edit/pulsar/pull/861)
|
||||
- Fixed: Tree-sitter rolling fixes (February) [@savetheclocktower](https://github.com/pulsar-edit/pulsar/pull/906)
|
||||
- Updated: [meta] Update Cirrus `GITHUB_TOKEN` [@confused-Techie](https://github.com/pulsar-edit/pulsar/pull/924)
|
||||
- Updated: deps: Update github to v0.36.20-pretranspiled to bump dugite [@DeeDeeG](https://github.com/pulsar-edit/pulsar/pull/925)
|
||||
- Fixed: [symbols-view] Remap go-to-declaration commands on Windows/Linux [@savetheclocktower](https://github.com/pulsar-edit/pulsar/pull/926)
|
||||
|
||||
### PPM
|
||||
- Fixed: Fix test failure due to missing atom command [@toddy15](https://github.com/pulsar-edit/ppm/pull/124)
|
||||
- Updated: Update syntax-variables.less to include language entity colors [@savetheclocktower](https://github.com/pulsar-edit/ppm/pull/123)
|
||||
- Added: feat(link): add --force flag [@claytonrcarter](https://github.com/pulsar-edit/ppm/pull/122)
|
||||
|
||||
### github
|
||||
- Updated: Bump dugite to 2.5.2 [@DeeDeeG](https://github.com/pulsar-edit/github/pull/39)
|
||||
|
||||
## 1.113.0
|
||||
|
||||
- Enabled Modern Tree-sitter Grammars by default
|
||||
- Added a modern Tree-sitter grammar for PHP.
|
||||
- Fix a measurement issue that was causing visual glitches in the `github` package's diff views.
|
||||
- Enabled the core `symbols-view` package to accept symbols from a number of sources, including Tree-sitter grammars and IDE packages.
|
||||
- Switch default to false for converting ASCII emoticons to emoji when rendering Markdown.
|
||||
- Fix certain find-and-replace scenarios when the "Preserve Case During Replace" setting is enabled.
|
||||
- Fix an issue in `symbols-view` when returning from visiting a symbol declaration.
|
||||
|
||||
### Pulsar
|
||||
- Fixed: Tree-sitter fixes for December (including a PHP grammar!) [@savetheclocktower](https://github.com/pulsar-edit/pulsar/pull/852)
|
||||
- Added: Make `useExperimentalModernTreeSitter` the default... [@savetheclocktower](https://github.com/pulsar-edit/pulsar/pull/855)
|
||||
- Fixed: Ensure editor is visible before measuring block decorations [@savetheclocktower](https://github.com/pulsar-edit/pulsar/pull/854)
|
||||
- Added: Overhaul `symbols-view` [@savetheclocktower](https://github.com/pulsar-edit/pulsar/pull/829)
|
||||
- Added: Default to no emoji when rendering Markdown [@savetheclocktower](https://github.com/pulsar-edit/pulsar/pull/850)
|
||||
|
||||
### find-and-replace
|
||||
- Fixed: [find-and-replace] Fix `capitalize` utility [@savetheclocktower](https://github.com/pulsar-edit/pulsar/pull/849)
|
||||
|
||||
### symbols-view
|
||||
- Fixed: [symbols-view] Fix issue with returning from a declaration [@savetheclocktower](https://github.com/pulsar-edit/pulsar/pull/864)
|
||||
|
||||
## 1.112.1
|
||||
|
||||
- Fixed a bug in PPM that prevented package publishing.
|
||||
|
||||
### Pulsar
|
||||
- Bumped: ppm: Update ppm to commit 0bc207133b26de82aa28500e [@DeeDeeG](https://github.com/pulsar-edit/pulsar/pull/845)
|
||||
- Bumped: ppm: Update ppm to commit 7dfd9ca8cf877391fc6ef1d5 [@DeeDeeG](https://github.com/pulsar-edit/pulsar/pull/842)
|
||||
|
||||
### PPM
|
||||
- Fixed: Fix placement of `setTimeout` [@savetheclocktower](https://github.com/pulsar-edit/ppm/pull/118)
|
||||
- Fixed: Fix bugs found in `publish` after 1.112 release [@savetheclocktower](https://github.com/pulsar-edit/ppm/pull/116)
|
||||
|
||||
## 1.112.0
|
||||
|
||||
- Fixed github package not giving feedback when a token with the wrong scopes was entered, tweak scope-checking logic to match expectations, and log incorrect scopes.
|
||||
- Various cleanups, maintenance and upkeep of the PPM repo.
|
||||
- Added options for a user to control when to automatically show or hide the wrap-guide; "Always", "When soft wrap is enabled", and "When soft wrap at preferred line length is enabled".
|
||||
- Updated network handling in PPM to something newer and more secure.
|
||||
- Updated most of PPM's code to use async/await and promises internally.
|
||||
- Created `atom.ui.fuzzyMatcher` API, moving the Pulsar `fuzzy-finder` module into the core of the editor for community packages to utilize.
|
||||
- Fixed an issue that prevented Pulsar from inheriting the directory from which the `pulsar` binary was run.
|
||||
|
||||
### Pulsar
|
||||
- Added: [tree-sitter] Share config caches between `ScopeResolver`s [@savetheclocktower](https://github.com/pulsar-edit/pulsar/pull/836)
|
||||
- Bumped: deps: Update github to v0.36.19-pretranspiled (fix silent failure when inputting a token with incorrect scopes) [@DeeDeeG](https://github.com/pulsar-edit/pulsar/pull/838)
|
||||
- Bumped: ppm: Update ppm to commit 957acbd90cfc9f361c183b3c [@DeeDeeG](https://github.com/pulsar-edit/pulsar/pull/837)
|
||||
- Added: Return to original logic for `ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT` [@savetheclocktower](https://github.com/pulsar-edit/pulsar/pull/831)
|
||||
- Added: Moving fuzzy-native to core [@mauricioszabo](https://github.com/pulsar-edit/pulsar/pull/774)
|
||||
- Fixed: Tree-sitter rolling fixes for November [@savetheclocktower](https://github.com/pulsar-edit/pulsar/pull/819)
|
||||
- Fixed: CI: Update Rolling upload token for Cirrus [@DeeDeeG](https://github.com/pulsar-edit/pulsar/pull/812)
|
||||
- Bumped: ppm: Update to commit 13fb2845e00d7e04c2461f93 [@DeeDeeG](https://github.com/pulsar-edit/pulsar/pull/809)
|
||||
- Added: Ability to indicate when to automatically show or hide the wrap-guide [@Trigan2025](https://github.com/pulsar-edit/pulsar/pull/780)
|
||||
|
||||
### PPM
|
||||
- Bumped: fix(deps): update dependency semver to v7.5.2 [security] [@renovate](https://github.com/pulsar-edit/ppm/pull/114)
|
||||
- Added: Configure Renovate [@confused-Techie](https://github.com/pulsar-edit/ppm/pull/110)
|
||||
- Added: Migrate from `rimraf` to NodeJS `fs` [@confused-Techie](https://github.com/pulsar-edit/ppm/pull/108)
|
||||
- Added: Implement Codacy Recommendations [@confused-Techie](https://github.com/pulsar-edit/ppm/pull/113)
|
||||
- Removed: Prune outdated Deps [@confused-Techie](https://github.com/pulsar-edit/ppm/pull/109)
|
||||
- Removed: Remove unused Variables [@confused-Techie](https://github.com/pulsar-edit/ppm/pull/112)
|
||||
- Added: Add Codacy and Friends Configuration [@confused-Techie](https://github.com/pulsar-edit/ppm/pull/111)
|
||||
- Removed: src: Delete unused code in uninstall.js [@DeeDeeG](https://github.com/pulsar-edit/ppm/pull/104)
|
||||
- Fixed: src: Fix usage/help text (and error message) for -b/-t flags for ppm install [@DeeDeeG](https://github.com/pulsar-edit/ppm/pull/105)
|
||||
- Added: Repository Cleanup [@confused-Techie](https://github.com/pulsar-edit/ppm/pull/107)
|
||||
- Fixed: Fix Newer NodeJS CI [@confused-Techie](https://github.com/pulsar-edit/ppm/pull/106)
|
||||
- Fixed: src: Stop pinging backend during package uninstalls [@DeeDeeG](https://github.com/pulsar-edit/ppm/pull/103)
|
||||
- Added: Asyncify without topmost interface [@2colours](https://github.com/pulsar-edit/ppm/pull/95)
|
||||
- Fixed: CI: Work around a weird bug in Yarn v1.x [@DeeDeeG](https://github.com/pulsar-edit/ppm/pull/101)
|
||||
- Fixed: src: Rebrand two lines of "ppm --version" output [@DeeDeeG](https://github.com/pulsar-edit/ppm/pull/100)
|
||||
- Bumped: deps: Bump nan for compatibility with newer NodeJS [@DeeDeeG](https://github.com/pulsar-edit/ppm/pull/97)
|
||||
- Fixed: Fix Error Handling [@confused-Techie](https://github.com/pulsar-edit/ppm/pull/99)
|
||||
- Removed: Remove `request` Migrate to `superagent` && Fix CI [@confused-Techie](https://github.com/pulsar-edit/ppm/pull/87)
|
||||
|
||||
### github
|
||||
- Added: lib: Allow parent scopes when checking if each required scope is set [@DeeDeeG](https://github.com/pulsar-edit/github/pull/38)
|
||||
|
||||
## 1.111.0
|
||||
|
||||
- Added a new "UI" API to `atom`, accessible via `atom.ui`. This exposes a `markdown` object, allowing community packages to offload Markdown handling to the core editor.
|
||||
- Fine-tuned/deduped dependencies to remove ~35.5 MB from Pulsar's installed size.
|
||||
- Fixed an issue that sometimes caused text to shift or disappear after an editor pane regains focus.
|
||||
- Fixed scoping/highlighting of single-quoted (`'...'`) and C-style (`$'...'`) strings in shell scripts.
|
||||
- Fixed an issue with the "Dismiss this Version" button (in the `pulsar-updater` package).
|
||||
- Fixed an issue with how Linux Pulsar binaries were built, to ensure compatibility with non-bleeding edge glibc versions. (Compatibility with even older glibc versions is still being looked into, for the folks on older or RHEL-compatible distros.)
|
||||
|
||||
### Pulsar
|
||||
- Fixed: meta: Update CirrusCI GitHub Token [@confused-Techie](https://github.com/pulsar-edit/pulsar/pull/804)
|
||||
- Bumped: deps: Update `github`, for `dugite` deduping purposes [@DeeDeeG](https://github.com/pulsar-edit/pulsar/pull/799)
|
||||
- Fixed: Tree-sitter running fixes (October) [@savetheclocktower](https://github.com/pulsar-edit/pulsar/pull/789)
|
||||
- Fixed: Prevent "half screen" bug by resetting scroll position when editor regains focus [@savetheclocktower](https://github.com/pulsar-edit/pulsar/pull/798)
|
||||
- Added: [core] New `UI` API [@confused-Techie](https://github.com/pulsar-edit/pulsar/pull/763)
|
||||
- Fixed: CI: Build binaries for tag pushes (GitHub Actions) [@DeeDeeG](https://github.com/pulsar-edit/pulsar/pull/782)
|
||||
- Added: [DOCS] Add non-macOS keybindings for fuzzy-finder readme [@Daeraxa](https://github.com/pulsar-edit/pulsar/pull/796)
|
||||
- Removed: Remove Teletype from Welcome guide [@Daeraxa](https://github.com/pulsar-edit/pulsar/pull/793)
|
||||
- Fixed: CI: Python 3.12-related fixes on Cirrus CI [@DeeDeeG](https://github.com/pulsar-edit/pulsar/pull/794)
|
||||
- Fixed: CI: Work around missing 'distutils' for Python 3.12+ (GHA round two) [@DeeDeeG](https://github.com/pulsar-edit/pulsar/pull/795)
|
||||
- Added: [meta] Create Workflow to validate WASM Grammar Changes [@confused-Techie](https://github.com/pulsar-edit/pulsar/pull/740)
|
||||
- Fixed: 🐛 ✅ Fix caching for "Dismiss this Version" in pulsar-updater [@kiskoza](https://github.com/pulsar-edit/pulsar/pull/785)
|
||||
- Fixed: [tree-sitter] Fix proliferation of extra injection layers [@savetheclocktower](https://github.com/pulsar-edit/pulsar/pull/783)
|
||||
- Added: CI: Increase timeout length for macOS binary builds [@DeeDeeG](https://github.com/pulsar-edit/pulsar/pull/781)
|
||||
- Fixed: Fix the matching of `$'...'` strings. [@danfuzz](https://github.com/pulsar-edit/pulsar/pull/776)
|
||||
- Fixed: [meta] Install Python package `setuptools` && Use Python `3.12.x` [@confused-Techie](https://github.com/pulsar-edit/pulsar/pull/779)
|
||||
- Fixed: Update `web-tree-sitter` to include `isalnum` builtin [@savetheclocktower](https://github.com/pulsar-edit/pulsar/pull/770)
|
||||
- Fixed: [meta] Build x86 Linux binaries on Ubuntu 20.04, for older (more compatible) glibc [@confused-Techie](https://github.com/pulsar-edit/pulsar/pull/760)
|
||||
- Bumped: [core] Bump `git-utils`: `5.7.1` => `^5.7.3` [@confused-Techie](https://github.com/pulsar-edit/pulsar/pull/772)
|
||||
- Removed: [core] Cleanup Unused Deps [@confused-Techie](https://github.com/pulsar-edit/pulsar/pull/771)
|
||||
|
||||
### github
|
||||
- Bumped: deps: Update `whats-my-line` to bump `dugite` to 2.1.0 [@DeeDeeG](https://github.com/pulsar-edit/github/pull/36)
|
||||
|
||||
### whats-my-line
|
||||
- Bumped: Pin `dugite` to `2.1.0` [@confused-Techie](https://github.com/pulsar-edit/whats-my-line/pull/7)
|
||||
- Bumped: Bump dugite && Bump `package.json` version [@confused-Techie](https://github.com/pulsar-edit/whats-my-line/pull/2)
|
||||
- Added: Add dugite tests [@confused-Techie](https://github.com/pulsar-edit/whats-my-line/pull/4)
|
||||
- Removed: Remove TypeScript [@confused-Techie](https://github.com/pulsar-edit/whats-my-line/pull/3)
|
||||
- Added: Setup Tests and Modernize [@confused-Techie](https://github.com/pulsar-edit/whats-my-line/pull/1)
|
||||
|
||||
## 1.110.0
|
||||
|
||||
- Made the modification of `editor.preferredLineLength` configurable within `wrap-guide` when changing `wrap-guide.columns`
|
||||
|
@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022-2023 Pulsar-Edit
|
||||
Copyright (c) 2022-2024 Pulsar-Edit
|
||||
Original work copyright (c) 2011-2022 GitHub Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
|
14
README.md
14
README.md
@ -5,7 +5,9 @@
|
||||
|
||||
[![Badge Discussions]][Discussions]
|
||||
[![Badge Discord]][Discord]
|
||||
[![Badge Reddit]][Reddit]
|
||||
[![Badge Reddit]][Reddit]
|
||||
[![Badge Mastodon]][Mastodon]
|
||||
[![Badge Lemmy]][Lemmy]
|
||||
|
||||
[![Badge Status]][Status]
|
||||
[![Badge License]][License]
|
||||
@ -51,6 +53,8 @@
|
||||
[Status]: https://cirrus-ci.com/github/pulsar-edit/pulsar/master
|
||||
[Codacy]: https://app.codacy.com/gh/pulsar-edit/pulsar
|
||||
[Reddit]: https://www.reddit.com/r/pulsaredit/
|
||||
[Mastodon]: https://fosstodon.org/@pulsaredit/
|
||||
[Lemmy]: https://lemmy.ml/c/pulsaredit/
|
||||
|
||||
[#]: #
|
||||
|
||||
@ -72,14 +76,18 @@
|
||||
<!---------------------------{ Badges }--------------------------->
|
||||
|
||||
[Badge OpenCollective]: https://opencollective.com/pulsar-edit/tiers/badge.svg
|
||||
[Badge Discussions]: https://img.shields.io/github/discussions/pulsar-edit/.github?style=for-the-badge&labelColor=78af9f&color=5a8377
|
||||
[Badge Discussions]: https://img.shields.io/github/discussions/pulsar-edit/.github?style=for-the-badge&logo=GitHub&labelColor=50555b&color=35393d
|
||||
[Badge Upstream]: https://img.shields.io/badge/Upstream_Status-Sunset-966227.svg?style=for-the-badge&labelColor=c38033
|
||||
[Badge Discord]: https://img.shields.io/badge/Discord-4b7494.svg?style=for-the-badge&labelColor=6399c4&logoColor=white&logo=Discord
|
||||
[Badge License]: https://img.shields.io/badge/License-MIT-ac7f31.svg?style=for-the-badge&labelColor=e5ab42
|
||||
[Badge Crowdin]: https://badges.crowdin.net/pulsar-edit/localized.svg
|
||||
[Badge Codacy]: https://app.codacy.com/project/badge/Grade/24873ecb93dc4c1d865202ce5b24efc1
|
||||
[Badge Reddit]: https://img.shields.io/reddit/subreddit-subscribers/pulsaredit?style=for-the-badge&label=Reddit&logoColor=white&logo=Reddit&labelColor=e05d44&color=b14835
|
||||
<!-- Reddit subscribers badge broken. See: https://github.com/badges/shields/issues/9817 & https://github.com/badges/shields/issues/9256 -->
|
||||
<!-- [Badge Reddit]: https://img.shields.io/reddit/subreddit-subscribers/pulsaredit?style=for-the-badge&label=Reddit&logoColor=white&logo=Reddit&labelColor=e05d44&color=b14835 -->
|
||||
[Badge Reddit]: https://img.shields.io/badge/%2Fr%2Fpulsaredit-e05d44?style=for-the-badge&logo=Reddit&logoColor=white&labelColor=e05d44&color=b14835
|
||||
[Badge Status]: https://img.shields.io/cirrus/github/pulsar-edit/pulsar?style=for-the-badge&labelColor=c77b7f&label=Build%20Status&color=8d575a
|
||||
[Badge Mastodon]: https://img.shields.io/mastodon/follow/109416671848539153?domain=https%3A%2F%2Ffosstodon.org%2F&style=for-the-badge&logo=Mastodon&logoColor=white&label=Mastodon&labelColor=a0a0d8&color=7c7cc1
|
||||
[Badge Lemmy]: https://img.shields.io/lemmy/pulsaredit%40lemmy.ml?style=for-the-badge&logo=Lemmy&logoColor=white&label=Lemmy&labelColor=64ad82&color=1d9b52
|
||||
|
||||
|
||||
<!--------------------------{ Buttons }--------------------------->
|
||||
|
@ -7,6 +7,23 @@
|
||||
</dd>
|
||||
<dt><a href="#Clipboard">Clipboard</a></dt>
|
||||
<dd></dd>
|
||||
<dt><a href="#Container">Container</a></dt>
|
||||
<dd><p>A container capture. When another capture's node is contained by the
|
||||
definition capture's node, it gets added to this instance.</p>
|
||||
</dd>
|
||||
<dt><a href="#CaptureOrganizer">CaptureOrganizer</a></dt>
|
||||
<dd><p>Keeps track of @definition.* captures and the captures they may contain.</p>
|
||||
</dd>
|
||||
<dt><a href="#InvalidProviderError">InvalidProviderError</a> ⇐ <code>Error</code></dt>
|
||||
<dd><p>An error thrown when a newly added symbol provider does not conform to its
|
||||
contract.</p>
|
||||
</dd>
|
||||
<dt><a href="#ListController">ListController</a></dt>
|
||||
<dd><p>A class for setting various UI properties on a symbol list palette. This is a
|
||||
privilege given to the “main” (or <em>exclusive</em>) provider for a given task.</p>
|
||||
<p>This is how we allow a provider to communicate its state to the UI without
|
||||
giving it full control over the <code>SelectListView</code> used to show results.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
## Constants
|
||||
@ -173,6 +190,9 @@ style: Exclusively used for the <code>style</code> attribute</p>
|
||||
## Functions
|
||||
|
||||
<dl>
|
||||
<dt><a href="#renderMarkdown">renderMarkdown(content, givenOpts)</a> ⇒ <code>string</code></dt>
|
||||
<dd><p>Takes a Markdown document and renders it as HTML.</p>
|
||||
</dd>
|
||||
<dt><a href="#beforeEach">beforeEach()</a></dt>
|
||||
<dd></dd>
|
||||
<dt><a href="#beforeEach">beforeEach()</a></dt>
|
||||
@ -199,6 +219,25 @@ style: Exclusively used for the <code>style</code> attribute</p>
|
||||
<dd></dd>
|
||||
<dt><a href="#beforeEach">beforeEach()</a></dt>
|
||||
<dd></dd>
|
||||
<dt><a href="#isIterable">isIterable(obj)</a> ⇒ <code>Boolean</code></dt>
|
||||
<dd><p>Ensures an object can be iterated over.</p>
|
||||
<p>The contract with the symbol providers is that they return an object that
|
||||
gives us symbol objects when we iterate over it. It'll probably be an array,
|
||||
but we're cool with anything iterable.</p>
|
||||
</dd>
|
||||
<dt><a href="#timeout">timeout(ms)</a> ⇒ <code>Promise.<true></code></dt>
|
||||
<dd><p>Returns a promise that resolves after a given number of milliseconds.</p>
|
||||
</dd>
|
||||
<dt><a href="#getBadgeTextVariant">getBadgeTextVariant(text)</a> ⇒ <code>String</code></dt>
|
||||
<dd><p>Given a string of text, returns a hexadecimal character from <code>0</code> to <code>f</code> to
|
||||
represent a classification “bucket.” This is used when assigning colors to
|
||||
various symbol badges.</p>
|
||||
</dd>
|
||||
<dt><a href="#badge">badge(text, options)</a> ⇒ <code>Element</code></dt>
|
||||
<dd><p>Return a DOM element for a badge for a given symbol tag name.</p>
|
||||
</dd>
|
||||
<dt><a href="#beforeEach">beforeEach()</a></dt>
|
||||
<dd></dd>
|
||||
<dt><a href="#beforeEach">beforeEach()</a></dt>
|
||||
<dd></dd>
|
||||
<dt><a href="#beforeEach">beforeEach()</a></dt>
|
||||
@ -355,6 +394,37 @@ atom.clipboard.write('hello');
|
||||
|
||||
console.log(atom.clipboard.read());
|
||||
```
|
||||
<a name="Container"></a>
|
||||
|
||||
## Container
|
||||
A container capture. When another capture's node is contained by the
|
||||
definition capture's node, it gets added to this instance.
|
||||
|
||||
**Kind**: global class
|
||||
<a name="CaptureOrganizer"></a>
|
||||
|
||||
## CaptureOrganizer
|
||||
Keeps track of @definition.* captures and the captures they may contain.
|
||||
|
||||
**Kind**: global class
|
||||
<a name="InvalidProviderError"></a>
|
||||
|
||||
## InvalidProviderError ⇐ <code>Error</code>
|
||||
An error thrown when a newly added symbol provider does not conform to its
|
||||
contract.
|
||||
|
||||
**Kind**: global class
|
||||
**Extends**: <code>Error</code>
|
||||
<a name="ListController"></a>
|
||||
|
||||
## ListController
|
||||
A class for setting various UI properties on a symbol list palette. This is a
|
||||
privilege given to the “main” (or _exclusive_) provider for a given task.
|
||||
|
||||
This is how we allow a provider to communicate its state to the UI without
|
||||
giving it full control over the `SelectListView` used to show results.
|
||||
|
||||
**Kind**: global class
|
||||
<a name="etch"></a>
|
||||
|
||||
## etch
|
||||
@ -625,6 +695,68 @@ This file aims to run some short simple tests against `update.js`. Focusing
|
||||
**Babel**:
|
||||
<a name="beforeEach"></a>
|
||||
|
||||
## beforeEach()
|
||||
**Kind**: global function
|
||||
**Babel**:
|
||||
<a name="isIterable"></a>
|
||||
|
||||
## isIterable(obj) ⇒ <code>Boolean</code>
|
||||
Ensures an object can be iterated over.
|
||||
|
||||
The contract with the symbol providers is that they return an object that
|
||||
gives us symbol objects when we iterate over it. It'll probably be an array,
|
||||
but we're cool with anything iterable.
|
||||
|
||||
**Kind**: global function
|
||||
**Returns**: <code>Boolean</code> - Whether the item will respond correctly to a `for..of`
|
||||
loop.
|
||||
|
||||
| Param | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| obj | <code>?</code> | Anything. |
|
||||
|
||||
<a name="timeout"></a>
|
||||
|
||||
## timeout(ms) ⇒ <code>Promise.<true></code>
|
||||
Returns a promise that resolves after a given number of milliseconds.
|
||||
|
||||
**Kind**: global function
|
||||
**Returns**: <code>Promise.<true></code> - A promise that resolves with `true` as its argument.
|
||||
|
||||
| Param | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| ms | <code>Number</code> | Number of milliseconds after which to resolve. |
|
||||
|
||||
<a name="getBadgeTextVariant"></a>
|
||||
|
||||
## getBadgeTextVariant(text) ⇒ <code>String</code>
|
||||
Given a string of text, returns a hexadecimal character from `0` to `f` to
|
||||
represent a classification “bucket.” This is used when assigning colors to
|
||||
various symbol badges.
|
||||
|
||||
**Kind**: global function
|
||||
**Returns**: <code>String</code> - A single character that represents a hexadecimal digit.
|
||||
|
||||
| Param | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| text | <code>String</code> | The text of the badge. |
|
||||
|
||||
<a name="badge"></a>
|
||||
|
||||
## badge(text, options) ⇒ <code>Element</code>
|
||||
Return a DOM element for a badge for a given symbol tag name.
|
||||
|
||||
**Kind**: global function
|
||||
**Returns**: <code>Element</code> - An element for adding to an `atom-select-view` entry.
|
||||
|
||||
| Param | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| text | <code>String</code> | The text of the tag. |
|
||||
| options | <code>Object</code> | Options. Defaults to an empty object. |
|
||||
| options.variant | <code>Boolean</code> | Whether to add a class name for the badge's “variant.” If enabled, this will attempt to assign a different badge color for each kind of tag. Optional; defaults to `false`. |
|
||||
|
||||
<a name="beforeEach"></a>
|
||||
|
||||
## beforeEach()
|
||||
**Kind**: global function
|
||||
**Babel**:
|
||||
|
@ -7,6 +7,23 @@
|
||||
</dd>
|
||||
<dt><a href="#Clipboard">Clipboard</a></dt>
|
||||
<dd></dd>
|
||||
<dt><a href="#Container">Container</a></dt>
|
||||
<dd><p>A container capture. When another capture's node is contained by the
|
||||
definition capture's node, it gets added to this instance.</p>
|
||||
</dd>
|
||||
<dt><a href="#CaptureOrganizer">CaptureOrganizer</a></dt>
|
||||
<dd><p>Keeps track of @definition.* captures and the captures they may contain.</p>
|
||||
</dd>
|
||||
<dt><a href="#InvalidProviderError">InvalidProviderError</a> ⇐ <code>Error</code></dt>
|
||||
<dd><p>An error thrown when a newly added symbol provider does not conform to its
|
||||
contract.</p>
|
||||
</dd>
|
||||
<dt><a href="#ListController">ListController</a></dt>
|
||||
<dd><p>A class for setting various UI properties on a symbol list palette. This is a
|
||||
privilege given to the “main” (or <em>exclusive</em>) provider for a given task.</p>
|
||||
<p>This is how we allow a provider to communicate its state to the UI without
|
||||
giving it full control over the <code>SelectListView</code> used to show results.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
## Constants
|
||||
@ -173,6 +190,9 @@ style: Exclusively used for the <code>style</code> attribute</p>
|
||||
## Functions
|
||||
|
||||
<dl>
|
||||
<dt><a href="#renderMarkdown">renderMarkdown(content, givenOpts)</a> ⇒ <code>string</code></dt>
|
||||
<dd><p>Takes a Markdown document and renders it as HTML.</p>
|
||||
</dd>
|
||||
<dt><a href="#beforeEach">beforeEach()</a></dt>
|
||||
<dd></dd>
|
||||
<dt><a href="#beforeEach">beforeEach()</a></dt>
|
||||
@ -199,6 +219,25 @@ style: Exclusively used for the <code>style</code> attribute</p>
|
||||
<dd></dd>
|
||||
<dt><a href="#beforeEach">beforeEach()</a></dt>
|
||||
<dd></dd>
|
||||
<dt><a href="#isIterable">isIterable(obj)</a> ⇒ <code>Boolean</code></dt>
|
||||
<dd><p>Ensures an object can be iterated over.</p>
|
||||
<p>The contract with the symbol providers is that they return an object that
|
||||
gives us symbol objects when we iterate over it. It'll probably be an array,
|
||||
but we're cool with anything iterable.</p>
|
||||
</dd>
|
||||
<dt><a href="#timeout">timeout(ms)</a> ⇒ <code>Promise.<true></code></dt>
|
||||
<dd><p>Returns a promise that resolves after a given number of milliseconds.</p>
|
||||
</dd>
|
||||
<dt><a href="#getBadgeTextVariant">getBadgeTextVariant(text)</a> ⇒ <code>String</code></dt>
|
||||
<dd><p>Given a string of text, returns a hexadecimal character from <code>0</code> to <code>f</code> to
|
||||
represent a classification “bucket.” This is used when assigning colors to
|
||||
various symbol badges.</p>
|
||||
</dd>
|
||||
<dt><a href="#badge">badge(text, options)</a> ⇒ <code>Element</code></dt>
|
||||
<dd><p>Return a DOM element for a badge for a given symbol tag name.</p>
|
||||
</dd>
|
||||
<dt><a href="#beforeEach">beforeEach()</a></dt>
|
||||
<dd></dd>
|
||||
<dt><a href="#beforeEach">beforeEach()</a></dt>
|
||||
<dd></dd>
|
||||
<dt><a href="#beforeEach">beforeEach()</a></dt>
|
||||
@ -363,6 +402,37 @@ atom.clipboard.write('hello');
|
||||
|
||||
console.log(atom.clipboard.read());
|
||||
```
|
||||
<a name="Container"></a>
|
||||
|
||||
## Container
|
||||
A container capture. When another capture's node is contained by the
|
||||
definition capture's node, it gets added to this instance.
|
||||
|
||||
**Kind**: global class
|
||||
<a name="CaptureOrganizer"></a>
|
||||
|
||||
## CaptureOrganizer
|
||||
Keeps track of @definition.* captures and the captures they may contain.
|
||||
|
||||
**Kind**: global class
|
||||
<a name="InvalidProviderError"></a>
|
||||
|
||||
## InvalidProviderError ⇐ <code>Error</code>
|
||||
An error thrown when a newly added symbol provider does not conform to its
|
||||
contract.
|
||||
|
||||
**Kind**: global class
|
||||
**Extends**: <code>Error</code>
|
||||
<a name="ListController"></a>
|
||||
|
||||
## ListController
|
||||
A class for setting various UI properties on a symbol list palette. This is a
|
||||
privilege given to the “main” (or _exclusive_) provider for a given task.
|
||||
|
||||
This is how we allow a provider to communicate its state to the UI without
|
||||
giving it full control over the `SelectListView` used to show results.
|
||||
|
||||
**Kind**: global class
|
||||
<a name="etch"></a>
|
||||
|
||||
## etch
|
||||
@ -633,6 +703,68 @@ This file aims to run some short simple tests against `update.js`. Focusing
|
||||
**Babel**:
|
||||
<a name="beforeEach"></a>
|
||||
|
||||
## beforeEach()
|
||||
**Kind**: global function
|
||||
**Babel**:
|
||||
<a name="isIterable"></a>
|
||||
|
||||
## isIterable(obj) ⇒ <code>Boolean</code>
|
||||
Ensures an object can be iterated over.
|
||||
|
||||
The contract with the symbol providers is that they return an object that
|
||||
gives us symbol objects when we iterate over it. It'll probably be an array,
|
||||
but we're cool with anything iterable.
|
||||
|
||||
**Kind**: global function
|
||||
**Returns**: <code>Boolean</code> - Whether the item will respond correctly to a `for..of`
|
||||
loop.
|
||||
|
||||
| Param | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| obj | <code>?</code> | Anything. |
|
||||
|
||||
<a name="timeout"></a>
|
||||
|
||||
## timeout(ms) ⇒ <code>Promise.<true></code>
|
||||
Returns a promise that resolves after a given number of milliseconds.
|
||||
|
||||
**Kind**: global function
|
||||
**Returns**: <code>Promise.<true></code> - A promise that resolves with `true` as its argument.
|
||||
|
||||
| Param | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| ms | <code>Number</code> | Number of milliseconds after which to resolve. |
|
||||
|
||||
<a name="getBadgeTextVariant"></a>
|
||||
|
||||
## getBadgeTextVariant(text) ⇒ <code>String</code>
|
||||
Given a string of text, returns a hexadecimal character from `0` to `f` to
|
||||
represent a classification “bucket.” This is used when assigning colors to
|
||||
various symbol badges.
|
||||
|
||||
**Kind**: global function
|
||||
**Returns**: <code>String</code> - A single character that represents a hexadecimal digit.
|
||||
|
||||
| Param | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| text | <code>String</code> | The text of the badge. |
|
||||
|
||||
<a name="badge"></a>
|
||||
|
||||
## badge(text, options) ⇒ <code>Element</code>
|
||||
Return a DOM element for a badge for a given symbol tag name.
|
||||
|
||||
**Kind**: global function
|
||||
**Returns**: <code>Element</code> - An element for adding to an `atom-select-view` entry.
|
||||
|
||||
| Param | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| text | <code>String</code> | The text of the tag. |
|
||||
| options | <code>Object</code> | Options. Defaults to an empty object. |
|
||||
| options.variant | <code>Boolean</code> | Whether to add a class name for the badge's “variant.” If enabled, this will attempt to assign a different badge color for each kind of tag. Optional; defaults to `false`. |
|
||||
|
||||
<a name="beforeEach"></a>
|
||||
|
||||
## beforeEach()
|
||||
**Kind**: global function
|
||||
**Babel**:
|
||||
|
@ -14,7 +14,7 @@ const languages = [
|
||||
// {language: "gfm", code: '10', checks: {numeric: '10'}},
|
||||
// {language: "git", code: '10', checks: {numeric: '10'}},
|
||||
{language: "Go", code: '10', checks: {numeric: '10'}},
|
||||
{language: "HTML", code: '<foo type="10"></foo>', checks: {string: '10'}},
|
||||
{language: "HTML", code: '<foo type="10"></foo>', checks: {quoted: '"10"'}},
|
||||
{language: "XML", code: '<foo type="10"></foo>', checks: {string: '"10"'}},
|
||||
// {language: "hyperlink", code: '10', checks: {numeric: '10'}},
|
||||
{language: "JSON", code: '10', checks: {numeric: '10'}},
|
||||
@ -23,7 +23,7 @@ const languages = [
|
||||
// {language: "mustache", code: '10', checks: {numeric: '10'}},
|
||||
{language: "Objective C", code: '10', checks: {numeric: '10'}},
|
||||
{language: "Perl", code: '10', checks: {numeric: '10'}},
|
||||
{language: "PHP", code: '<? 10 %>', checks: {numeric: '10'}},
|
||||
{language: "PHP", code: '<? $foo ?>', checks: {variable: '$foo'}},
|
||||
// {language: "property-list", code: '10', checks: {numeric: '10'}},
|
||||
{language: "Python", code: '10', checks: {numeric: '10'}},
|
||||
{language: "Ruby on Rails", code: '10', checks: {numeric: '10'}},
|
||||
|
23
package.json
23
package.json
@ -2,7 +2,7 @@
|
||||
"name": "pulsar",
|
||||
"author": "Pulsar-Edit <admin@pulsar-edit.dev>",
|
||||
"productName": "Pulsar",
|
||||
"version": "1.110.0-dev",
|
||||
"version": "1.115.0-dev",
|
||||
"description": "A Community-led Hyper-Hackable Text Editor",
|
||||
"branding": {
|
||||
"id": "pulsar",
|
||||
@ -30,6 +30,7 @@
|
||||
"dependencies": {
|
||||
"@atom/source-map-support": "^0.3.4",
|
||||
"@babel/core": "7.18.6",
|
||||
"@pulsar-edit/fuzzy-native": "https://github.com/pulsar-edit/fuzzy-native.git#c6ddd2e0ace7b3cfe8082fcbe5985c49f76da5b8",
|
||||
"about": "file:packages/about",
|
||||
"archive-view": "file:packages/archive-view",
|
||||
"async": "3.2.4",
|
||||
@ -62,6 +63,7 @@
|
||||
"deprecation-cop": "file:packages/deprecation-cop",
|
||||
"dev-live-reload": "file:packages/dev-live-reload",
|
||||
"document-register-element": "https://github.com/pulsar-edit/document-register-element.git#1f5868f",
|
||||
"dompurify": "^3.0.6",
|
||||
"encoding-selector": "file:packages/encoding-selector",
|
||||
"etch": "0.14.1",
|
||||
"event-kit": "^2.5.3",
|
||||
@ -75,7 +77,7 @@
|
||||
"fuzzy-finder": "file:packages/fuzzy-finder",
|
||||
"git-diff": "file:packages/git-diff",
|
||||
"git-utils": "^5.7.3",
|
||||
"github": "https://codeload.github.com/pulsar-edit/github/tar.gz/refs/tags/v0.36.17-pretranspiled",
|
||||
"github": "https://github.com/pulsar-edit/github/archive/refs/tags/v0.36.20-pretranspiled.tar.gz",
|
||||
"go-to-line": "file:packages/go-to-line",
|
||||
"grammar-selector": "file:packages/grammar-selector",
|
||||
"grim": "2.0.3",
|
||||
@ -124,6 +126,10 @@
|
||||
"line-ending-selector": "file:packages/line-ending-selector",
|
||||
"line-top-index": "0.3.1",
|
||||
"link": "file:packages/link",
|
||||
"markdown-it": "^13.0.2",
|
||||
"markdown-it-emoji": "^2.0.2",
|
||||
"markdown-it-github-headings": "^2.0.1",
|
||||
"markdown-it-task-checkbox": "^1.0.6",
|
||||
"markdown-preview": "file:./packages/markdown-preview",
|
||||
"minimatch": "^3.0.3",
|
||||
"mocha": "6.2.3",
|
||||
@ -153,14 +159,16 @@
|
||||
"service-hub": "^0.7.4",
|
||||
"settings-view": "file:packages/settings-view",
|
||||
"sinon": "9.2.1",
|
||||
"snippets": "github:pulsar-edit/snippets#ba70705",
|
||||
"snippets": "github:pulsar-edit/snippets#6b9163415270b8757b262f5dfaafaa05d47324a9",
|
||||
"solarized-dark-syntax": "file:packages/solarized-dark-syntax",
|
||||
"solarized-light-syntax": "file:packages/solarized-light-syntax",
|
||||
"spell-check": "file:packages/spell-check",
|
||||
"status-bar": "file:packages/status-bar",
|
||||
"styleguide": "file:./packages/styleguide",
|
||||
"superstring": "^2.4.4",
|
||||
"symbols-view": "https://codeload.github.com/atom/symbols-view/legacy.tar.gz/refs/tags/v0.118.4",
|
||||
"symbol-provider-ctags": "file:./packages/symbol-provider-ctags",
|
||||
"symbol-provider-tree-sitter": "file:./packages/symbol-provider-tree-sitter",
|
||||
"symbols-view": "file:./packages/symbols-view",
|
||||
"tabs": "file:packages/tabs",
|
||||
"temp": "0.9.4",
|
||||
"text-buffer": "^13.18.6",
|
||||
@ -176,6 +184,7 @@
|
||||
"whitespace": "file:./packages/whitespace",
|
||||
"winreg": "^1.2.1",
|
||||
"wrap-guide": "file:./packages/wrap-guide",
|
||||
"yaml-front-matter": "^4.1.1",
|
||||
"yargs": "17.6.2"
|
||||
},
|
||||
"packageDependencies": {
|
||||
@ -211,7 +220,7 @@
|
||||
"exception-reporting": "file:./packages/exception-reporting",
|
||||
"find-and-replace": "file:./packages/find-and-replace",
|
||||
"fuzzy-finder": "file:packages/fuzzy-finder",
|
||||
"github": "0.36.17",
|
||||
"github": "0.36.20",
|
||||
"git-diff": "file:./packages/git-diff",
|
||||
"go-to-line": "file:./packages/go-to-line",
|
||||
"grammar-selector": "file:./packages/grammar-selector",
|
||||
@ -230,7 +239,9 @@
|
||||
"spell-check": "file:./packages/spell-check",
|
||||
"status-bar": "file:./packages/status-bar",
|
||||
"styleguide": "file:./packages/styleguide",
|
||||
"symbols-view": "0.118.4",
|
||||
"symbol-provider-ctags": "file:./packages/symbol-provider-ctags",
|
||||
"symbol-provider-tree-sitter": "file:./packages/symbol-provider-tree-sitter",
|
||||
"symbols-view": "file:./packages/symbols-view",
|
||||
"tabs": "file:./packages/tabs",
|
||||
"timecop": "file:./packages/timecop",
|
||||
"tree-view": "file:./packages/tree-view",
|
||||
|
@ -9,3 +9,4 @@
|
||||
@import "styles/syntax/base.less";
|
||||
@import "styles/syntax/css.less";
|
||||
@import "styles/syntax/html.less";
|
||||
@import "styles/syntax/json.less";
|
||||
|
@ -229,6 +229,12 @@
|
||||
&.syntax--italic {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
// Horizontal rules in GFM used to be scoped as `comment.hr`. For continuity,
|
||||
// we assign the color of a comment to this new scope.
|
||||
&.syntax--horizontal-rule {
|
||||
color: #8A8A8A;
|
||||
}
|
||||
}
|
||||
|
||||
// /* comment */
|
||||
|
11
packages/atom-dark-syntax/styles/syntax/json.less
Normal file
11
packages/atom-dark-syntax/styles/syntax/json.less
Normal file
@ -0,0 +1,11 @@
|
||||
|
||||
.syntax--source.syntax--json {
|
||||
|
||||
// Color JSON keys differently from other strings.
|
||||
.syntax--meta.syntax--structure.syntax--key {
|
||||
.syntax--string.syntax--quoted.syntax--double {
|
||||
color: #96CBFE;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -8,3 +8,4 @@
|
||||
|
||||
@import "styles/syntax/base.less";
|
||||
@import "styles/syntax/css.less";
|
||||
@import "styles/syntax/json.less";
|
||||
|
@ -198,6 +198,12 @@
|
||||
&.syntax--italic {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
// Horizontal rules in GFM used to be scoped as `comment.hr`. For continuity,
|
||||
// we assign the color of a comment to this new scope.
|
||||
&.syntax--horizontal-rule {
|
||||
color: #999988;
|
||||
}
|
||||
}
|
||||
|
||||
// /* comment */
|
||||
|
11
packages/atom-light-syntax/styles/syntax/json.less
Normal file
11
packages/atom-light-syntax/styles/syntax/json.less
Normal file
@ -0,0 +1,11 @@
|
||||
|
||||
.syntax--source.syntax--json {
|
||||
|
||||
// Color JSON keys differently from other strings.
|
||||
.syntax--meta.syntax--structure.syntax--key {
|
||||
.syntax--string.syntax--quoted.syntax--double {
|
||||
color: #008080;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -33,7 +33,7 @@ const packagesToTest = {
|
||||
|
||||
const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
||||
|
||||
const whenEditorReady = function(editor) {
|
||||
const whenEditorReady = function (editor) {
|
||||
const languageMode = editor.getBuffer().getLanguageMode();
|
||||
if (!languageMode.constructor.name.includes('TreeSitter')) {
|
||||
return Promise.resolve();
|
||||
@ -105,8 +105,6 @@ describe("CSS property name and value autocompletions", async () => {
|
||||
await atom.workspace.open(packagesToTest[packageLabel].file);
|
||||
editor = atom.workspace.getActiveTextEditor();
|
||||
await whenEditorReady(editor);
|
||||
console.warn('USING TREE SITTER?!?', packageLabel, meta.useTreeSitter);
|
||||
atom.config.set('core.useExperimentalModernTreeSitter', meta.useTreeSitter ?? false);
|
||||
atom.config.set('core.useTreeSitterParsers', meta.useTreeSitter ?? false);
|
||||
});
|
||||
|
||||
@ -738,7 +736,7 @@ div:nth {
|
||||
})
|
||||
);
|
||||
|
||||
Object.keys(packagesToTest).forEach(function(packageLabel) {
|
||||
Object.keys(packagesToTest).forEach(function (packageLabel) {
|
||||
if (packagesToTest[packageLabel].name !== 'language-css') {
|
||||
describe(`${packageLabel} files`, async () => {
|
||||
beforeEach(async () => {
|
||||
|
@ -9,7 +9,8 @@ const provider = {
|
||||
|
||||
getSuggestions (request) {
|
||||
try {
|
||||
if (request.editor.getBuffer().getLanguageMode().tree) {
|
||||
let languageMode = request.editor.getBuffer().getLanguageMode();
|
||||
if (languageMode.constructor.name === 'TreeSitterLanguageMode') {
|
||||
return getSuggestionsWithTreeSitter(request)
|
||||
} else {
|
||||
return getSuggestionsWithTextMate(request)
|
||||
|
@ -50,7 +50,7 @@ function isTagStart ({prefix, scopeDescriptor, bufferPosition, editor}) {
|
||||
function isAttributeStart ({prefix, scopeDescriptor, bufferPosition, editor}) {
|
||||
const scopes = scopeDescriptor.getScopesArray()
|
||||
if (!getPreviousAttribute(editor, bufferPosition) && prefix && !prefix.trim()) {
|
||||
return hasTagScope(scopes)
|
||||
return hasTagScope(scopes) || afterTagScope(editor, bufferPosition)
|
||||
}
|
||||
|
||||
const previousBufferPosition = [bufferPosition.row, Math.max(0, bufferPosition.column - 1)]
|
||||
@ -68,6 +68,25 @@ function isAttributeStart ({prefix, scopeDescriptor, bufferPosition, editor}) {
|
||||
)
|
||||
}
|
||||
|
||||
// This fixes the
|
||||
//
|
||||
// <div |
|
||||
//
|
||||
// scenario in Tree-sitter grammars — no closing `>` on the tag, so we should
|
||||
// move back to the nearest text and try to read the scopes from there.
|
||||
// Designed to work no matter how many spaces there are between the end of the
|
||||
// tag name and the cursor.
|
||||
function afterTagScope (editor, bufferPosition) {
|
||||
let cursor = editor.getCursors().find(cursor => {
|
||||
return cursor.getBufferPosition().isEqual(bufferPosition)
|
||||
})
|
||||
if (!cursor) return false;
|
||||
let position = cursor.getPreviousWordBoundaryBufferPosition();
|
||||
position = position.translate([0, -1]);
|
||||
let scopes = editor.scopeDescriptorForBufferPosition(position);
|
||||
return scopes.getScopesArray().some(t => t.startsWith('entity.name.tag'));
|
||||
}
|
||||
|
||||
function isAttributeValueStart ({scopeDescriptor, bufferPosition, editor}) {
|
||||
const scopes = scopeDescriptor.getScopesArray()
|
||||
|
||||
@ -75,6 +94,11 @@ function isAttributeValueStart ({scopeDescriptor, bufferPosition, editor}) {
|
||||
const previousScopes = editor.scopeDescriptorForBufferPosition(previousBufferPosition)
|
||||
const previousScopesArray = previousScopes.getScopesArray()
|
||||
|
||||
// This is an unambiguous case — if the cursor is on the right side of the
|
||||
// opening quote, then we must be in the right place.
|
||||
if (previousScopesArray.includes('punctuation.definition.string.begin.html'))
|
||||
return true
|
||||
|
||||
// autocomplete here: attribute="|"
|
||||
// not here: attribute=|""
|
||||
// or here: attribute=""|
|
||||
|
13
packages/autocomplete-html/spec/.eslintrc.js
Normal file
13
packages/autocomplete-html/spec/.eslintrc.js
Normal file
@ -0,0 +1,13 @@
|
||||
module.exports = {
|
||||
env: { jasmine: true },
|
||||
globals: {
|
||||
"waitsForPromise": true,
|
||||
"advanceClock": true
|
||||
},
|
||||
rules: {
|
||||
"node/no-unpublished-require": "off",
|
||||
"node/no-extraneous-require": "off",
|
||||
"no-unused-vars": "off",
|
||||
"no-empty": "off"
|
||||
}
|
||||
};
|
@ -1,5 +1,5 @@
|
||||
describe('HTML autocompletions', () => {
|
||||
let editor, provider
|
||||
let editor, provider, languageMode
|
||||
|
||||
function getCompletions () {
|
||||
const cursor = editor.getLastCursor()
|
||||
@ -32,51 +32,66 @@ describe('HTML autocompletions', () => {
|
||||
return -1;
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
waitsForPromise(() => atom.packages.activatePackage('autocomplete-html'))
|
||||
waitsForPromise(() => atom.packages.activatePackage('language-html'))
|
||||
waitsForPromise(() => atom.workspace.open('test.html'))
|
||||
beforeEach(async () => {
|
||||
await atom.packages.activatePackage('autocomplete-html')
|
||||
await atom.packages.activatePackage('language-html')
|
||||
await atom.workspace.open('test.html')
|
||||
editor = atom.workspace.getActiveTextEditor()
|
||||
languageMode = editor.getBuffer().getLanguageMode()
|
||||
languageMode.useAsyncParsing = false
|
||||
languageMode.useAsyncIndent = false
|
||||
await languageMode.ready
|
||||
|
||||
runs(() => provider = atom.packages.getActivePackage('autocomplete-html').mainModule.getProvider())
|
||||
runs(() => editor = atom.workspace.getActiveTextEditor())
|
||||
provider = atom.packages.getActivePackage('autocomplete-html').mainModule.getProvider()
|
||||
})
|
||||
|
||||
it('returns no completions when not at the start of a tag', () => {
|
||||
afterEach(async () => {
|
||||
await languageMode.atTransactionEnd()
|
||||
})
|
||||
|
||||
it('returns no completions when not at the start of a tag', async () => {
|
||||
editor.setText('')
|
||||
await languageMode.atTransactionEnd()
|
||||
expect(getCompletions().length).toBe(0)
|
||||
|
||||
editor.setText('d')
|
||||
editor.setCursorBufferPosition([0, 0])
|
||||
await languageMode.atTransactionEnd()
|
||||
|
||||
expect(getCompletions().length).toBe(0)
|
||||
|
||||
editor.setCursorBufferPosition([0, 1])
|
||||
expect(getCompletions().length).toBe(0)
|
||||
})
|
||||
|
||||
it('returns no completions in style tags', () => {
|
||||
it('returns no completions in style tags', async () => {
|
||||
editor.setText(`\
|
||||
<style>
|
||||
<
|
||||
</style>\
|
||||
`
|
||||
)
|
||||
await languageMode.atTransactionEnd()
|
||||
editor.setCursorBufferPosition([1, 1])
|
||||
expect(getCompletions().length).toBe(0)
|
||||
})
|
||||
|
||||
it('returns no completions in script tags', () => {
|
||||
it('returns no completions in script tags', async () => {
|
||||
editor.setText(`\
|
||||
<script>
|
||||
<
|
||||
</script>\
|
||||
`
|
||||
)
|
||||
await languageMode.atTransactionEnd()
|
||||
editor.setCursorBufferPosition([1, 1])
|
||||
expect(getCompletions().length).toBe(0)
|
||||
})
|
||||
|
||||
it('autcompletes tag names without a prefix', () => {
|
||||
it('autcompletes tag names without a prefix', async () => {
|
||||
editor.setText('<')
|
||||
editor.setCursorBufferPosition([0, 1])
|
||||
await languageMode.atTransactionEnd()
|
||||
|
||||
const completions = getCompletions()
|
||||
expect(completions.length).toBeGreaterThan(113) // Fun Fact last check this was 232
|
||||
@ -90,9 +105,10 @@ describe('HTML autocompletions', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('autocompletes tag names with a prefix', () => {
|
||||
it('autocompletes tag names with a prefix', async () => {
|
||||
editor.setText('<d')
|
||||
editor.setCursorBufferPosition([0, 2])
|
||||
await languageMode.atTransactionEnd()
|
||||
|
||||
let completions = getCompletions()
|
||||
expect(completions.length).toBeGreaterThan(9) // Fun fact last check was 14
|
||||
@ -126,24 +142,27 @@ describe('HTML autocompletions', () => {
|
||||
expect(isValueInCompletions('dt', completions)).toBe(true)
|
||||
})
|
||||
|
||||
it("does not autocomplete tag names if there's a space after the <", () => {
|
||||
it("does not autocomplete tag names if there's a space after the <", async () => {
|
||||
editor.setText('< ')
|
||||
editor.setCursorBufferPosition([0, 2])
|
||||
await languageMode.atTransactionEnd()
|
||||
|
||||
let completions = getCompletions()
|
||||
expect(completions.length).toBe(0)
|
||||
|
||||
editor.setText('< h')
|
||||
editor.setCursorBufferPosition([0, 2])
|
||||
await languageMode.atTransactionEnd()
|
||||
|
||||
completions = getCompletions()
|
||||
expect(completions.length).toBe(0)
|
||||
})
|
||||
|
||||
it('does not provide a descriptionMoreURL if the tag does not have a unique description', () => {
|
||||
it('does not provide a descriptionMoreURL if the tag does not have a unique description', async () => {
|
||||
// isindex does not have an associated MDN page as of March 25, 2023
|
||||
editor.setText('<i')
|
||||
editor.setCursorBufferPosition([0, 2])
|
||||
await languageMode.atTransactionEnd()
|
||||
|
||||
const completions = getCompletions()
|
||||
const loc = getValueInCompletionsIndex('isindex', completions)
|
||||
@ -153,9 +172,10 @@ describe('HTML autocompletions', () => {
|
||||
expect(completions[loc].descriptionMoreURL).toBeNull()
|
||||
})
|
||||
|
||||
it('autocompletes attribute names without a prefix', () => {
|
||||
it('autocompletes attribute names without a prefix', async () => {
|
||||
editor.setText('<div ')
|
||||
editor.setCursorBufferPosition([0, 5])
|
||||
await languageMode.atTransactionEnd()
|
||||
|
||||
let completions = getCompletions()
|
||||
expect(completions.length).toBeGreaterThan(86) // Fun fact last check this was 264
|
||||
@ -171,6 +191,7 @@ describe('HTML autocompletions', () => {
|
||||
|
||||
editor.setText('<marquee ')
|
||||
editor.setCursorBufferPosition([0, 9])
|
||||
await languageMode.atTransactionEnd()
|
||||
|
||||
completions = getCompletions()
|
||||
expect(completions.length).toBeGreaterThan(98) // Last check 274
|
||||
@ -186,6 +207,7 @@ describe('HTML autocompletions', () => {
|
||||
|
||||
editor.setText('<div >')
|
||||
editor.setCursorBufferPosition([0, 5])
|
||||
await languageMode.atTransactionEnd()
|
||||
|
||||
completions = getCompletions()
|
||||
expect(completions.length).toBeGreaterThan(0)
|
||||
@ -193,6 +215,7 @@ describe('HTML autocompletions', () => {
|
||||
|
||||
editor.setText('<div >')
|
||||
editor.setCursorBufferPosition([0, 5])
|
||||
await languageMode.atTransactionEnd()
|
||||
|
||||
completions = getCompletions()
|
||||
expect(completions.length).toBeGreaterThan(0)
|
||||
@ -201,9 +224,10 @@ describe('HTML autocompletions', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('autocompletes attribute names with a prefix', () => {
|
||||
it('autocompletes attribute names with a prefix', async () => {
|
||||
editor.setText('<div c')
|
||||
editor.setCursorBufferPosition([0, 6])
|
||||
await languageMode.atTransactionEnd()
|
||||
|
||||
let completions = getCompletions()
|
||||
expect(completions.length).toBeGreaterThan(3) // Last check 9
|
||||
@ -218,6 +242,7 @@ describe('HTML autocompletions', () => {
|
||||
|
||||
editor.setText('<div C')
|
||||
editor.setCursorBufferPosition([0, 6])
|
||||
await languageMode.atTransactionEnd()
|
||||
|
||||
completions = getCompletions()
|
||||
expect(completions.length).toBeGreaterThan(3) // Last check 9
|
||||
@ -228,6 +253,7 @@ describe('HTML autocompletions', () => {
|
||||
|
||||
editor.setText('<div c>')
|
||||
editor.setCursorBufferPosition([0, 6])
|
||||
await languageMode.atTransactionEnd()
|
||||
|
||||
completions = getCompletions()
|
||||
expect(completions.length).toBeGreaterThan(3)
|
||||
@ -238,6 +264,7 @@ describe('HTML autocompletions', () => {
|
||||
|
||||
editor.setText('<div c></div>')
|
||||
editor.setCursorBufferPosition([0, 6])
|
||||
await languageMode.atTransactionEnd()
|
||||
|
||||
completions = getCompletions()
|
||||
expect(completions.length).toBeGreaterThan(3)
|
||||
@ -248,6 +275,7 @@ describe('HTML autocompletions', () => {
|
||||
|
||||
editor.setText('<marquee di')
|
||||
editor.setCursorBufferPosition([0, 12])
|
||||
await languageMode.atTransactionEnd()
|
||||
|
||||
completions = getCompletions()
|
||||
expect(isValueInCompletions('direction', completions, 'displayText'))
|
||||
@ -255,65 +283,73 @@ describe('HTML autocompletions', () => {
|
||||
|
||||
editor.setText('<marquee dI')
|
||||
editor.setCursorBufferPosition([0, 12])
|
||||
await languageMode.atTransactionEnd()
|
||||
|
||||
completions = getCompletions()
|
||||
expect(isValueInCompletions('direction', completions, 'displayText'))
|
||||
expect(isValueInCompletions('dir', completions, 'displayText'))
|
||||
})
|
||||
|
||||
it('autocompletes attribute names without a prefix surrounded by whitespace', () => {
|
||||
it('autocompletes attribute names without a prefix surrounded by whitespace', async () => {
|
||||
editor.setText('<select autofocus')
|
||||
editor.setCursorBufferPosition([0, 8])
|
||||
await languageMode.atTransactionEnd()
|
||||
|
||||
const completions = getCompletions()
|
||||
for (let completion of completions) { expect(completion.type).toBe('attribute') }
|
||||
expect(isValueInCompletions('autofocus', completions, 'displayText'))
|
||||
})
|
||||
|
||||
it('autocompletes attribute names with a prefix surrounded by whitespace', () => {
|
||||
it('autocompletes attribute names with a prefix surrounded by whitespace', async () => {
|
||||
editor.setText('<select o autofocus')
|
||||
editor.setCursorBufferPosition([0, 9])
|
||||
await languageMode.atTransactionEnd()
|
||||
|
||||
const completions = getCompletions()
|
||||
for (let completion of completions) { expect(completion.type).toBe('attribute') }
|
||||
expect(isValueInCompletions('onabort', completions, 'displayText'))
|
||||
})
|
||||
|
||||
it("respects the 'flag' type when autocompleting attribute names", () => {
|
||||
it("respects the 'flag' type when autocompleting attribute names", async () => {
|
||||
editor.setText('<select ')
|
||||
editor.setCursorBufferPosition([0, 8])
|
||||
await languageMode.atTransactionEnd()
|
||||
|
||||
const completions = getCompletions()
|
||||
expect(isValueInCompletions('autofocus', completions, 'snippet'))
|
||||
})
|
||||
|
||||
it('does not autocomplete attribute names in between an attribute name and value', () => {
|
||||
it('does not autocomplete attribute names in between an attribute name and value', async () => {
|
||||
editor.setText('<select autofocus=""')
|
||||
editor.setCursorBufferPosition([0, 18])
|
||||
await languageMode.atTransactionEnd()
|
||||
|
||||
let completions = getCompletions()
|
||||
expect(completions.length).toBe(0)
|
||||
|
||||
editor.setText('<select autofocus= ""')
|
||||
editor.setCursorBufferPosition([0, 18])
|
||||
await languageMode.atTransactionEnd()
|
||||
|
||||
completions = getCompletions()
|
||||
expect(completions.length).toBe(0)
|
||||
|
||||
editor.setText('<select autofocus= ""')
|
||||
editor.setCursorBufferPosition([0, 19])
|
||||
await languageMode.atTransactionEnd()
|
||||
|
||||
completions = getCompletions()
|
||||
expect(completions.length).toBe(0)
|
||||
|
||||
editor.setText('<select autofocus= ""')
|
||||
editor.setCursorBufferPosition([0, 19])
|
||||
await languageMode.atTransactionEnd()
|
||||
|
||||
completions = getCompletions()
|
||||
expect(completions.length).toBe(0)
|
||||
})
|
||||
|
||||
it('does not autocomplete attribute names outside of a tag', () => {
|
||||
it('does not autocomplete attribute names outside of a tag', async () => {
|
||||
editor.setText('<kbd>')
|
||||
editor.setCursorBufferPosition([0, 0])
|
||||
|
||||
@ -324,18 +360,20 @@ describe('HTML autocompletions', () => {
|
||||
expect(getCompletions().length).toBe(0)
|
||||
})
|
||||
|
||||
it('does not throw when a local attribute is not in the attributes list', () => {
|
||||
it('does not throw when a local attribute is not in the attributes list', async () => {
|
||||
// Some tags, like body, have local attributes that are not present in the top-level attributes array
|
||||
editor.setText('<body ')
|
||||
editor.setCursorBufferPosition([0, 6])
|
||||
await languageMode.atTransactionEnd()
|
||||
|
||||
const completions = getCompletions()
|
||||
expect(isValueInCompletions('onafterprint', completions, 'displayText'))
|
||||
})
|
||||
|
||||
it('does not provide a descriptionMoreURL if the attribute does not have a unique description', () => {
|
||||
it('does not provide a descriptionMoreURL if the attribute does not have a unique description', async () => {
|
||||
editor.setText('<input on')
|
||||
editor.setCursorBufferPosition([0, 9])
|
||||
await languageMode.atTransactionEnd()
|
||||
|
||||
const completions = getCompletions()
|
||||
|
||||
@ -346,9 +384,10 @@ describe('HTML autocompletions', () => {
|
||||
expect(completions[loc].descriptionMoreURL).toBeNull()
|
||||
})
|
||||
|
||||
it('autocompletes attribute values without a prefix', () => {
|
||||
it('autocompletes attribute values without a prefix', async () => {
|
||||
editor.setText('<marquee behavior=""')
|
||||
editor.setCursorBufferPosition([0, 19])
|
||||
await languageMode.atTransactionEnd()
|
||||
|
||||
let completions = getCompletions()
|
||||
expect(completions.length).toBe(3)
|
||||
@ -360,28 +399,37 @@ describe('HTML autocompletions', () => {
|
||||
expect(completions[1].text).toBe('slide')
|
||||
expect(completions[2].text).toBe('alternate')
|
||||
|
||||
editor.setText('<marquee behavior="')
|
||||
editor.setCursorBufferPosition([0, 19])
|
||||
|
||||
completions = getCompletions()
|
||||
expect(completions.length).toBe(3)
|
||||
|
||||
expect(completions[0].text).toBe('scroll')
|
||||
expect(completions[1].text).toBe('slide')
|
||||
expect(completions[2].text).toBe('alternate')
|
||||
|
||||
editor.setText('<marquee behavior=\'')
|
||||
editor.setCursorBufferPosition([0, 19])
|
||||
|
||||
completions = getCompletions()
|
||||
expect(completions.length).toBe(3)
|
||||
|
||||
expect(completions[0].text).toBe('scroll')
|
||||
expect(completions[1].text).toBe('slide')
|
||||
expect(completions[2].text).toBe('alternate')
|
||||
// NOTE: The Tree-sitter parser goes absolutely mental in this scenario. It
|
||||
// presents a much more reasonable tree when the closing quote is present,
|
||||
// but it'd be incredibly hard to salvage something from this mess.
|
||||
//
|
||||
// This isn't ideal, but most users will have enabled smart typing pairs,
|
||||
// so we'll let this slide.
|
||||
// editor.setText('<marquee behavior="')
|
||||
// editor.setCursorBufferPosition([0, 19])
|
||||
// await languageMode.atTransactionEnd()
|
||||
//
|
||||
// completions = getCompletions()
|
||||
// expect(completions.length).toBe(3)
|
||||
//
|
||||
// expect(completions[0].text).toBe('scroll')
|
||||
// expect(completions[1].text).toBe('slide')
|
||||
// expect(completions[2].text).toBe('alternate')
|
||||
//
|
||||
// editor.setText('<marquee behavior=\'')
|
||||
// editor.setCursorBufferPosition([0, 19])
|
||||
// await languageMode.atTransactionEnd()
|
||||
//
|
||||
// completions = getCompletions()
|
||||
// expect(completions.length).toBe(3)
|
||||
//
|
||||
// expect(completions[0].text).toBe('scroll')
|
||||
// expect(completions[1].text).toBe('slide')
|
||||
// expect(completions[2].text).toBe('alternate')
|
||||
|
||||
editor.setText('<marquee behavior=\'\'')
|
||||
editor.setCursorBufferPosition([0, 19])
|
||||
await languageMode.atTransactionEnd()
|
||||
|
||||
completions = getCompletions()
|
||||
expect(completions.length).toBe(3)
|
||||
@ -391,9 +439,10 @@ describe('HTML autocompletions', () => {
|
||||
expect(completions[2].text).toBe('alternate')
|
||||
})
|
||||
|
||||
it('autocompletes attribute values with a prefix', () => {
|
||||
it('autocompletes attribute values with a prefix', async () => {
|
||||
editor.setText('<html behavior="" lang="e"')
|
||||
editor.setCursorBufferPosition([0, 25])
|
||||
await languageMode.atTransactionEnd()
|
||||
|
||||
let completions = getCompletions()
|
||||
expect(completions.length).toBe(6)
|
||||
@ -408,6 +457,7 @@ describe('HTML autocompletions', () => {
|
||||
|
||||
editor.setText('<html behavior="" lang="E"')
|
||||
editor.setCursorBufferPosition([0, 25])
|
||||
await languageMode.atTransactionEnd()
|
||||
|
||||
completions = getCompletions()
|
||||
expect(completions.length).toBe(6)
|
||||
@ -421,6 +471,7 @@ describe('HTML autocompletions', () => {
|
||||
|
||||
editor.setText('<html behavior="" lang=\'e\'')
|
||||
editor.setCursorBufferPosition([0, 25])
|
||||
await languageMode.atTransactionEnd()
|
||||
|
||||
completions = getCompletions()
|
||||
expect(completions.length).toBe(6)
|
||||
@ -433,9 +484,10 @@ describe('HTML autocompletions', () => {
|
||||
expect(completions[5].text).toBe('es')
|
||||
})
|
||||
|
||||
it('autocompletes ambiguous attribute values', () => {
|
||||
it('autocompletes ambiguous attribute values', async () => {
|
||||
editor.setText('<button type=""')
|
||||
editor.setCursorBufferPosition([0, 14])
|
||||
await languageMode.atTransactionEnd()
|
||||
|
||||
let completions = getCompletions()
|
||||
expect(completions.length).toBe(3)
|
||||
@ -449,6 +501,7 @@ describe('HTML autocompletions', () => {
|
||||
|
||||
editor.setText('<link rel=""')
|
||||
editor.setCursorBufferPosition([0, 11])
|
||||
await languageMode.atTransactionEnd()
|
||||
|
||||
completions = getCompletions()
|
||||
expect(completions.length).toBe(13)
|
||||
@ -460,9 +513,10 @@ describe('HTML autocompletions', () => {
|
||||
expect(completions[0].descriptionMoreURL.endsWith('/HTML/Element/link#attributes')).toBe(true)
|
||||
})
|
||||
|
||||
it("provides 'true' and 'false' suggestions when autocompleting boolean attributes", () => {
|
||||
it("provides 'true' and 'false' suggestions when autocompleting boolean attributes", async () => {
|
||||
editor.setText('<html contenteditable=""')
|
||||
editor.setCursorBufferPosition([0, 23])
|
||||
await languageMode.atTransactionEnd()
|
||||
|
||||
const completions = getCompletions()
|
||||
expect(completions.length).toBe(2)
|
||||
@ -470,50 +524,55 @@ describe('HTML autocompletions', () => {
|
||||
expect(completions[1].text).toBe('false')
|
||||
})
|
||||
|
||||
it('does not attempt to autocomplete values before the beginning of a string', () => {
|
||||
it('does not attempt to autocomplete values before the beginning of a string', async () => {
|
||||
editor.setText('<button type=""')
|
||||
editor.setCursorBufferPosition([0, 13])
|
||||
await languageMode.atTransactionEnd()
|
||||
|
||||
let completions = []
|
||||
expect(() => completions = getCompletions()).not.toThrow()
|
||||
expect(completions.length).toBe(0)
|
||||
})
|
||||
|
||||
it('does not attempt to autocomplete values after the end of a string', () => {
|
||||
it('does not attempt to autocomplete values after the end of a string', async () => {
|
||||
editor.setText('<button type=""')
|
||||
editor.setCursorBufferPosition([0, 15])
|
||||
await languageMode.atTransactionEnd()
|
||||
|
||||
let completions = []
|
||||
expect(() => completions = getCompletions()).not.toThrow()
|
||||
expect(completions.length).toBe(0)
|
||||
})
|
||||
|
||||
it('does not throw when quotes are in the attribute value', () => {
|
||||
it('does not throw when quotes are in the attribute value', async () => {
|
||||
editor.setText('<button type="\'"')
|
||||
editor.setCursorBufferPosition([0, 15])
|
||||
await languageMode.atTransactionEnd()
|
||||
|
||||
expect(() => getCompletions()).not.toThrow()
|
||||
})
|
||||
|
||||
it("does not autocomplete attribute values if there isn't a corresponding attribute", () => {
|
||||
it("does not autocomplete attribute values if there isn't a corresponding attribute", async () => {
|
||||
editor.setText('<button type="""')
|
||||
editor.setCursorBufferPosition([0, 16])
|
||||
await languageMode.atTransactionEnd()
|
||||
|
||||
let completions = []
|
||||
expect(() => completions = getCompletions()).not.toThrow()
|
||||
expect(completions.length).toBe(0)
|
||||
})
|
||||
|
||||
it('does not throw when attempting to autocomplete values for nonexistent attributes', () => {
|
||||
it('does not throw when attempting to autocomplete values for nonexistent attributes', async () => {
|
||||
editor.setText('<button typ=""')
|
||||
editor.setCursorBufferPosition([0, 13])
|
||||
await languageMode.atTransactionEnd()
|
||||
|
||||
let completions = []
|
||||
expect(() => completions = getCompletions()).not.toThrow()
|
||||
expect(completions.length).toBe(0)
|
||||
})
|
||||
|
||||
it('triggers autocomplete when an attibute has been inserted', () => {
|
||||
it('triggers autocomplete when an attibute has been inserted', async () => {
|
||||
spyOn(atom.commands, 'dispatch')
|
||||
const suggestion = {type: 'attribute', text: 'whatever'}
|
||||
provider.onDidInsertSuggestion({editor, suggestion})
|
||||
@ -526,20 +585,16 @@ describe('HTML autocompletions', () => {
|
||||
expect(args[1]).toBe('autocomplete-plus:activate')
|
||||
})
|
||||
|
||||
it('does not error in EJS documents', () => {
|
||||
it('does not error in EJS documents', async () => {
|
||||
waitsForPromise(async () => {
|
||||
await atom.workspace.open('test.html.ejs')
|
||||
editor = atom.workspace.getActiveTextEditor()
|
||||
editor.setText('<span><% a = ""; %></span>')
|
||||
return languageMode.atTransactionEnd()
|
||||
})
|
||||
|
||||
waitsForPromise(() => {
|
||||
return atom.packages.activatePackage('language-javascript')
|
||||
})
|
||||
|
||||
runs(() => {
|
||||
editor.setCursorBufferPosition([0, editor.getText().indexOf('""') + 1])
|
||||
expect(() => getCompletions()).not.toThrow()
|
||||
})
|
||||
await atom.packages.activatePackage('language-javascript')
|
||||
editor.setCursorBufferPosition([0, editor.getText().indexOf('""') + 1])
|
||||
expect(() => getCompletions()).not.toThrow()
|
||||
})
|
||||
})
|
||||
|
@ -1,7 +1,5 @@
|
||||
const {CompositeDisposable, Disposable, Point, Range} = require('atom')
|
||||
const path = require('path')
|
||||
const fuzzaldrin = require('fuzzaldrin')
|
||||
const fuzzaldrinPlus = require('fuzzaldrin-plus')
|
||||
|
||||
const ProviderManager = require('./provider-manager')
|
||||
const SuggestionList = require('./suggestion-list')
|
||||
@ -190,7 +188,6 @@ class AutocompleteManager {
|
||||
this.subscriptions.add(atom.config.observe('autocomplete-plus.enableAutoActivation', (value) => { this.autoActivationEnabled = value }))
|
||||
this.subscriptions.add(atom.config.observe('autocomplete-plus.enableAutoConfirmSingleSuggestion', (value) => { this.autoConfirmSingleSuggestionEnabled = value }))
|
||||
this.subscriptions.add(atom.config.observe('autocomplete-plus.consumeSuffix', (value) => { this.consumeSuffix = value }))
|
||||
this.subscriptions.add(atom.config.observe('autocomplete-plus.useAlternateScoring', (value) => { this.useAlternateScoring = value }))
|
||||
this.subscriptions.add(atom.config.observe('autocomplete-plus.fileBlacklist', (value) => {
|
||||
if (value) {
|
||||
this.fileBlacklist = value.map((s) => { return s.trim() })
|
||||
@ -365,7 +362,6 @@ class AutocompleteManager {
|
||||
|
||||
filterSuggestions (suggestions, {prefix}) {
|
||||
const results = []
|
||||
const fuzzaldrinProvider = this.useAlternateScoring ? fuzzaldrinPlus : fuzzaldrin
|
||||
for (let i = 0; i < suggestions.length; i++) {
|
||||
// sortScore mostly preserves in the original sorting. The function is
|
||||
// chosen such that suggestions with a very high match score can break out.
|
||||
@ -382,7 +378,7 @@ class AutocompleteManager {
|
||||
results.push(suggestion)
|
||||
} else {
|
||||
const keepMatching = suggestion.ranges || suggestionPrefix[0].toLowerCase() === text[0].toLowerCase()
|
||||
if (keepMatching && (score = fuzzaldrinProvider.score(text, suggestionPrefix)) > 0) {
|
||||
if (keepMatching && (score = atom.ui.fuzzyMatcher.score(text, suggestionPrefix)) > 0) {
|
||||
suggestion.score = score * suggestion.sortScore
|
||||
results.push(suggestion)
|
||||
}
|
||||
|
@ -1,9 +1,6 @@
|
||||
const {CompositeDisposable} = require('atom')
|
||||
const SnippetParser = require('./snippet-parser')
|
||||
const {isString} = require('./type-helpers')
|
||||
const fuzzaldrinPlus = require('fuzzaldrin-plus')
|
||||
const {marked} = require('marked')
|
||||
const createDOMPurify = require('dompurify')
|
||||
|
||||
const createSuggestionFrag = () => {
|
||||
const frag = document.createDocumentFragment()
|
||||
@ -83,9 +80,6 @@ module.exports = class SuggestionListElement {
|
||||
this.subscriptions.add(atom.config.observe('autocomplete-plus.maxVisibleSuggestions', maxVisibleSuggestions => {
|
||||
this.maxVisibleSuggestions = maxVisibleSuggestions
|
||||
}))
|
||||
this.subscriptions.add(atom.config.observe('autocomplete-plus.useAlternateScoring', useAlternateScoring => {
|
||||
this.useAlternateScoring = useAlternateScoring
|
||||
}))
|
||||
this.subscriptions.add(atom.config.observe('autocomplete-plus.moveToCancel', moveToCancel => {
|
||||
this.moveToCancel = moveToCancel
|
||||
}))
|
||||
@ -137,15 +131,13 @@ module.exports = class SuggestionListElement {
|
||||
|
||||
if (item.descriptionMarkdown && item.descriptionMarkdown.length > 0) {
|
||||
this.descriptionContainer.style.display = 'block'
|
||||
this.descriptionContent.innerHTML = createDOMPurify().sanitize(
|
||||
marked(item.descriptionMarkdown, {
|
||||
gfm: true,
|
||||
this.descriptionContent.innerHTML = atom.ui.markdown.render(
|
||||
item.descriptionMarkdown,
|
||||
{
|
||||
breaks: true,
|
||||
sanitize: false,
|
||||
mangle: false,
|
||||
headerIds: false
|
||||
})
|
||||
)
|
||||
renderMode: "fragment"
|
||||
}
|
||||
);
|
||||
this.setDescriptionMoreLink(item)
|
||||
} else if (item.description && item.description.length > 0) {
|
||||
this.descriptionContainer.style.display = 'block'
|
||||
@ -544,12 +536,8 @@ module.exports = class SuggestionListElement {
|
||||
|
||||
if (!characterMatchIndices) {
|
||||
characterMatchIndices = this.findCharacterMatchIndices(replacementText, replacementPrefix)
|
||||
} else {
|
||||
characterMatchIndices = characterMatchIndices.reduce((matches, index) => {
|
||||
matches[index] = true
|
||||
return matches
|
||||
}, {})
|
||||
}
|
||||
characterMatchIndices = new Set(characterMatchIndices);
|
||||
|
||||
const appendNonMatchChars = (el, nonMatchChars) => {
|
||||
if (nonMatchChars) {
|
||||
@ -572,7 +560,7 @@ module.exports = class SuggestionListElement {
|
||||
workingEl = s
|
||||
}
|
||||
|
||||
if (characterMatchIndices && characterMatchIndices[index]) {
|
||||
if (characterMatchIndices && characterMatchIndices.has(index)) {
|
||||
appendNonMatchChars(workingEl, nonMatchChars)
|
||||
nonMatchChars = ''
|
||||
|
||||
@ -668,26 +656,11 @@ module.exports = class SuggestionListElement {
|
||||
//
|
||||
// Returns an {Object}
|
||||
findCharacterMatchIndices (text, replacementPrefix) {
|
||||
if (!text || !text.length || !replacementPrefix || !replacementPrefix.length) { return }
|
||||
if (!text?.length || !replacementPrefix?.length) { return }
|
||||
const matches = {}
|
||||
if (this.useAlternateScoring) {
|
||||
const matchIndices = fuzzaldrinPlus.match(text, replacementPrefix)
|
||||
for (const i of matchIndices) {
|
||||
matches[i] = true
|
||||
}
|
||||
} else {
|
||||
let wordIndex = 0
|
||||
for (let i = 0; i < replacementPrefix.length; i++) {
|
||||
const ch = replacementPrefix[i]
|
||||
while (wordIndex < text.length && text[wordIndex].toLowerCase() !== ch.toLowerCase()) {
|
||||
wordIndex += 1
|
||||
}
|
||||
if (wordIndex >= text.length) { break }
|
||||
matches[wordIndex] = true
|
||||
wordIndex += 1
|
||||
}
|
||||
}
|
||||
return matches
|
||||
return atom.ui.fuzzyMatcher.match(
|
||||
text, replacementPrefix, {recordMatchIndexes: true}
|
||||
)?.matchIndexes || [];
|
||||
}
|
||||
|
||||
dispose () {
|
||||
|
@ -10,11 +10,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"atom-slick": "^2.0.0",
|
||||
"dompurify": "^3.0.4",
|
||||
"fuzzaldrin": "^2.1.0",
|
||||
"fuzzaldrin-plus": "^0.6.0",
|
||||
"grim": "^2.0.1",
|
||||
"marked": "^5.1.1",
|
||||
"minimatch": "^3.0.3",
|
||||
"selector-kit": "^0.1.0",
|
||||
"stable": "^0.1.8",
|
||||
@ -200,12 +196,6 @@
|
||||
"default": true,
|
||||
"order": 18
|
||||
},
|
||||
"useAlternateScoring": {
|
||||
"description": "Prefers runs of consecutive characters, acronyms and start of words. (Experimental)",
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"order": 19
|
||||
},
|
||||
"useLocalityBonus": {
|
||||
"description": "Gives words near the cursor position a higher score than those far away",
|
||||
"type": "boolean",
|
||||
|
@ -1,6 +1,7 @@
|
||||
/* eslint-env jasmine */
|
||||
/* eslint-disable no-template-curly-in-string */
|
||||
const SuggestionListElement = require('../lib/suggestion-list-element')
|
||||
const { conditionPromise } = require('./spec-helper')
|
||||
|
||||
const fragmentToHtml = fragment => {
|
||||
const el = document.createElement('span')
|
||||
@ -63,11 +64,12 @@ describe('Suggestion List Element', () => {
|
||||
})
|
||||
|
||||
describe('itemChanged', () => {
|
||||
beforeEach(() => jasmine.attachToDOM(suggestionListElement.element))
|
||||
beforeEach(() => {
|
||||
jasmine.useRealClock()
|
||||
jasmine.attachToDOM(suggestionListElement.element)
|
||||
})
|
||||
|
||||
it('updates the list item', async () => {
|
||||
jasmine.useRealClock()
|
||||
|
||||
const suggestion = {text: 'foo'}
|
||||
const newSuggestion = {text: 'foo', description: 'foobar', rightLabel: 'foo'}
|
||||
suggestionListElement.model = {items: [newSuggestion]}
|
||||
@ -77,6 +79,9 @@ describe('Suggestion List Element', () => {
|
||||
|
||||
suggestionListElement.itemChanged({suggestion: newSuggestion, index: 0})
|
||||
|
||||
await conditionPromise(() =>
|
||||
suggestionListElement.element.querySelector('.right-label').innerText
|
||||
)
|
||||
expect(suggestionListElement.element.querySelector('.right-label').innerText)
|
||||
.toBe('foo')
|
||||
|
||||
@ -190,14 +195,15 @@ describe('Suggestion List Element', () => {
|
||||
let snippets = suggestionListElement.snippetParser.findSnippets(text)
|
||||
text = suggestionListElement.removeSnippetsFromText(snippets, text)
|
||||
let matches = suggestionListElement.findCharacterMatchIndices(text, replacementPrefix)
|
||||
matches = new Set(matches)
|
||||
|
||||
for (var i = 0; i <= text.length; i++) {
|
||||
if (truthyIndices.indexOf(i) !== -1) {
|
||||
expect(matches[i]).toBeTruthy()
|
||||
expect(matches.has(i)).toBeTruthy()
|
||||
} else {
|
||||
let m = matches
|
||||
if (m) {
|
||||
m = m[i]
|
||||
m = m.has(i)
|
||||
}
|
||||
expect(m).toBeFalsy()
|
||||
}
|
||||
@ -208,14 +214,14 @@ describe('Suggestion List Element', () => {
|
||||
assertMatches('hello', '', [])
|
||||
assertMatches('hello', 'h', [0])
|
||||
assertMatches('hello', 'hl', [0, 2])
|
||||
assertMatches('hello', 'hlo', [0, 2, 4])
|
||||
assertMatches('hello', 'hlo', [0, 3, 4])
|
||||
})
|
||||
|
||||
it('finds matches when snippets exist', () => {
|
||||
assertMatches('${0:hello}', '', [])
|
||||
assertMatches('${0:hello}', 'h', [0])
|
||||
assertMatches('${0:hello}', 'hl', [0, 2])
|
||||
assertMatches('${0:hello}', 'hlo', [0, 2, 4])
|
||||
assertMatches('${0:hello}', 'hlo', [0, 3, 4])
|
||||
assertMatches('${0:hello}world', '', [])
|
||||
assertMatches('${0:hello}world', 'h', [0])
|
||||
assertMatches('${0:hello}world', 'hw', [0, 5])
|
||||
|
@ -267,6 +267,12 @@
|
||||
&.syntax--raw {
|
||||
color: @green;
|
||||
}
|
||||
|
||||
// Horizontal rules in GFM used to be scoped as `comment.hr`. For continuity,
|
||||
// we assign the color of a comment to this new scope.
|
||||
&.syntax--horizontal-rule {
|
||||
color: @gray;
|
||||
}
|
||||
}
|
||||
|
||||
.syntax--source.syntax--gfm {
|
||||
|
@ -8,6 +8,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Color JSON keys differently from other strings.
|
||||
.syntax--meta.syntax--structure.syntax--key {
|
||||
.syntax--string.syntax--quoted.syntax--double {
|
||||
color: @red;
|
||||
}
|
||||
}
|
||||
|
||||
.syntax--meta.syntax--structure.syntax--dictionary.syntax--json, .syntax--meta.syntax--structure.syntax--array.syntax--json {
|
||||
& > .syntax--value.syntax--json > .syntax--string.syntax--quoted.syntax--json,
|
||||
& > .syntax--value.syntax--json > .syntax--string.syntax--quoted.syntax--json > .syntax--punctuation {
|
||||
|
@ -267,6 +267,12 @@
|
||||
&.syntax--raw {
|
||||
color: @green;
|
||||
}
|
||||
|
||||
// Horizontal rules in GFM used to be scoped as `comment.hr`. For continuity,
|
||||
// we assign the color of a comment to this new scope.
|
||||
&.syntax--horizontal-rule {
|
||||
color: @gray;
|
||||
}
|
||||
}
|
||||
|
||||
.syntax--source.syntax--gfm {
|
||||
|
@ -8,6 +8,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Color JSON keys differently from other strings.
|
||||
.syntax--meta.syntax--structure.syntax--key {
|
||||
.syntax--string.syntax--quoted.syntax--double {
|
||||
color: @red;
|
||||
}
|
||||
}
|
||||
|
||||
.syntax--meta.syntax--structure.syntax--dictionary.syntax--json, .syntax--meta.syntax--structure.syntax--array.syntax--json {
|
||||
& > .syntax--value.syntax--json > .syntax--string.syntax--quoted.syntax--json,
|
||||
& > .syntax--value.syntax--json > .syntax--string.syntax--quoted.syntax--json > .syntax--punctuation {
|
||||
|
10
packages/bookmarks/spec/.eslintrc.js
Normal file
10
packages/bookmarks/spec/.eslintrc.js
Normal file
@ -0,0 +1,10 @@
|
||||
module.exports = {
|
||||
env: { jasmine: true },
|
||||
rules: {
|
||||
"semi": ["error", "never"],
|
||||
"node/no-unpublished-require": "off",
|
||||
"node/no-extraneous-require": "off",
|
||||
"no-unused-vars": "off",
|
||||
"no-empty": "off"
|
||||
}
|
||||
}
|
@ -264,7 +264,7 @@ class BracketMatcherView {
|
||||
firstChild.childCount > 2 &&
|
||||
firstChild.firstChild.type === '<'
|
||||
) {
|
||||
if (lastChild === firstChild && firstChild.lastChild.type === '/>') {
|
||||
if (lastChild.id === firstChild.id && firstChild.lastChild.type === '/>') {
|
||||
startTag = firstChild
|
||||
endTag = firstChild
|
||||
} else if (
|
||||
|
12
packages/bracket-matcher/spec/.eslintrc.js
Normal file
12
packages/bracket-matcher/spec/.eslintrc.js
Normal file
@ -0,0 +1,12 @@
|
||||
module.exports = {
|
||||
env: { jasmine: true },
|
||||
globals: {
|
||||
waitsForPromise: true,
|
||||
},
|
||||
rules: {
|
||||
"node/no-unpublished-require": "off",
|
||||
"node/no-extraneous-require": "off",
|
||||
"no-unused-vars": "off",
|
||||
"no-empty": "off"
|
||||
}
|
||||
};
|
@ -3,8 +3,16 @@ const {Point, TextBuffer} = require('atom')
|
||||
const HAS_NEW_TEXT_BUFFER_VERSION = (new TextBuffer()).getLanguageMode().bufferDidFinishTransaction
|
||||
const path = require('path')
|
||||
|
||||
function editorReady (languageMode) {
|
||||
if (languageMode.atTransactionEnd) {
|
||||
return languageMode.atTransactionEnd()
|
||||
} else {
|
||||
return Promise.resolve()
|
||||
}
|
||||
}
|
||||
|
||||
describe('bracket matching', () => {
|
||||
let editorElement, editor, buffer
|
||||
let editorElement, editor, buffer, languageMode
|
||||
|
||||
beforeEach(() => {
|
||||
atom.config.set('bracket-matcher.autocompleteBrackets', true)
|
||||
@ -17,10 +25,17 @@ describe('bracket matching', () => {
|
||||
|
||||
waitsForPromise(() => atom.workspace.open(path.join(__dirname, 'fixtures', 'sample.js')))
|
||||
|
||||
waitsForPromise(() => {
|
||||
let editor = atom.workspace.getActiveTextEditor()
|
||||
let languageMode = editor.getBuffer().getLanguageMode()
|
||||
return languageMode.ready
|
||||
})
|
||||
|
||||
runs(() => {
|
||||
editor = atom.workspace.getActiveTextEditor()
|
||||
editorElement = atom.views.getView(editor)
|
||||
buffer = editor.getBuffer()
|
||||
languageMode = buffer.getLanguageMode()
|
||||
})
|
||||
})
|
||||
|
||||
@ -112,9 +127,10 @@ describe('bracket matching', () => {
|
||||
})
|
||||
|
||||
describe('when there are unpaired brackets', () => {
|
||||
it('highlights the correct start/end pairs', () => {
|
||||
it('highlights the correct start/end pairs', async () => {
|
||||
editor.setText('(()')
|
||||
editor.setCursorBufferPosition([0, 0])
|
||||
await editorReady(languageMode)
|
||||
expectNoHighlights()
|
||||
|
||||
editor.setCursorBufferPosition([0, 1])
|
||||
@ -125,6 +141,7 @@ describe('bracket matching', () => {
|
||||
|
||||
editor.setText(('())'))
|
||||
editor.setCursorBufferPosition([0, 0])
|
||||
await editorReady(languageMode)
|
||||
expectHighlights([0, 0], [0, 1])
|
||||
|
||||
editor.setCursorBufferPosition([0, 1])
|
||||
@ -139,9 +156,10 @@ describe('bracket matching', () => {
|
||||
})
|
||||
|
||||
describe('when there are commented brackets', () => {
|
||||
it('highlights the correct start/end pairs', () => {
|
||||
it('highlights the correct start/end pairs', async () => {
|
||||
editor.setText('(//)')
|
||||
editor.setCursorBufferPosition([0, 0])
|
||||
await editorReady(languageMode)
|
||||
expectNoHighlights()
|
||||
|
||||
editor.setCursorBufferPosition([0, 2])
|
||||
@ -152,6 +170,7 @@ describe('bracket matching', () => {
|
||||
|
||||
editor.setText('{/*}*/')
|
||||
editor.setCursorBufferPosition([0, 0])
|
||||
await editorReady(languageMode)
|
||||
expectNoHighlights()
|
||||
|
||||
editor.setCursorBufferPosition([0, 2])
|
||||
@ -162,6 +181,7 @@ describe('bracket matching', () => {
|
||||
|
||||
editor.setText('[/*]*/]')
|
||||
editor.setCursorBufferPosition([0, 0])
|
||||
await editorReady(languageMode)
|
||||
expectHighlights([0, 0], [0, 6])
|
||||
|
||||
editor.setCursorBufferPosition([0, 6])
|
||||
@ -173,9 +193,10 @@ describe('bracket matching', () => {
|
||||
})
|
||||
|
||||
describe('when there are quoted brackets', () => {
|
||||
it('highlights the correct start/end pairs', () => {
|
||||
it('highlights the correct start/end pairs', async () => {
|
||||
editor.setText("(')')")
|
||||
editor.setCursorBufferPosition([0, 0])
|
||||
await editorReady(languageMode)
|
||||
expectHighlights([0, 0], [0, 4])
|
||||
|
||||
editor.setCursorBufferPosition([0, 5])
|
||||
@ -186,6 +207,7 @@ describe('bracket matching', () => {
|
||||
|
||||
editor.setText('["]"]')
|
||||
editor.setCursorBufferPosition([0, 0])
|
||||
await editorReady(languageMode)
|
||||
expectHighlights([0, 0], [0, 4])
|
||||
|
||||
editor.setCursorBufferPosition([0, 5])
|
||||
@ -197,9 +219,10 @@ describe('bracket matching', () => {
|
||||
})
|
||||
|
||||
describe('when there are brackets inside code embedded in a string', () => {
|
||||
it('highlights the correct start/end pairs', () => {
|
||||
it('highlights the correct start/end pairs', async () => {
|
||||
editor.setText('(`${(1+1)}`)')
|
||||
editor.setCursorBufferPosition([0, 0])
|
||||
await editorReady(languageMode)
|
||||
expectHighlights([0, 0], [0, 11])
|
||||
|
||||
editor.setCursorBufferPosition([0, 12])
|
||||
@ -211,9 +234,10 @@ describe('bracket matching', () => {
|
||||
})
|
||||
|
||||
describe('when there are brackets inside a string inside code embedded in a string', () => {
|
||||
it('highlights the correct start/end pairs', () => {
|
||||
it('highlights the correct start/end pairs', async () => {
|
||||
editor.setText("(`${('(1+1)')}`)")
|
||||
editor.setCursorBufferPosition([0, 0])
|
||||
await editorReady(languageMode)
|
||||
expectHighlights([0, 0], [0, 15])
|
||||
|
||||
editor.setCursorBufferPosition([0, 16])
|
||||
@ -225,9 +249,10 @@ describe('bracket matching', () => {
|
||||
})
|
||||
|
||||
describe('when there are brackets in regular expressions', () => {
|
||||
it('highlights the correct start/end pairs', () => {
|
||||
it('highlights the correct start/end pairs', async () => {
|
||||
editor.setText('(/[)]/)')
|
||||
editor.setCursorBufferPosition([0, 0])
|
||||
await editorReady(languageMode)
|
||||
expectHighlights([0, 0], [0, 6])
|
||||
|
||||
editor.setCursorBufferPosition([0, 7])
|
||||
@ -239,9 +264,10 @@ describe('bracket matching', () => {
|
||||
})
|
||||
|
||||
describe('when the start character and end character of the pair are equivalent', () => {
|
||||
it('does not attempt to highlight pairs', () => {
|
||||
it('does not attempt to highlight pairs', async () => {
|
||||
editor.setText("'hello'")
|
||||
editor.setCursorBufferPosition([0, 0])
|
||||
await editorReady(languageMode)
|
||||
expectNoHighlights()
|
||||
})
|
||||
})
|
||||
@ -257,12 +283,13 @@ describe('bracket matching', () => {
|
||||
})
|
||||
|
||||
describe('when the pair moves', () => {
|
||||
it('repositions the highlights', () => {
|
||||
it('repositions the highlights', async () => {
|
||||
editor.moveToEndOfLine()
|
||||
editor.moveLeft()
|
||||
expectHighlights([0, 28], [12, 0])
|
||||
|
||||
editor.deleteToBeginningOfLine()
|
||||
await editorReady(languageMode)
|
||||
expectHighlights([0, 0], [12, 0])
|
||||
})
|
||||
})
|
||||
@ -313,21 +340,20 @@ describe('bracket matching', () => {
|
||||
|
||||
forEachLanguageWithTags(scopeName => {
|
||||
describe(`${scopeName} tag matching`, () => {
|
||||
beforeEach(() => {
|
||||
waitsForPromise(() => atom.packages.activatePackage('language-html'))
|
||||
waitsForPromise(() => atom.workspace.open(path.join(__dirname, 'fixtures', 'sample.xml')))
|
||||
|
||||
runs(() => {
|
||||
editor = atom.workspace.getActiveTextEditor()
|
||||
editorElement = atom.views.getView(editor)
|
||||
buffer = editor.buffer
|
||||
atom.grammars.assignLanguageMode(buffer, scopeName)
|
||||
buffer.getLanguageMode().syncOperationLimit = Infinity
|
||||
})
|
||||
beforeEach( async () => {
|
||||
await atom.packages.activatePackage('language-html')
|
||||
await atom.workspace.open(path.join(__dirname, 'fixtures', 'sample.xml'))
|
||||
editor = atom.workspace.getActiveTextEditor()
|
||||
editorElement = atom.views.getView(editor)
|
||||
buffer = editor.buffer
|
||||
atom.grammars.assignLanguageMode(buffer, scopeName)
|
||||
languageMode = buffer.getLanguageMode()
|
||||
languageMode.syncOperationLimit = Infinity
|
||||
await languageMode.ready
|
||||
})
|
||||
|
||||
describe('when on an opening tag', () => {
|
||||
it('highlights the opening and closing tag', () => {
|
||||
it('highlights the opening and closing tag', async () => {
|
||||
buffer.setText(`\
|
||||
<test>
|
||||
<test>text</test>
|
||||
@ -337,6 +363,7 @@ describe('bracket matching', () => {
|
||||
)
|
||||
|
||||
editor.setCursorBufferPosition([0, 0])
|
||||
await editorReady(languageMode)
|
||||
expectHighlights([0, 1], [3, 2])
|
||||
|
||||
editor.setCursorBufferPosition([0, 1])
|
||||
@ -345,7 +372,7 @@ describe('bracket matching', () => {
|
||||
})
|
||||
|
||||
describe('when on a closing tag', () => {
|
||||
it('highlights the opening and closing tag', () => {
|
||||
it('highlights the opening and closing tag', async () => {
|
||||
buffer.setText(`\
|
||||
<test>
|
||||
<!-- <test> -->
|
||||
@ -355,6 +382,7 @@ describe('bracket matching', () => {
|
||||
)
|
||||
|
||||
editor.setCursorBufferPosition([3, 0])
|
||||
await editorReady(languageMode)
|
||||
expectHighlights([3, 2], [0, 1])
|
||||
|
||||
editor.setCursorBufferPosition([3, 2])
|
||||
@ -369,13 +397,14 @@ describe('bracket matching', () => {
|
||||
)
|
||||
|
||||
editor.setCursorBufferPosition([1, Infinity])
|
||||
await editorReady(languageMode)
|
||||
expectHighlights([1, 14], [1, 3])
|
||||
|
||||
editor.setCursorBufferPosition([2, Infinity])
|
||||
expectHighlights([2, 14], [2, 3])
|
||||
})
|
||||
|
||||
it('highlights the correct opening tag, skipping self-closing tags', () => {
|
||||
it('highlights the correct opening tag, skipping self-closing tags', async () => {
|
||||
buffer.setText(`\
|
||||
<test>
|
||||
<test />
|
||||
@ -384,12 +413,13 @@ describe('bracket matching', () => {
|
||||
)
|
||||
|
||||
editor.setCursorBufferPosition([2, Infinity])
|
||||
await editorReady(languageMode)
|
||||
expectHighlights([2, 2], [0, 1])
|
||||
})
|
||||
})
|
||||
|
||||
describe('when on a self-closing tag', () => {
|
||||
it('highlights only the self-closing tag', () => {
|
||||
it('highlights only the self-closing tag', async () => {
|
||||
buffer.setText(`\
|
||||
<test>
|
||||
<test />
|
||||
@ -398,10 +428,11 @@ describe('bracket matching', () => {
|
||||
)
|
||||
|
||||
editor.setCursorBufferPosition([1, Infinity])
|
||||
await editorReady(languageMode)
|
||||
expectHighlights([1, 3], [1, 3])
|
||||
})
|
||||
|
||||
it('highlights a self-closing tag without a space', () => {
|
||||
it('highlights a self-closing tag without a space', async () => {
|
||||
buffer.setText(`\
|
||||
<test>
|
||||
<test/>
|
||||
@ -410,10 +441,11 @@ describe('bracket matching', () => {
|
||||
)
|
||||
|
||||
editor.setCursorBufferPosition([1, Infinity])
|
||||
await editorReady(languageMode)
|
||||
expectHighlights([1, 3], [1, 3])
|
||||
})
|
||||
|
||||
it('highlights a self-closing tag with many spaces', () => {
|
||||
it('highlights a self-closing tag with many spaces', async () => {
|
||||
buffer.setText(`\
|
||||
<test>
|
||||
<test />
|
||||
@ -422,10 +454,11 @@ describe('bracket matching', () => {
|
||||
)
|
||||
|
||||
editor.setCursorBufferPosition([1, Infinity])
|
||||
await editorReady(languageMode)
|
||||
expectHighlights([1, 3], [1, 3])
|
||||
})
|
||||
|
||||
it('does not catastrophically backtrack when many attributes are present (regression)', () => {
|
||||
it('does not catastrophically backtrack when many attributes are present (regression)', async () => {
|
||||
// https://github.com/atom/bracket-matcher/issues/303
|
||||
|
||||
buffer.setText(`\
|
||||
@ -438,6 +471,7 @@ describe('bracket matching', () => {
|
||||
)
|
||||
|
||||
editor.setCursorBufferPosition([0, 6])
|
||||
await editorReady(languageMode)
|
||||
expectHighlights([0, 1], [4, 2])
|
||||
|
||||
editor.setCursorBufferPosition([1, 6])
|
||||
@ -455,7 +489,7 @@ describe('bracket matching', () => {
|
||||
})
|
||||
|
||||
describe('when the tag spans multiple lines', () => {
|
||||
it('highlights the opening and closing tag', () => {
|
||||
it('highlights the opening and closing tag', async () => {
|
||||
buffer.setText(`\
|
||||
<div>
|
||||
<div class="test"
|
||||
@ -469,6 +503,7 @@ describe('bracket matching', () => {
|
||||
)
|
||||
|
||||
editor.setCursorBufferPosition([0, 1])
|
||||
await editorReady(languageMode)
|
||||
expectHighlights([0, 1], [6, 2])
|
||||
editor.setCursorBufferPosition([6, 2])
|
||||
expectHighlights([6, 2], [0, 1])
|
||||
@ -476,7 +511,7 @@ describe('bracket matching', () => {
|
||||
})
|
||||
|
||||
describe('when the tag has attributes', () => {
|
||||
it('highlights the opening and closing tags', () => {
|
||||
it('highlights the opening and closing tags', async () => {
|
||||
buffer.setText(`\
|
||||
<test a="test">
|
||||
text
|
||||
@ -485,6 +520,7 @@ describe('bracket matching', () => {
|
||||
)
|
||||
|
||||
editor.setCursorBufferPosition([2, 2])
|
||||
await editorReady(languageMode)
|
||||
expectHighlights([2, 2], [0, 1])
|
||||
|
||||
editor.setCursorBufferPosition([0, 7])
|
||||
@ -493,7 +529,7 @@ describe('bracket matching', () => {
|
||||
})
|
||||
|
||||
describe("when the tag has an attribute with a value of '/'", () => {
|
||||
it('highlights the opening and closing tags', () => {
|
||||
it('highlights the opening and closing tags', async () => {
|
||||
buffer.setText(`\
|
||||
<test a="/">
|
||||
text
|
||||
@ -502,6 +538,7 @@ describe('bracket matching', () => {
|
||||
)
|
||||
|
||||
editor.setCursorBufferPosition([2, 2])
|
||||
await editorReady(languageMode)
|
||||
expectHighlights([2, 2], [0, 1])
|
||||
|
||||
editor.setCursorBufferPosition([0, 7])
|
||||
@ -510,10 +547,11 @@ describe('bracket matching', () => {
|
||||
})
|
||||
|
||||
describe('when the opening and closing tags are on the same line', () => {
|
||||
it('highlight the opening and closing tags', () => {
|
||||
it('highlight the opening and closing tags', async () => {
|
||||
buffer.setText('<test>text</test>')
|
||||
|
||||
editor.setCursorBufferPosition([0, 2])
|
||||
await editorReady(languageMode)
|
||||
expectHighlights([0, 1], [0, 12])
|
||||
|
||||
editor.setCursorBufferPosition([0, 12])
|
||||
@ -522,17 +560,19 @@ describe('bracket matching', () => {
|
||||
})
|
||||
|
||||
describe('when the closing tag is missing', () => {
|
||||
it('does not highlight anything', () => {
|
||||
it('does not highlight anything', async () => {
|
||||
buffer.setText('<test>\ntext\n')
|
||||
editor.setCursorBufferPosition([0, 10])
|
||||
await editorReady(languageMode)
|
||||
expectNoHighlights()
|
||||
})
|
||||
})
|
||||
|
||||
describe('when between the opening and closing tag', () => {
|
||||
it('does not highlight anything', () => {
|
||||
it('does not highlight anything', async () => {
|
||||
buffer.setText('<div>\nhi\n</div>\n')
|
||||
editor.setCursorBufferPosition([1, 0])
|
||||
await editorReady(languageMode)
|
||||
expectNoHighlights()
|
||||
})
|
||||
})
|
||||
@ -986,14 +1026,16 @@ describe('bracket matching', () => {
|
||||
})
|
||||
|
||||
describe('matching bracket insertion', () => {
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
editor.buffer.setText('')
|
||||
await editorReady(languageMode)
|
||||
atom.config.set('editor.autoIndent', true)
|
||||
})
|
||||
|
||||
describe('when more than one character is inserted', () => {
|
||||
it('does not insert a matching bracket', () => {
|
||||
it('does not insert a matching bracket', async () => {
|
||||
editor.insertText('woah(')
|
||||
await editorReady(languageMode)
|
||||
expect(editor.buffer.getText()).toBe('woah(')
|
||||
})
|
||||
})
|
||||
@ -1467,18 +1509,23 @@ describe('bracket matching', () => {
|
||||
})
|
||||
|
||||
describe('when return is pressed inside a matching pair', () => {
|
||||
it('puts the cursor on the indented empty line', () => {
|
||||
it('puts the cursor on the indented empty line', async () => {
|
||||
editor.insertText('void main() ')
|
||||
editor.insertText('{')
|
||||
await editorReady(languageMode)
|
||||
expect(editor.getText()).toBe('void main() {}')
|
||||
editor.insertNewline()
|
||||
expect(editor.getCursorBufferPosition()).toEqual([1, 2])
|
||||
expect(buffer.lineForRow(1)).toBe(' ')
|
||||
expect(buffer.lineForRow(2)).toBe('}')
|
||||
|
||||
editor.setText(' void main() ')
|
||||
editor.setText('void main() ')
|
||||
editor.insertText('{')
|
||||
await editorReady(languageMode)
|
||||
expect(editor.getText()).toBe('void main() {}')
|
||||
editor.indentSelectedRows()
|
||||
expect(editor.getText()).toBe(' void main() {}')
|
||||
await editorReady(languageMode)
|
||||
editor.insertNewline()
|
||||
expect(editor.getCursorBufferPosition()).toEqual([1, 4])
|
||||
expect(buffer.lineForRow(1)).toBe(' ')
|
||||
@ -1514,13 +1561,15 @@ describe('bracket matching', () => {
|
||||
|
||||
describe('when in language specific scope', () => {
|
||||
describe('string interpolation', () => {
|
||||
beforeEach(() => {
|
||||
waitsForPromise(() => atom.packages.activatePackage('language-ruby'))
|
||||
|
||||
runs(() => buffer.setPath('foo.rb'))
|
||||
beforeEach(async () => {
|
||||
await atom.packages.activatePackage('language-ruby')
|
||||
buffer.setPath('foo.rb')
|
||||
let languageMode = buffer.getLanguageMode()
|
||||
languageMode.useAsyncParsing = false
|
||||
await languageMode.ready
|
||||
})
|
||||
|
||||
it('should insert curly braces inside doubly quoted string', () => {
|
||||
it('should insert curly braces inside double-quoted string', () => {
|
||||
editor.insertText('foo = ')
|
||||
editor.insertText('"')
|
||||
editor.insertText('#')
|
||||
@ -1529,7 +1578,7 @@ describe('bracket matching', () => {
|
||||
expect(editor.getText()).toBe('foo = ""')
|
||||
})
|
||||
|
||||
it('should not insert curly braces inside singly quoted string', () => {
|
||||
it('should not insert curly braces inside single-quoted string', () => {
|
||||
editor.insertText('foo = ')
|
||||
editor.insertText("'")
|
||||
editor.insertText('#')
|
||||
|
@ -15,9 +15,6 @@ class CommandPalettePackage {
|
||||
this.commandPaletteView.show(true)
|
||||
}
|
||||
}))
|
||||
this.disposables.add(atom.config.observe('command-palette.useAlternateScoring', (newValue) => {
|
||||
this.commandPaletteView.update({useAlternateScoring: newValue})
|
||||
}))
|
||||
this.disposables.add(atom.config.observe('command-palette.preserveLastSearch', (newValue) => {
|
||||
this.commandPaletteView.update({preserveLastSearch: newValue})
|
||||
}))
|
||||
|
@ -2,8 +2,6 @@
|
||||
|
||||
import SelectListView from 'atom-select-list'
|
||||
import {humanizeKeystroke} from 'underscore-plus'
|
||||
import fuzzaldrin from 'fuzzaldrin'
|
||||
import fuzzaldrinPlus from 'fuzzaldrin-plus'
|
||||
|
||||
export default class CommandPaletteView {
|
||||
constructor (initiallyVisibleItemCount = 10) {
|
||||
@ -55,7 +53,7 @@ export default class CommandPaletteView {
|
||||
|
||||
if (Array.isArray(item.tags)) {
|
||||
const matchingTags = item.tags
|
||||
.map(t => [t, this.fuzz.score(t, query)])
|
||||
.map(t => [t, atom.ui.fuzzyMatcher.score(t, query)])
|
||||
.filter(([t, s]) => s > 0)
|
||||
.sort((a, b) => a.s - b.s)
|
||||
.map(([t, s]) => t)
|
||||
@ -132,21 +130,14 @@ export default class CommandPaletteView {
|
||||
if (props.hasOwnProperty('preserveLastSearch')) {
|
||||
this.preserveLastSearch = props.preserveLastSearch
|
||||
}
|
||||
|
||||
if (props.hasOwnProperty('useAlternateScoring')) {
|
||||
this.useAlternateScoring = props.useAlternateScoring
|
||||
}
|
||||
}
|
||||
|
||||
get fuzz () {
|
||||
return this.useAlternateScoring ? fuzzaldrinPlus : fuzzaldrin
|
||||
}
|
||||
|
||||
highlightMatchesInElement (text, query, el) {
|
||||
const matches = this.fuzz.match(text, query)
|
||||
const matches = atom.ui.fuzzyMatcher.match(text, query, {recordMatchIndexes: true})
|
||||
let matchedChars = []
|
||||
let lastIndex = 0
|
||||
for (const matchIndex of matches) {
|
||||
let lastIndex = 0;
|
||||
const matchIndexes = matches ? (matches.matchIndexes ?? []) : []
|
||||
matchIndexes.forEach(matchIndex => {
|
||||
const unmatched = text.substring(lastIndex, matchIndex)
|
||||
if (unmatched) {
|
||||
if (matchedChars.length > 0) {
|
||||
@ -162,7 +153,7 @@ export default class CommandPaletteView {
|
||||
|
||||
matchedChars.push(text[matchIndex])
|
||||
lastIndex = matchIndex + 1
|
||||
}
|
||||
})
|
||||
|
||||
if (matchedChars.length > 0) {
|
||||
const matchSpan = document.createElement('span')
|
||||
@ -184,15 +175,15 @@ export default class CommandPaletteView {
|
||||
|
||||
const scoredItems = []
|
||||
for (const item of items) {
|
||||
let score = this.fuzz.score(item.displayName, query)
|
||||
let score = atom.ui.fuzzyMatcher.score(item.displayName, query)
|
||||
if (item.tags) {
|
||||
score += item.tags.reduce(
|
||||
(currentScore, tag) => currentScore + this.fuzz.score(tag, query),
|
||||
(currentScore, tag) => currentScore + atom.ui.fuzzyMatcher.score(tag, query),
|
||||
0
|
||||
)
|
||||
}
|
||||
if (item.description) {
|
||||
score += this.fuzz.score(item.description, query)
|
||||
score += atom.ui.fuzzyMatcher.score(item.description, query)
|
||||
}
|
||||
|
||||
if (score > 0) {
|
||||
|
@ -16,8 +16,6 @@
|
||||
"atomTestRunner": "atom-mocha-test-runner",
|
||||
"dependencies": {
|
||||
"atom-select-list": "^0.7.1",
|
||||
"fuzzaldrin": "^2.1.0",
|
||||
"fuzzaldrin-plus": "^0.6.0",
|
||||
"underscore-plus": "^1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@ -27,11 +25,6 @@
|
||||
"sinon": "^3.2.1"
|
||||
},
|
||||
"configSchema": {
|
||||
"useAlternateScoring": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Use an alternative scoring approach which prefers run of consecutive characters, acronyms and start of words."
|
||||
},
|
||||
"preserveLastSearch": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
|
@ -6,7 +6,6 @@ import { CompositeDisposable } from 'atom';
|
||||
import etch from 'etch';
|
||||
import fs from 'fs-plus';
|
||||
import Grim from 'grim';
|
||||
import { marked } from 'marked';
|
||||
import path from 'path';
|
||||
import { shell } from 'electron';
|
||||
|
||||
@ -138,7 +137,7 @@ export default class DeprecationCopView {
|
||||
<span className="text-warning icon icon-alert" />
|
||||
<div
|
||||
className="list-item deprecation-message"
|
||||
innerHTML={marked(deprecation.getMessage())}
|
||||
innerHTML={atom.ui.markdown.render(deprecation.getMessage())}
|
||||
/>
|
||||
{this.renderIssueURLIfNeeded(
|
||||
packageName,
|
||||
@ -218,7 +217,7 @@ export default class DeprecationCopView {
|
||||
<span className="text-warning icon icon-alert" />
|
||||
<div
|
||||
className="list-item deprecation-message"
|
||||
innerHTML={marked(deprecation.message)}
|
||||
innerHTML={atom.ui.markdown.render(deprecation.message)}
|
||||
/>
|
||||
{this.renderSelectorIssueURLIfNeeded(
|
||||
packageName,
|
||||
|
@ -12,7 +12,6 @@
|
||||
"etch": "0.9.0",
|
||||
"fs-plus": "^3.0.0",
|
||||
"grim": "^2.0.1",
|
||||
"marked": "5.0.3",
|
||||
"underscore-plus": "^1.7.0"
|
||||
},
|
||||
"consumedServices": {
|
||||
|
@ -36,7 +36,10 @@ showIf = (condition) ->
|
||||
else
|
||||
{display: 'none'}
|
||||
|
||||
capitalize = (str) -> str[0].toUpperCase() + str.toLowerCase().slice(1)
|
||||
capitalize = (str) ->
|
||||
return '' if str == ''
|
||||
str[0].toUpperCase() + str.toLowerCase().slice(1)
|
||||
|
||||
titleize = (str) -> str.toLowerCase().replace(/(?:^|\s)\S/g, (capital) -> capital.toUpperCase())
|
||||
|
||||
preserveCase = (text, reference) ->
|
||||
|
@ -139,7 +139,7 @@
|
||||
"preserveCaseOnReplace": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"title": "Preserve case during replace.",
|
||||
"title": "Preserve Case During Replace",
|
||||
"description": "Keep the replaced text case during replace: replacing 'user' with 'person' will replace 'User' with 'Person' and 'USER' with 'PERSON'."
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
const {Point, CompositeDisposable} = require('atom')
|
||||
const fs = require('fs')
|
||||
const NativeFuzzy = require('@pulsar-edit/fuzzy-native')
|
||||
|
||||
const path = require('path')
|
||||
const SelectListView = require('atom-select-list')
|
||||
@ -79,10 +78,12 @@ module.exports = class FuzzyFinderView {
|
||||
elementForItem: ({filePath, label, ownerGitHubUsername}) => {
|
||||
const filterQuery = this.selectListView.getFilterQuery()
|
||||
|
||||
this.nativeFuzzyForResults.setCandidates([0], [label])
|
||||
atom.ui.fuzzyMatcher.setCandidates(
|
||||
this.nativeFuzzyForResults, [label]
|
||||
);
|
||||
const items = this.nativeFuzzyForResults.match(
|
||||
filterQuery,
|
||||
{maxResults: 1, recordMatchIndexes: true}
|
||||
{maxResults: 1, recordMatchIndexes: true, algorithm: 'command-t'}
|
||||
)
|
||||
const matches = items.length ? items[0].matchIndexes : []
|
||||
const repository = repositoryForPath(filePath)
|
||||
@ -125,14 +126,13 @@ module.exports = class FuzzyFinderView {
|
||||
})
|
||||
|
||||
if (!this.nativeFuzzy) {
|
||||
this.nativeFuzzy = new NativeFuzzy.Matcher(
|
||||
indexArray(this.items.length),
|
||||
this.nativeFuzzy = atom.ui.fuzzyMatcher.setCandidates(
|
||||
this.items.map(el => el.label)
|
||||
)
|
||||
);
|
||||
// We need a separate instance of the fuzzy finder to calculate the
|
||||
// matched paths only for the returned results. This speeds up considerably
|
||||
// the filtering of items.
|
||||
this.nativeFuzzyForResults = new NativeFuzzy.Matcher([], [])
|
||||
this.nativeFuzzyForResults = atom.ui.fuzzyMatcher.setCandidates([]);
|
||||
}
|
||||
this.selectListView.update({ filter: this.filterFn })
|
||||
}
|
||||
@ -166,10 +166,14 @@ module.exports = class FuzzyFinderView {
|
||||
this.moveToCaretPosition(caretPosition)
|
||||
} else if (!uri) {
|
||||
this.cancel()
|
||||
} else if (fs.lstatSync(uri).isDirectory()) {
|
||||
this.selectListView.update({errorMessage: 'Selected path is a directory'})
|
||||
setTimeout(() => { this.selectListView.update({errorMessage: null}) }, 2000)
|
||||
} else {
|
||||
try {
|
||||
if (fs.lstatSync(uri).isDirectory()) {
|
||||
this.selectListView.update({errorMessage: 'Selected path is a directory'})
|
||||
setTimeout(() => { this.selectListView.update({errorMessage: null}) }, 2000)
|
||||
return
|
||||
}
|
||||
} catch (e) {}
|
||||
const caretPosition = this.getCaretPosition()
|
||||
this.cancel()
|
||||
this.openURI(uri, caretPosition, openOptions)
|
||||
@ -290,10 +294,10 @@ module.exports = class FuzzyFinderView {
|
||||
|
||||
setItems (items) {
|
||||
this.items = items
|
||||
this.nativeFuzzy.setCandidates(
|
||||
indexArray(this.items.length),
|
||||
atom.ui.fuzzyMatcher.setCandidates(
|
||||
this.nativeFuzzy,
|
||||
this.items.map(item => item.label)
|
||||
)
|
||||
);
|
||||
|
||||
if (this.isQueryALineJump()) {
|
||||
this.selectListView.update({
|
||||
@ -338,7 +342,7 @@ module.exports = class FuzzyFinderView {
|
||||
|
||||
filterFn(items, query) {
|
||||
if (!query) return items
|
||||
return this.nativeFuzzy.match(query, {maxResults: MAX_RESULTS})
|
||||
return this.nativeFuzzy.match(query, {maxResults: MAX_RESULTS, algorithm: 'command-t'})
|
||||
.map(({id}) => this.items[id])
|
||||
}
|
||||
}
|
||||
@ -382,14 +386,6 @@ function highlight (path, matches, offsetIndex) {
|
||||
return fragment
|
||||
}
|
||||
|
||||
function indexArray (length) {
|
||||
const array = []
|
||||
for (let i = 0; i < length; i++) {
|
||||
array[i] = i
|
||||
}
|
||||
return array
|
||||
}
|
||||
|
||||
class FuzzyFinderItem {
|
||||
constructor ({filePath, label, ownerGitHubUsername, filterQuery, matches, repository}) {
|
||||
this.filePath = filePath
|
||||
|
4555
packages/fuzzy-finder/package-lock.json
generated
4555
packages/fuzzy-finder/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -6,7 +6,6 @@
|
||||
"repository": "https://github.com/pulsar-edit/fuzzy-finder",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@pulsar-edit/fuzzy-native": "https://github.com/pulsar-edit/fuzzy-native.git#13d2e5f",
|
||||
"atom-select-list": "^0.7.0",
|
||||
"fs-plus": "^3.0.0",
|
||||
"minimatch": "~3.0.3",
|
||||
|
@ -4,6 +4,13 @@ module.exports = class GrammarListView {
|
||||
constructor() {
|
||||
this.autoDetect = { name: 'Auto Detect' };
|
||||
|
||||
this.configSubscription = atom.config.observe(
|
||||
'grammar-selector.hideDuplicateTextMateGrammars',
|
||||
(value) => {
|
||||
this.hideDuplicateGrammars = value
|
||||
}
|
||||
);
|
||||
|
||||
this.selectListView = new SelectListView({
|
||||
itemsClassList: ['mark-active'],
|
||||
items: [],
|
||||
@ -14,24 +21,20 @@ module.exports = class GrammarListView {
|
||||
if (grammar === this.currentGrammar) {
|
||||
element.classList.add('active');
|
||||
}
|
||||
element.classList.add('grammar-item');
|
||||
element.textContent = grammarName;
|
||||
element.dataset.grammar = grammarName;
|
||||
|
||||
const div = document.createElement('div');
|
||||
div.classList.add('pull-right');
|
||||
|
||||
if (isTreeSitter(grammar)) {
|
||||
if (!this.hideDuplicateGrammars) {
|
||||
// When we show all grammars, we should add a badge to each grammar
|
||||
// to distinguish them from one another in the list.
|
||||
const parser = document.createElement('span');
|
||||
|
||||
let badgeColor = 'badge-success';
|
||||
let badgeText = 'Tree-sitter';
|
||||
|
||||
if (isExperimentalTreeSitterMode()) {
|
||||
badgeColor = isModernTreeSitter(grammar) ?
|
||||
'badge-success' : 'badge-warning';
|
||||
badgeText = isModernTreeSitter(grammar) ?
|
||||
'Tree-sitter' : 'Legacy Tree-sitter';
|
||||
}
|
||||
let badgeText = getBadgeTextForGrammar(grammar);
|
||||
let badgeColor = getBadgeColorForGrammar(grammar);
|
||||
|
||||
parser.classList.add(
|
||||
'grammar-selector-parser',
|
||||
@ -39,10 +42,12 @@ module.exports = class GrammarListView {
|
||||
badgeColor
|
||||
);
|
||||
parser.textContent = badgeText;
|
||||
parser.setAttribute(
|
||||
'title',
|
||||
'(Recommended) A faster parser with improved syntax highlighting & code navigation support.'
|
||||
);
|
||||
if (isModernTreeSitter(grammar)) {
|
||||
parser.setAttribute(
|
||||
'title',
|
||||
'(Recommended) A faster parser with improved syntax highlighting & code navigation support.'
|
||||
);
|
||||
}
|
||||
div.appendChild(parser);
|
||||
}
|
||||
|
||||
@ -98,6 +103,16 @@ module.exports = class GrammarListView {
|
||||
this.selectListView.reset();
|
||||
}
|
||||
|
||||
getAllDisplayableGrammars() {
|
||||
let allGrammars = atom.grammars
|
||||
.getGrammars({ includeTreeSitter: true })
|
||||
.filter(grammar => {
|
||||
return grammar !== atom.grammars.nullGrammar && grammar.name;
|
||||
});
|
||||
|
||||
return allGrammars;
|
||||
}
|
||||
|
||||
async toggle() {
|
||||
if (this.panel != null) {
|
||||
this.cancel();
|
||||
@ -112,29 +127,7 @@ module.exports = class GrammarListView {
|
||||
this.currentGrammar = this.autoDetect;
|
||||
}
|
||||
|
||||
let grammars = atom.grammars
|
||||
.getGrammars({ includeTreeSitter: true })
|
||||
.filter(grammar => {
|
||||
return grammar !== atom.grammars.nullGrammar && grammar.name;
|
||||
});
|
||||
|
||||
// Don't show 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 => {
|
||||
if (isTreeSitter(grammar)) {
|
||||
blacklist.add(grammar.name);
|
||||
}
|
||||
});
|
||||
grammars = grammars.filter(
|
||||
grammar => isTreeSitter(grammar) || !blacklist.has(grammar.name)
|
||||
);
|
||||
}
|
||||
let grammars = this.getAllDisplayableGrammars();
|
||||
|
||||
grammars.sort((a, b) => {
|
||||
if (a.scopeName === 'text.plain') {
|
||||
@ -146,6 +139,20 @@ module.exports = class GrammarListView {
|
||||
}
|
||||
return a.name.localeCompare(b.name);
|
||||
});
|
||||
|
||||
if (this.hideDuplicateGrammars) {
|
||||
let displayedGrammars = [];
|
||||
let seenIds = new Set();
|
||||
|
||||
for (let grammar of grammars) {
|
||||
if (seenIds.has(grammar.scopeName)) continue;
|
||||
seenIds.add(grammar.scopeName);
|
||||
displayedGrammars.push(grammar);
|
||||
}
|
||||
|
||||
grammars = displayedGrammars;
|
||||
}
|
||||
|
||||
grammars.unshift(this.autoDetect);
|
||||
await this.selectListView.update({ items: grammars });
|
||||
this.attach();
|
||||
@ -153,26 +160,21 @@ module.exports = class GrammarListView {
|
||||
}
|
||||
};
|
||||
|
||||
// We look up global settings here, but it's just to determine the badge
|
||||
// colors. Otherwise we should be looking up these values in a scope-specific
|
||||
// manner.
|
||||
function getLanguageModeConfig() {
|
||||
let isTreeSitterMode = atom.config.get('core.useTreeSitterParsers');
|
||||
let isExperimental = atom.config.get('core.useExperimentalModernTreeSitter');
|
||||
let isLegacy = atom.config.get('core.useLegacyTreeSitter');
|
||||
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);
|
||||
return isLegacy ? 'node-tree-sitter' : 'web-tree-sitter';
|
||||
}
|
||||
|
||||
function isModernTreeSitter(grammar) {
|
||||
return grammar.constructor.name === 'WASMTreeSitterGrammar';
|
||||
}
|
||||
|
||||
function isOldTreeSitter(grammar) {
|
||||
function isLegacyTreeSitter(grammar) {
|
||||
return grammar.constructor.name === 'TreeSitterGrammar';
|
||||
}
|
||||
|
||||
@ -180,9 +182,69 @@ function compareGrammarType(a, b) {
|
||||
return getGrammarScore(a) - getGrammarScore(b);
|
||||
}
|
||||
|
||||
// Given a scope name, determines the user's preferred parser type for that
|
||||
// language.
|
||||
function getParserPreferenceForScopeName(scopeName) {
|
||||
let useTreeSitterParsers = atom.config.get(
|
||||
'core.useTreeSitterParsers',
|
||||
{ scope: [scopeName] }
|
||||
);
|
||||
let useLegacyTreeSitter = atom.config.get(
|
||||
'core.useLegacyTreeSitter',
|
||||
{ scope: [scopeName] }
|
||||
);
|
||||
|
||||
if (!useTreeSitterParsers) {
|
||||
return 'textmate';
|
||||
} else if (useLegacyTreeSitter) {
|
||||
return 'node-tree-sitter';
|
||||
} else {
|
||||
return 'web-tree-sitter';
|
||||
}
|
||||
}
|
||||
|
||||
function getBadgeTextForGrammar(grammar) {
|
||||
switch (grammar.constructor.name) {
|
||||
case 'Grammar':
|
||||
return 'TextMate';
|
||||
case 'WASMTreeSitterGrammar':
|
||||
return 'Modern Tree-sitter';
|
||||
case 'TreeSitterGrammar':
|
||||
return 'Legacy Tree-sitter';
|
||||
}
|
||||
}
|
||||
|
||||
const BADGE_COLORS_BY_LANGUAGE_MODE_CONFIG = {
|
||||
'textmate': {
|
||||
'Grammar': 'badge-success',
|
||||
'TreeSitterGrammar': 'badge-info',
|
||||
'WASMTreeSitterGrammar': 'badge-info'
|
||||
},
|
||||
'web-tree-sitter': {
|
||||
'WASMTreeSitterGrammar': 'badge-success',
|
||||
'TreeSitterGrammar': 'badge-warning',
|
||||
'Grammar': 'badge-info'
|
||||
},
|
||||
'node-tree-sitter': {
|
||||
'TreeSitterGrammar': 'badge-success',
|
||||
'WASMTreeSitterGrammar': 'badge-warning',
|
||||
'Grammar': 'badge-info'
|
||||
}
|
||||
};
|
||||
|
||||
function getBadgeColorForGrammar(grammar) {
|
||||
let languageModeConfig = getLanguageModeConfig();
|
||||
let classNameMap = BADGE_COLORS_BY_LANGUAGE_MODE_CONFIG[languageModeConfig];
|
||||
return classNameMap[grammar.constructor.name];
|
||||
}
|
||||
|
||||
function getGrammarScore(grammar) {
|
||||
let languageParser = getLanguageModeConfig();
|
||||
if (isModernTreeSitter(grammar)) { return -2; }
|
||||
if (isOldTreeSitter(grammar)) { return -1; }
|
||||
let languageParser = getParserPreferenceForScopeName(grammar.scopeName);
|
||||
if (isModernTreeSitter(grammar)) {
|
||||
return languageParser === 'node-tree-sitter' ? -1 : -2;
|
||||
}
|
||||
if (isLegacyTreeSitter(grammar)) {
|
||||
return languageParser === 'node-tree-sitter' ? -2 : -1;
|
||||
}
|
||||
return languageParser === 'textmate' ? -3 : 0;
|
||||
}
|
||||
|
@ -22,12 +22,13 @@
|
||||
"showOnRightSideOfStatusBar": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Show the active pane item's language on the right side of Pulsar's status bar, instead of the left."
|
||||
"description": "Show the active pane item’s language on the right side of Pulsar’s status bar, instead of the left."
|
||||
},
|
||||
"hideDuplicateTextMateGrammars": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Hides the TextMate grammar when there is an existing Tree-sitter grammar"
|
||||
"title": "Hide Duplicate Grammars",
|
||||
"description": "Hides non-preferred grammars when there is more than one grammar. When checked, whichever grammar is preferred for a given scope name (TextMate or Tree-sitter) will be the only one shown. When unchecked, all grammars will always be shown in the list, regardless of the user’s settings."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
8
packages/grammar-selector/spec/.eslintrc.js
Normal file
8
packages/grammar-selector/spec/.eslintrc.js
Normal file
@ -0,0 +1,8 @@
|
||||
module.exports = {
|
||||
env: { jasmine: true },
|
||||
rules: {
|
||||
"node/no-unpublished-require": "off",
|
||||
"no-unused-vars": "off",
|
||||
"no-empty": "off"
|
||||
}
|
||||
};
|
@ -3,9 +3,9 @@ const SelectListView = require('atom-select-list');
|
||||
|
||||
function setConfigForLanguageMode(mode) {
|
||||
let useTreeSitterParsers = mode !== 'textmate';
|
||||
let useExperimentalModernTreeSitter = mode === 'wasm-tree-sitter';
|
||||
let useLegacyTreeSitter = mode === 'node-tree-sitter';
|
||||
atom.config.set('core.useTreeSitterParsers', useTreeSitterParsers);
|
||||
atom.config.set('core.useExperimentalModernTreeSitter', useExperimentalModernTreeSitter);
|
||||
atom.config.set('core.useLegacyTreeSitter', useLegacyTreeSitter);
|
||||
}
|
||||
|
||||
describe('GrammarSelector', () => {
|
||||
@ -39,12 +39,14 @@ describe('GrammarSelector', () => {
|
||||
it('displays a list of all the available grammars', async () => {
|
||||
const grammarView = (await getGrammarView(editor)).element;
|
||||
|
||||
let allGrammars = atom.grammars
|
||||
.getGrammars({ includeTreeSitter: true })
|
||||
.filter(g => g.name)
|
||||
|
||||
// -1 for removing nullGrammar, +1 for adding "Auto Detect"
|
||||
// Tree-sitter names the regex and JSDoc grammars
|
||||
expect(grammarView.querySelectorAll('li').length).toBe(
|
||||
atom.grammars
|
||||
.getGrammars({ includeTreeSitter: true })
|
||||
.filter(g => g.name).length
|
||||
allGrammars.length
|
||||
);
|
||||
expect(grammarView.querySelectorAll('li')[0].textContent).toBe(
|
||||
'Auto Detect'
|
||||
@ -55,7 +57,9 @@ describe('GrammarSelector', () => {
|
||||
.forEach(li =>
|
||||
expect(li.textContent).not.toBe(atom.grammars.nullGrammar.name)
|
||||
);
|
||||
expect(grammarView.textContent.includes('Tree-sitter')).toBe(true); // check we are showing and labelling Tree-sitter grammars
|
||||
if (!atom.config.get('grammar-selector.hideDuplicateTextMateGrammars')) {
|
||||
expect(grammarView.textContent.includes('Tree-sitter')).toBe(true); // check we are showing and labelling Tree-sitter grammars
|
||||
}
|
||||
}));
|
||||
|
||||
describe('when a grammar is selected', () =>
|
||||
@ -204,6 +208,11 @@ describe('GrammarSelector', () => {
|
||||
}));
|
||||
|
||||
describe('when toggling hideDuplicateTextMateGrammars', () => {
|
||||
// For continuity reasons, the name of the setting won't be changed; but
|
||||
// this should now be construed as “hide duplicate grammars” — with the
|
||||
// grammar selector showing whatever the user-indicated preference is for
|
||||
// a given grammar.
|
||||
|
||||
it('shows only the Tree-sitter if true and both exist', async () => {
|
||||
// the main JS grammar has both a TextMate and Tree-sitter implementation
|
||||
atom.config.set('grammar-selector.hideDuplicateTextMateGrammars', true);
|
||||
@ -224,7 +233,7 @@ describe('GrammarSelector', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('shows both if false (in proper order when language parser is node-tree-sitter)', async () => {
|
||||
it('shows all three 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(
|
||||
@ -239,10 +248,14 @@ describe('GrammarSelector', () => {
|
||||
const grammar = listItems[i];
|
||||
const name = grammar.name;
|
||||
if (cppCount === 0 && name === 'C++') {
|
||||
// first C++ entry should be Tree-sitter
|
||||
// first C++ entry should be legacy Tree-sitter
|
||||
expect(grammar.constructor.name).toBe('TreeSitterGrammar');
|
||||
cppCount++;
|
||||
} else if (cppCount === 1) {
|
||||
// next C++ entry should be modern Tree-sitter
|
||||
expect(grammar.constructor.name).toBe('WASMTreeSitterGrammar');
|
||||
cppCount++;
|
||||
} else if (cppCount === 2) {
|
||||
// immediate next grammar should be the TextMate version
|
||||
expect(name).toBe('C++');
|
||||
expect(grammar.constructor.name).toBe('Grammar');
|
||||
@ -252,10 +265,44 @@ describe('GrammarSelector', () => {
|
||||
}
|
||||
}
|
||||
|
||||
expect(cppCount).toBe(2); // ensure we actually saw both grammars
|
||||
expect(cppCount).toBe(3); // ensure we actually saw all three grammars
|
||||
});
|
||||
|
||||
it('shows both if false (in proper order when language parser is textmate)', async () => {
|
||||
it('shows all three if false (in proper order when language parser is wasm-tree-sitter)', async () => {
|
||||
await atom.packages.activatePackage('language-c'); // punctuation making it sort wrong
|
||||
setConfigForLanguageMode('wasm-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 legacy Tree-sitter
|
||||
expect(grammar.constructor.name).toBe('WASMTreeSitterGrammar');
|
||||
cppCount++;
|
||||
} else if (cppCount === 1) {
|
||||
// next C++ entry should be modern Tree-sitter
|
||||
expect(grammar.constructor.name).toBe('TreeSitterGrammar');
|
||||
cppCount++;
|
||||
} else if (cppCount === 2) {
|
||||
// immediate next grammar should be the TextMate version
|
||||
expect(name).toBe('C++');
|
||||
expect(grammar.constructor.name).toBe('Grammar');
|
||||
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
|
||||
});
|
||||
|
||||
it('shows all three if false (in proper order when language parser is textmate)', async () => {
|
||||
await atom.packages.activatePackage('language-c'); // punctuation making it sort wrong
|
||||
atom.config.set(
|
||||
'grammar-selector.hideDuplicateTextMateGrammars',
|
||||
@ -269,11 +316,15 @@ describe('GrammarSelector', () => {
|
||||
const grammar = listItems[i];
|
||||
const name = grammar.name;
|
||||
if (cppCount === 0 && name === 'C++') {
|
||||
// first C++ entry should be TextMate
|
||||
// first C++ entry should be legacy Tree-sitter
|
||||
expect(grammar.constructor.name).toBe('Grammar');
|
||||
cppCount++;
|
||||
} else if (cppCount === 1) {
|
||||
// immediate next grammar should be the Tree-sitter version
|
||||
// next C++ entry should be modern Tree-sitter
|
||||
expect(grammar.constructor.name).toBe('WASMTreeSitterGrammar');
|
||||
cppCount++;
|
||||
} else if (cppCount === 2) {
|
||||
// immediate next grammar should be the TextMate version
|
||||
expect(name).toBe('C++');
|
||||
expect(grammar.constructor.name).toBe('TreeSitterGrammar');
|
||||
cppCount++;
|
||||
@ -282,7 +333,7 @@ describe('GrammarSelector', () => {
|
||||
}
|
||||
}
|
||||
|
||||
expect(cppCount).toBe(2); // ensure we actually saw both grammars
|
||||
expect(cppCount).toBe(3); // ensure we actually saw three grammars
|
||||
});
|
||||
|
||||
});
|
||||
@ -352,40 +403,85 @@ describe('GrammarSelector', () => {
|
||||
.forEach(li =>
|
||||
expect(li.textContent).not.toBe(atom.grammars.nullGrammar.name)
|
||||
);
|
||||
// Ensure we're showing and labelling tree-sitter grammars…
|
||||
if (!atom.config.get('grammar-selector.hideDuplicateTextMateGrammars')) {
|
||||
// Ensure we're showing and labelling tree-sitter grammars.
|
||||
expect(grammarView.textContent.includes('Tree-sitter')).toBe(true);
|
||||
// …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
|
||||
it('shows only the preferred if true and several exist (and preferred is default)', async () => {
|
||||
atom.config.set('grammar-selector.hideDuplicateTextMateGrammars', true);
|
||||
const grammarView = await getGrammarView(editor);
|
||||
const observedNames = new Map();
|
||||
// Show a maximum of two different kinds of grammar (both tree-sitter
|
||||
// variants).
|
||||
// Show a maximum of one grammar (the modern tree-sitter variant).
|
||||
grammarView.element.querySelectorAll('li').forEach(li => {
|
||||
const name = li.getAttribute('data-grammar');
|
||||
if (!observedNames.has(name)) {
|
||||
observedNames.set(name, 0);
|
||||
}
|
||||
observedNames.set(name, observedNames.get(name) + 1);
|
||||
expect(observedNames.get(name) < 3).toBe(true, `found ${observedNames.get(name)} of ${name}`);
|
||||
expect(observedNames.get(name) < 2).toBe(true, `found ${observedNames.get(name)} of ${name}`);
|
||||
});
|
||||
|
||||
// check the seen JS is actually the Tree-sitter one
|
||||
const list = atom.workspace.getModalPanels()[0].item;
|
||||
for (const item of list.items) {
|
||||
if (item.name === 'JavaScript') {
|
||||
expect(item.constructor.name.includes('TreeSitterGrammar'));
|
||||
expect(item.constructor.name).toBe('WASMTreeSitterGrammar');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('shows both if false', async () => {
|
||||
it('shows only the preferred if true and several exist (and preferred is node-tree-sitter)', async () => {
|
||||
atom.config.set('grammar-selector.hideDuplicateTextMateGrammars', true);
|
||||
setConfigForLanguageMode('node-tree-sitter', { scopeSelector: '.source.js' })
|
||||
const grammarView = await getGrammarView(editor);
|
||||
const observedNames = new Map();
|
||||
grammarView.element.querySelectorAll('li').forEach(li => {
|
||||
const name = li.getAttribute('data-grammar');
|
||||
if (!observedNames.has(name)) {
|
||||
observedNames.set(name, 0);
|
||||
}
|
||||
observedNames.set(name, observedNames.get(name) + 1);
|
||||
expect(observedNames.get(name) < 2).toBe(true, `found ${observedNames.get(name)} of ${name}`);
|
||||
});
|
||||
|
||||
// check the seen JS is actually the Tree-sitter one
|
||||
const list = atom.workspace.getModalPanels()[0].item;
|
||||
for (const item of list.items) {
|
||||
if (item.name === 'JavaScript') {
|
||||
expect(item.constructor.name).toBe('TreeSitterGrammar');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('shows only the preferred if true and several exist (and preferred is textmate)', async () => {
|
||||
atom.config.set('grammar-selector.hideDuplicateTextMateGrammars', true);
|
||||
setConfigForLanguageMode('textmate', { scopeSelector: '.source.js' })
|
||||
const grammarView = await getGrammarView(editor);
|
||||
const observedNames = new Map();
|
||||
grammarView.element.querySelectorAll('li').forEach(li => {
|
||||
const name = li.getAttribute('data-grammar');
|
||||
if (!observedNames.has(name)) {
|
||||
observedNames.set(name, 0);
|
||||
}
|
||||
observedNames.set(name, observedNames.get(name) + 1);
|
||||
expect(observedNames.get(name) < 2).toBe(true, `found ${observedNames.get(name)} of ${name}`);
|
||||
});
|
||||
|
||||
// check the seen JS is actually the Tree-sitter one
|
||||
const list = atom.workspace.getModalPanels()[0].item;
|
||||
for (const item of list.items) {
|
||||
if (item.name === 'JavaScript') {
|
||||
expect(item.constructor.name).toBe('Grammar');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('shows three if false (in the proper order when language parser is web-tree-sitter)', async () => {
|
||||
await atom.packages.activatePackage('language-c'); // punctuation making it sort wrong
|
||||
atom.config.set(
|
||||
'grammar-selector.hideDuplicateTextMateGrammars',
|
||||
@ -403,14 +499,14 @@ describe('GrammarSelector', () => {
|
||||
cppCount++;
|
||||
} else if (cppCount === 1) {
|
||||
expect(name).toBe('C++');
|
||||
expect(grammar.constructor.name).toBe('TreeSitterGrammar'); // second C++ entry should be Legacy Tree-sitter
|
||||
expect(grammar.constructor.name).toBe('TreeSitterGrammar'); // first C++ entry should be Modern Tree-sitter
|
||||
cppCount++;
|
||||
} else if (cppCount === 2) {
|
||||
expect(name).toBe('C++');
|
||||
expect(grammar.constructor.name).toBe('Grammar'); // immediate next grammar should be the TextMate version
|
||||
cppCount++;
|
||||
} else {
|
||||
expect(name).not.toBe('C++'); // there should not be any other C++ grammars
|
||||
expect(name).not.toBe('C++');
|
||||
}
|
||||
}
|
||||
|
||||
@ -420,6 +516,10 @@ describe('GrammarSelector', () => {
|
||||
|
||||
describe('for every Tree-sitter grammar', () => {
|
||||
it('adds a label to identify it as Tree-sitter', async () => {
|
||||
atom.config.set(
|
||||
'grammar-selector.hideDuplicateTextMateGrammars',
|
||||
false
|
||||
);
|
||||
const grammarView = await getGrammarView(editor);
|
||||
const elements = grammarView.element.querySelectorAll('li');
|
||||
const listItems = atom.workspace.getModalPanels()[0].item.items;
|
||||
@ -448,6 +548,164 @@ describe('GrammarSelector', () => {
|
||||
|
||||
});
|
||||
|
||||
// We will not need these tests for long.
|
||||
describe('when language parser is "node-tree-sitter"', () => {
|
||||
beforeEach(() => {
|
||||
setConfigForLanguageMode('node-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 also 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 several 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();
|
||||
|
||||
grammarView.element.querySelectorAll('li').forEach(li => {
|
||||
const name = li.getAttribute('data-grammar');
|
||||
if (!observedNames.has(name)) {
|
||||
observedNames.set(name, 0);
|
||||
}
|
||||
observedNames.set(name, observedNames.get(name) + 1);
|
||||
expect(observedNames.get(name) < 2).toBe(true, `found ${observedNames.get(name)} of ${name}`);
|
||||
});
|
||||
|
||||
// check the seen JS is actually the Tree-sitter one
|
||||
const list = atom.workspace.getModalPanels()[0].item;
|
||||
for (const item of list.items) {
|
||||
if (item.name === 'JavaScript') {
|
||||
expect(item.constructor.name).toBe('TreeSitterGrammar');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('shows all if false', async () => {
|
||||
await atom.packages.activatePackage('language-c');
|
||||
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('TreeSitterGrammar'); // first C++ entry should be Modern Tree-sitter
|
||||
cppCount++;
|
||||
} else if (cppCount === 1) {
|
||||
expect(grammar.constructor.name).toBe('WASMTreeSitterGrammar'); // next 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 (when showing duplicate grammars)', async () => {
|
||||
atom.config.set(
|
||||
'grammar-selector.hideDuplicateTextMateGrammars',
|
||||
false
|
||||
);
|
||||
const grammarView = await getGrammarView(editor);
|
||||
const elements = grammarView.element.querySelectorAll('li');
|
||||
const listItems = atom.workspace.getModalPanels()[0].item.items;
|
||||
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-success')
|
||||
).toBe(true);
|
||||
} else if (item.constructor.name === 'WASMTreeSitterGrammar') {
|
||||
expect(
|
||||
element.childNodes[1].childNodes[0].classList.contains('badge-warning')
|
||||
).toBe(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('does not add a label to identify it as Tree-sitter (when hiding duplicate grammars)', async () => {
|
||||
atom.config.set(
|
||||
'grammar-selector.hideDuplicateTextMateGrammars',
|
||||
true
|
||||
);
|
||||
const grammarView = await getGrammarView(editor);
|
||||
const elements = grammarView.element.querySelectorAll('li');
|
||||
const listItems = atom.workspace.getModalPanels()[0].item.items;
|
||||
for (let i = 0; i < listItems.length; i++) {
|
||||
let item = listItems[i];
|
||||
let element = elements[i];
|
||||
if (item.constructor.name.includes('TreeSitterGrammar')) {
|
||||
expect(
|
||||
element.childNodes[1].childNodes[0].className.startsWith(
|
||||
'grammar-selector-parser'
|
||||
)
|
||||
).toBe(false);
|
||||
}
|
||||
if (item.constructor.name === 'TreeSitterGrammar') {
|
||||
expect(
|
||||
element.childNodes[1].childNodes[0].classList.contains('badge-success')
|
||||
).toBe(false);
|
||||
} else if (item.constructor.name === 'WASMTreeSitterGrammar') {
|
||||
expect(
|
||||
element.childNodes[1].childNodes[0].classList.contains('badge-warning')
|
||||
).toBe(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
function getTooltipText(element) {
|
||||
|
@ -1,5 +1,13 @@
|
||||
@import "ui-variables";
|
||||
|
||||
.grammar-item {
|
||||
&:after {
|
||||
content: "";
|
||||
display: table;
|
||||
clear: both;
|
||||
}
|
||||
}
|
||||
|
||||
.grammar-status a,
|
||||
.grammar-status a:hover {
|
||||
color: @text-color;
|
||||
|
@ -3,9 +3,11 @@ scopeName: 'source.c'
|
||||
type: 'modern-tree-sitter'
|
||||
parser: 'tree-sitter-c'
|
||||
|
||||
firstLineRegex: '-\\*-[^*]*(Mode:\\s*)?C(\\s*;.*?)?\\s*-\\*-'
|
||||
injectionRegex: '^(c|C)$'
|
||||
|
||||
treeSitter:
|
||||
parserSource: 'github:tree-sitter/tree-sitter-c#212a80f86452bb1316324fa0db730cf52f29e05a'
|
||||
grammar: 'tree-sitter-c/tree-sitter-c.wasm'
|
||||
highlightsQuery: 'tree-sitter-c/highlights.scm'
|
||||
tagsQuery: 'tree-sitter-c/tags.scm'
|
||||
@ -17,3 +19,6 @@ fileTypes: [
|
||||
'c'
|
||||
'h.in'
|
||||
]
|
||||
|
||||
comments:
|
||||
start: '// '
|
||||
|
@ -6,6 +6,7 @@ parser: 'tree-sitter-cpp'
|
||||
injectionRegex: '^(c|C)(\\+\\+|pp|PP)$'
|
||||
|
||||
treeSitter:
|
||||
parserSource: 'github:tree-sitter/tree-sitter-cpp#a71474021410973b29bfe99440d57bcd750246b1'
|
||||
grammar: 'tree-sitter-cpp/tree-sitter-cpp.wasm'
|
||||
highlightsQuery: 'tree-sitter-cpp/highlights.scm'
|
||||
tagsQuery: 'tree-sitter-cpp/tags.scm'
|
||||
@ -33,3 +34,6 @@ fileTypes: [
|
||||
]
|
||||
|
||||
contentRegex: '\n\\s*(namespace|class|template)\\s+'
|
||||
|
||||
comments:
|
||||
start: '// '
|
||||
|
@ -3,6 +3,7 @@ scopeName: 'source.c'
|
||||
type: 'tree-sitter'
|
||||
parser: 'tree-sitter-c'
|
||||
|
||||
firstLineRegex: '-\\*-[^*]*(Mode:\\s*)?C(\\s*;.*?)?\\s*-\\*-'
|
||||
injectionRegex: 'c|C'
|
||||
|
||||
fileTypes: [
|
||||
|
@ -13,20 +13,51 @@
|
||||
"#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.
|
||||
(["#if" "#ifdef" "#ifndef" "#endif" "#elif" "#else" "#define" "#include"] @punctuation.definition.directive.c
|
||||
(#set! adjust.endAfterFirstMatchOf "^#"))
|
||||
|
||||
; `preproc_directive` will be used when the parser doesn't recognize the
|
||||
; directive as one of the above. It's permissive; `#afdfafsdfdfad` would be
|
||||
; parsed as a `preproc_directive`.
|
||||
;
|
||||
; Hence this rule will match if the more specific rules above haven't matched.
|
||||
; The anonymous nodes will match under ideal conditions, but might not be
|
||||
; present even when they ought to be _if_ the parser is flummoxed; so this'll
|
||||
; sometimes catch `#ifdef` and others.
|
||||
((preproc_directive) @keyword.control.directive.c
|
||||
(#set! capture.shy true))
|
||||
|
||||
((preproc_ifdef
|
||||
(identifier) @entity.name.function.preprocessor.c
|
||||
(#match? @entity.name.function.preprocessor.c "[a-zA-Z_$][\\w$]*")))
|
||||
((preproc_directive) @punctuation.definition.directive.c
|
||||
(#set! capture.shy true)
|
||||
(#set! adjust.endAfterFirstMatchOf "^#"))
|
||||
|
||||
; Macro functions are definitely entities.
|
||||
(preproc_function_def
|
||||
(identifier) @entity.name.function.preprocessor.c
|
||||
(#set! capture.final true))
|
||||
|
||||
; Identifiers in macro definitions are definitely constants.
|
||||
((preproc_def
|
||||
name: (identifier) @constant.preprocessor.c))
|
||||
|
||||
; We can also safely treat identifiers as constants in `#ifdef`…
|
||||
((preproc_ifdef
|
||||
(identifier) @constant.preprocessor.c))
|
||||
|
||||
; …and `#if` and `#elif`…
|
||||
(preproc_if
|
||||
(binary_expression
|
||||
(identifier) @constant.preprocessor.c))
|
||||
(preproc_elif
|
||||
(binary_expression
|
||||
(identifier) @constant.preprocessor.c))
|
||||
|
||||
; …and `#undef`.
|
||||
((preproc_call
|
||||
directive: (preproc_directive) @_IGNORE_
|
||||
argument: (preproc_arg) @constant.preprocessor.c)
|
||||
(#eq? @_IGNORE_ "#undef"))
|
||||
|
||||
(system_lib_string) @string.quoted.other.lt-gt.include.c
|
||||
((system_lib_string) @punctuation.definition.string.begin.c
|
||||
(#set! adjust.endAfterFirstMatchOf "^<"))
|
||||
@ -43,30 +74,50 @@
|
||||
(type_identifier) @_IGNORE_
|
||||
(#set! capture.final true))
|
||||
|
||||
(primitive_type) @support.type.builtin.c
|
||||
(type_identifier) @support.type.other.c
|
||||
(primitive_type) @support.storage.type.builtin.c
|
||||
|
||||
; When the user has typed `#define FOO`, the macro injection thinks that `FOO`
|
||||
; is a type declaration (for some reason). This node structure seems to exist
|
||||
; only in that unusual and incorrect scenario, so we'll stop it from happening
|
||||
; so that it doesn't override the underlying `constant.other.c` scope.
|
||||
(translation_unit
|
||||
(type_identifier) @_IGNORE_
|
||||
(#set! capture.final))
|
||||
|
||||
(type_identifier) @support.other.storage.type.c
|
||||
|
||||
; These types are all reserved words; if we see an identifier with this name,
|
||||
; it must be a type.
|
||||
((identifier) @support.type.builtin.c
|
||||
(#match? @support.type.builtin.c "^(char|int|float|double|long)$"))
|
||||
((identifier) @support.storage.type.builtin.c
|
||||
(#match? @support.storage.type.builtin.c "^(char|int|float|double|long)$"))
|
||||
|
||||
; Assume any identifier that ends in `_t` is a type. This convention is not
|
||||
; always followed, but it's a very strong indicator when it's present.
|
||||
((identifier) @support.type.other.c
|
||||
(#match? @support.type.other.c "_t$"))
|
||||
((identifier) @support.other.storage.type.c
|
||||
(#match? @support.other.storage.type.c "_t$"))
|
||||
|
||||
; These refer to language constructs and remain in the `storage` namespace.
|
||||
[
|
||||
"enum"
|
||||
"long"
|
||||
"short"
|
||||
"signed"
|
||||
"struct"
|
||||
"typedef"
|
||||
"union"
|
||||
"unsigned"
|
||||
] @storage.type.c
|
||||
|
||||
; These refer to value types and go under `support`.
|
||||
[
|
||||
"long"
|
||||
"short"
|
||||
] @support.storage.type.builtin.c
|
||||
|
||||
; These act as modifiers to value types and also go under `support`.
|
||||
[
|
||||
"signed"
|
||||
"unsigned"
|
||||
] @support.storage.modifier.builtin.c
|
||||
|
||||
; These act as general language modifiers and remain in the `storage`
|
||||
; namespace.
|
||||
[
|
||||
"const"
|
||||
"extern"
|
||||
@ -75,10 +126,10 @@
|
||||
"restrict"
|
||||
"static"
|
||||
"volatile"
|
||||
] @storage.modifier.c
|
||||
] @storage.modifier._TYPE_.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)$"))
|
||||
((primitive_type) @support.storage.type.stdint.c
|
||||
(#match? @support.storage.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)$"))
|
||||
|
||||
(enum_specifier
|
||||
name: (type_identifier) @variable.other.declaration.type.c)
|
||||
@ -116,32 +167,69 @@
|
||||
; Declarations and assignments
|
||||
; ----------------------------
|
||||
|
||||
; The "x" in `int x`;
|
||||
; The "x" in `int x;`
|
||||
(declaration
|
||||
declarator: (identifier) @variable.declaration.c)
|
||||
declarator: (identifier) @variable.other.declaration.c)
|
||||
|
||||
; The "x" in `int x = y`;
|
||||
; The "x" in `int x = y;`
|
||||
(init_declarator
|
||||
declarator: (identifier) @variable.declaration.c)
|
||||
declarator: (identifier) @variable.other.declaration.c)
|
||||
|
||||
; The "x" in `SomeType *x;`
|
||||
; (Should work no matter how many pointers deep we are.)
|
||||
(pointer_declarator
|
||||
declarator: [(identifier) (field_identifier)] @variable.other.declaration.pointer.c
|
||||
(#is? test.descendantOfType "declaration field_declaration"))
|
||||
|
||||
; An array declarator: the "table" in `int table[4];`
|
||||
(array_declarator
|
||||
declarator: (identifier) @variable.other.declaration.c)
|
||||
|
||||
; A member of a struct.
|
||||
(field_declaration
|
||||
(field_identifier) @entity.other.attribute-name.c)
|
||||
(field_identifier) @variable.other.declaration.member.c)
|
||||
|
||||
; An attribute in a C99 struct designated initializer:
|
||||
; the "foo" in `MY_TYPE a = { .foo = true };
|
||||
(initializer_pair
|
||||
(field_designator
|
||||
(field_identifier) @variable.other.declaration.member.c))
|
||||
|
||||
; (and the associated ".")
|
||||
(initializer_pair
|
||||
(field_designator
|
||||
"." @keyword.operator.accessor.c))
|
||||
|
||||
(field_declaration
|
||||
(pointer_declarator
|
||||
(field_identifier) @entity.other.attribute-name.c))
|
||||
(field_identifier) @variable.other.declaration.member.c))
|
||||
|
||||
(field_declaration
|
||||
(array_declarator
|
||||
(field_identifier) @entity.other.attribute-name.c))
|
||||
(field_identifier) @variable.other.declaration.member.c))
|
||||
|
||||
(init_declarator
|
||||
(pointer_declarator
|
||||
(identifier) @entity.other.attribute-name.c))
|
||||
(identifier) @variable.other.declaration.member.c))
|
||||
|
||||
; The "x" in `x = y;`
|
||||
(assignment_expression
|
||||
left: (identifier) @variable.other.assignment.c)
|
||||
|
||||
; The "foo" in `something->foo = "bar";`
|
||||
(assignment_expression
|
||||
left: (field_expression
|
||||
field: (field_identifier) @variable.other.member.assignment.c)
|
||||
(#set! capture.final))
|
||||
|
||||
; Goto label definitions: the "foo" in `foo:` before a statement.
|
||||
(labeled_statement
|
||||
label: (statement_identifier) @entity.name.label.c)
|
||||
|
||||
; Goto statements — the "foo" in `goto foo;`
|
||||
(goto_statement
|
||||
label: (statement_identifier) @support.other.label.c)
|
||||
|
||||
|
||||
; Function parameters
|
||||
; -------------------
|
||||
@ -154,9 +242,10 @@
|
||||
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))
|
||||
; (Should work no matter how many pointers deep we are.)
|
||||
(pointer_declarator
|
||||
declarator: [(identifier) (field_identifier)] @variable.parameter.pointer.c
|
||||
(#is? test.descendantOfType "parameter_declaration"))
|
||||
|
||||
; The "foo" in `const char foo[]` within a parameter list.
|
||||
(parameter_declaration
|
||||
@ -172,7 +261,13 @@
|
||||
; The "size" in `finfo->size`.
|
||||
(field_expression
|
||||
"->"
|
||||
field: (field_identifier) @support.other.property.c)
|
||||
field: (field_identifier) @variable.other.member.c)
|
||||
|
||||
; The "bar" in `foo.bar`.
|
||||
(field_expression
|
||||
operator: "."
|
||||
field: (field_identifier) @variable.other.member.c)
|
||||
|
||||
|
||||
|
||||
; FUNCTIONS
|
||||
@ -212,8 +307,19 @@
|
||||
(false)
|
||||
] @constant.language._TYPE_.c
|
||||
|
||||
((identifier) @constant.c
|
||||
(#match? @constant.c "[_A-Z][_A-Z0-9]*$"))
|
||||
; Don't try to scope (e.g.) `int FOO = 1` as a constant when the user types `=`
|
||||
; but has not typed the value yet.
|
||||
(ERROR
|
||||
(identifier) @_IGNORE_
|
||||
(#set! capture.final))
|
||||
|
||||
; In most languages we wouldn't be making the assumption that an all-caps
|
||||
; identifier should be treated as a constant. But those languages don't have
|
||||
; macro preprocessors. The convention is decently strong in C/C++ that all-caps
|
||||
; identifiers will refer to `#define`d things.
|
||||
((identifier) @constant.other.c
|
||||
(#match? @constant.other.c "^[_A-Z][_A-Z0-9]*$")
|
||||
(#set! capture.shy))
|
||||
|
||||
|
||||
; COMMENTS
|
||||
@ -309,8 +415,10 @@
|
||||
|
||||
";" @punctuation.terminator.statement.c
|
||||
|
||||
"," @punctuation.separator.comma.c
|
||||
"->" @punctuation.separator.pointer-access.c
|
||||
("," @punctuation.separator.comma.c
|
||||
(#set! capture.shy))
|
||||
("->" @keyword.operator.accessor.pointer-access.c
|
||||
(#set! capture.shy))
|
||||
|
||||
(parameter_list
|
||||
"(" @punctuation.definition.parameters.begin.bracket.round.c
|
||||
@ -335,6 +443,22 @@
|
||||
"[" @punctuation.definition.array.begin.bracket.square.c
|
||||
"]" @punctuation.definition.array.end.bracket.square.c
|
||||
|
||||
|
||||
; META
|
||||
; ====
|
||||
|
||||
((compound_statement) @meta.block.c
|
||||
(#set! adjust.startAt firstChild.endPosition)
|
||||
(#set! adjust.endAt lastChild.startPosition))
|
||||
|
||||
((enumerator_list) @meta.block.enum.c
|
||||
(#set! adjust.startAt firstChild.endPosition)
|
||||
(#set! adjust.endAt lastChild.startPosition))
|
||||
|
||||
((field_declaration_list) @meta.block.field.c
|
||||
(#set! adjust.startAt firstChild.endPosition)
|
||||
(#set! adjust.endAt lastChild.startPosition))
|
||||
|
||||
; TODO:
|
||||
;
|
||||
; * TM-style grammar has a lot of `mac-classic` scopes. I doubt they'd be
|
||||
|
Binary file not shown.
@ -13,29 +13,55 @@
|
||||
"#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
|
||||
(["#if" "#ifdef" "#ifndef" "#endif" "#elif" "#else" "#define" "#include"] @punctuation.definition.directive.cpp
|
||||
(#set! adjust.endAfterFirstMatchOf "^#"))
|
||||
|
||||
; `preproc_directive` will be used when the parser doesn't recognize the
|
||||
; directive as one of the above. It's permissive; `#afdfafsdfdfad` would be
|
||||
; parsed as a `preproc_directive`.
|
||||
;
|
||||
; Hence this rule will match if the more specific rules above haven't matched.
|
||||
; The anonymous nodes will match under ideal conditions, but might not be
|
||||
; present even when they ought to be _if_ the parser is flummoxed; so this'll
|
||||
; sometimes catch `#ifdef` and others.
|
||||
((preproc_directive) @keyword.control.directive.cpp
|
||||
(#set! capture.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! capture.final true))
|
||||
((preproc_directive) @punctuation.definition.directive.cpp
|
||||
(#set! capture.shy true)
|
||||
(#set! adjust.endAfterFirstMatchOf "^#"))
|
||||
|
||||
; Macro functions are definitely entities.
|
||||
(preproc_function_def
|
||||
(identifier) @entity.name.function.preprocessor.cpp
|
||||
(#set! capture.final true)
|
||||
)
|
||||
(#set! capture.final true))
|
||||
|
||||
(system_lib_string) @string.quoted.other.lt-gt.include.c
|
||||
((system_lib_string) @punctuation.definition.string.begin.c
|
||||
; Identifiers in macro definitions are definitely constants.
|
||||
((preproc_def
|
||||
name: (identifier) @constant.preprocessor.cpp))
|
||||
|
||||
; We can also safely treat identifiers as constants in `#ifdef`…
|
||||
((preproc_ifdef
|
||||
(identifier) @constant.preprocessor.cpp))
|
||||
|
||||
; …and `#if` and `#elif`…
|
||||
(preproc_if
|
||||
(binary_expression
|
||||
(identifier) @constant.preprocessor.cpp))
|
||||
(preproc_elif
|
||||
(binary_expression
|
||||
(identifier) @constant.preprocessor.cpp))
|
||||
|
||||
; …and `#undef`.
|
||||
((preproc_call
|
||||
directive: (preproc_directive) @_IGNORE_
|
||||
argument: (preproc_arg) @constant.preprocessor.cpp)
|
||||
(#eq? @_IGNORE_ "#undef"))
|
||||
|
||||
(system_lib_string) @string.quoted.other.lt-gt.include.cpp
|
||||
((system_lib_string) @punctuation.definition.string.begin.cpp
|
||||
(#set! adjust.endAfterFirstMatchOf "^<"))
|
||||
((system_lib_string) @punctuation.definition.string.end.c
|
||||
((system_lib_string) @punctuation.definition.string.end.cpp
|
||||
(#set! adjust.startBeforeFirstMatchOf ">$"))
|
||||
|
||||
|
||||
@ -48,15 +74,24 @@
|
||||
(type_identifier) @_IGNORE_
|
||||
(#set! capture.final true))
|
||||
|
||||
; When the user has typed `#define FOO`, the macro injection thinks that `FOO`
|
||||
; is a type declaration (for some reason). This node structure seems to exist
|
||||
; only in that unusual and incorrect scenario, so we'll stop it from happening
|
||||
; so that it doesn't override the underlying `constant.other.c` scope.
|
||||
(translation_unit
|
||||
(type_identifier) @_IGNORE_
|
||||
(#set! capture.final))
|
||||
|
||||
(primitive_type) @support.type.builtin.cpp
|
||||
|
||||
; Type parameters
|
||||
(template_argument_list
|
||||
(type_descriptor
|
||||
type: (type_identifier) @variable.parameter.type.cpp
|
||||
(#set! capture.final true)))
|
||||
; Mark all function definition types with data…
|
||||
(function_definition
|
||||
type: (_) @_IGNORE_
|
||||
(#set! functionDefinitionType true))
|
||||
|
||||
; …so that we can detect when a type identifier is part of a template/generic.
|
||||
((type_identifier) @variable.parameter.type.cpp
|
||||
(#is? test.descendantOfNodeWithData functionDefinitionType))
|
||||
|
||||
(class_specifier
|
||||
(type_identifier) @entity.name.class.cpp
|
||||
@ -67,28 +102,38 @@
|
||||
|
||||
; These types are all reserved words; if we see an identifier with this name,
|
||||
; it must be a type.
|
||||
((identifier) @support.type.builtin.cpp
|
||||
(#match? @support.type.builtin.cpp "^(char|int|float|double|long)$"))
|
||||
((identifier) @support.storage.type.builtin.cpp
|
||||
(#match? @support.storage.type.builtin.cpp "^(char|int|float|double|long)$"))
|
||||
|
||||
; Assume any identifier that ends in `_t` is a type. This convention is not
|
||||
; always followed, but it's a very strong indicator when it's present.
|
||||
((identifier) @support.type.other.cpp
|
||||
(#match? @support.type.other.cpp "_t$"))
|
||||
((identifier) @support.other.storage.type.cpp
|
||||
(#match? @support.other.storage.type.cpp "_t$"))
|
||||
|
||||
|
||||
; These refer to language constructs and remain in the `storage` namespace.
|
||||
[
|
||||
"enum"
|
||||
"long"
|
||||
"short"
|
||||
"signed"
|
||||
"struct"
|
||||
"typedef"
|
||||
"union"
|
||||
"unsigned"
|
||||
|
||||
"template"
|
||||
] @storage.type.cpp
|
||||
|
||||
; These refer to value types and go under `support`.
|
||||
[
|
||||
"long"
|
||||
"short"
|
||||
] @support.storage.type.builtin.cpp
|
||||
|
||||
; These act as modifiers to value types and also go under `support`.
|
||||
[
|
||||
"signed"
|
||||
"unsigned"
|
||||
] @support.storage.modifier.builtin.cpp
|
||||
|
||||
; These act as general language modifiers and remain in the `storage`
|
||||
; namespace.
|
||||
[
|
||||
"const"
|
||||
"extern"
|
||||
@ -108,15 +153,15 @@
|
||||
"override"
|
||||
"final"
|
||||
"noexcept"
|
||||
] @storage.modifier.cpp
|
||||
|
||||
"typename"
|
||||
] @storage.modifier._TYPE_.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)$")
|
||||
(primitive_type) @support.storage.type.stdint.cpp
|
||||
(#match? @support.storage.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
|
||||
; =========
|
||||
@ -124,9 +169,21 @@
|
||||
(function_declarator
|
||||
(identifier) @entity.name.function.cpp)
|
||||
|
||||
(function_declarator
|
||||
(destructor_name
|
||||
(identifier) @entity.name.function.cpp))
|
||||
|
||||
; The "foo" in `void Bar::foo () {`.
|
||||
(function_declarator
|
||||
declarator: (qualified_identifier
|
||||
name: (identifier) @entity.name.function.cpp))
|
||||
|
||||
(function_declarator
|
||||
(field_identifier) @entity.name.function.method.cpp)
|
||||
|
||||
(field_initializer
|
||||
(field_identifier) @entity.name.function.cpp)
|
||||
|
||||
(call_expression
|
||||
(identifier) @support.function.c99.cpp
|
||||
; Regex copied from the TM grammar.
|
||||
@ -139,6 +196,19 @@
|
||||
field: (field_identifier) @support.other.function.cpp)
|
||||
(#set! capture.final true))
|
||||
|
||||
; The "foo" in `troz::foo(...)`.
|
||||
(call_expression
|
||||
function: (qualified_identifier
|
||||
name: (identifier) @support.other.function.cpp)
|
||||
(#set! capture.final true))
|
||||
|
||||
; The "foo" in `troz::foo<SomeType>(...)`.
|
||||
(call_expression
|
||||
function: (qualified_identifier
|
||||
name: (template_function
|
||||
name: (identifier) @support.other.function.cpp))
|
||||
(#set! capture.final true))
|
||||
|
||||
(call_expression
|
||||
(identifier) @support.other.function.cpp
|
||||
(#set! capture.final true))
|
||||
@ -180,34 +250,67 @@
|
||||
; Declarations and assignments
|
||||
; ----------------------------
|
||||
|
||||
; The "x" in `int x`;
|
||||
; The "x" in `int x;`
|
||||
(declaration
|
||||
declarator: (identifier) @variable.declaration.cpp)
|
||||
|
||||
; The "x" in `int x = y`;
|
||||
; The "x" in `int x = y;`
|
||||
(init_declarator
|
||||
declarator: (identifier) @variable.declaration.cpp)
|
||||
|
||||
; The "x" in `SomeType *x;`
|
||||
; (Should work no matter how many pointers deep we are.)
|
||||
(pointer_declarator
|
||||
declarator: [(identifier) (field_identifier)] @variable.declaration.pointer.cpp
|
||||
(#is? test.descendantOfType "declaration field_declaration"))
|
||||
|
||||
; A member of a struct.
|
||||
(field_declaration
|
||||
(field_identifier) @variable.declaration.cpp)
|
||||
(field_identifier) @variable.declaration.member.cpp)
|
||||
|
||||
; An attribute in a C99 struct designated initializer:
|
||||
; the "foo" in `MY_TYPE a = { .foo = true };
|
||||
(initializer_pair
|
||||
(field_designator
|
||||
(field_identifier) @variable.declaration.member.cpp))
|
||||
|
||||
; (and the associated ".")
|
||||
(initializer_pair
|
||||
(field_designator
|
||||
"." @keyword.operator.accessor.cpp))
|
||||
|
||||
(field_declaration
|
||||
(pointer_declarator
|
||||
(field_identifier) @variable.declaration.cpp))
|
||||
(field_identifier) @variable.declaration.member.cpp))
|
||||
|
||||
(field_declaration
|
||||
(array_declarator
|
||||
(field_identifier) @variable.declaration.cpp))
|
||||
(field_identifier) @variable.declaration.member.cpp))
|
||||
|
||||
(init_declarator
|
||||
(pointer_declarator
|
||||
(identifier) @variable.declaration.cpp))
|
||||
(identifier) @variable.declaration.member.cpp))
|
||||
|
||||
; The "x" in `x = y;`
|
||||
(assignment_expression
|
||||
left: (identifier) @variable.other.assignment.cpp)
|
||||
|
||||
(reference_declarator
|
||||
; The "foo" in `something->foo = "bar";`
|
||||
(assignment_expression
|
||||
left: (field_expression
|
||||
field: (field_identifier) @variable.other.member.assignment.cpp)
|
||||
(#set! capture.final))
|
||||
|
||||
((reference_declarator
|
||||
(identifier) @variable.declaration.cpp)
|
||||
(#is-not? test.descendantOfType parameter_declaration))
|
||||
|
||||
; Goto label definitions like `foo:` before a statement.
|
||||
(labeled_statement
|
||||
label: (statement_identifier) @entity.name.label.cpp)
|
||||
|
||||
(goto_statement
|
||||
label: (statement_identifier) @support.other.label.cpp)
|
||||
|
||||
; Function parameters
|
||||
; -------------------
|
||||
@ -215,12 +318,19 @@
|
||||
(preproc_params
|
||||
(identifier) @variable.parameter.preprocessor.cpp)
|
||||
|
||||
; The "foo" in `const char foo` within a parameter list.
|
||||
(parameter_declaration
|
||||
declarator: (identifier) @variable.parameter.cpp)
|
||||
|
||||
; The "foo" in `const char *foo` within a parameter list.
|
||||
; (Should work no matter how many pointers deep we are.)
|
||||
(pointer_declarator
|
||||
declarator: [(identifier) (field_identifier)] @variable.parameter.pointer.cpp
|
||||
(#is? test.descendantOfType "parameter_declaration"))
|
||||
|
||||
(parameter_declaration
|
||||
declarator: (pointer_declarator
|
||||
declarator: (identifier) @variable.parameter.cpp))
|
||||
declarator: (reference_declarator
|
||||
(identifier) @variable.parameter.cpp))
|
||||
|
||||
; The "foo" in `const char foo[]` within a parameter list.
|
||||
(parameter_declaration
|
||||
@ -238,6 +348,11 @@
|
||||
"->"
|
||||
field: (field_identifier) @variable.other.member.cpp)
|
||||
|
||||
; The "bar" in `foo.bar`.
|
||||
(field_expression
|
||||
operator: "."
|
||||
field: (field_identifier) @variable.other.member.cpp)
|
||||
|
||||
; Common naming idiom for C++ instanced vars: "fMemberName"
|
||||
; ((identifier) @variable.other.readwrite.member.cpp
|
||||
; (#match? @variable.other.readwrite.member.cpp "^(f|m)[A-Z]\\w*$"))
|
||||
@ -256,11 +371,21 @@
|
||||
(null)
|
||||
(true)
|
||||
(false)
|
||||
(nullptr)
|
||||
] @constant.language._TYPE_.cpp
|
||||
|
||||
((identifier) @constant.cpp
|
||||
(#match? @constant.cpp "[_A-Z][_A-Z0-9]*$"))
|
||||
; Don't try to scope (e.g.) `int FOO = 1` as a constant when the user types `=`
|
||||
; but has not typed the value yet.
|
||||
(ERROR
|
||||
(identifier) @_IGNORE_
|
||||
(#set! capture.final))
|
||||
|
||||
; In most languages we wouldn't be making the assumption that an all-caps
|
||||
; identifier should be treated as a constant. But those languages don't have
|
||||
; macro preprocessors. The convention is decently strong in C/C++ that all-caps
|
||||
; identifiers will refer to `#define`d things.
|
||||
((identifier) @constant.other.cpp
|
||||
(#match? @constant.other.cpp "^[_A-Z][_A-Z0-9]*$")
|
||||
(#set! capture.shy))
|
||||
|
||||
|
||||
; COMMENTS
|
||||
@ -315,6 +440,7 @@
|
||||
"throw"
|
||||
"using"
|
||||
"namespace"
|
||||
"class"
|
||||
] @keyword.control._TYPE_.cpp
|
||||
|
||||
; OPERATORS
|
||||
@ -324,6 +450,7 @@
|
||||
(abstract_pointer_declarator "*" @keyword.operator.pointer.cpp)
|
||||
(pointer_expression "*" @keyword.operator.pointer.cpp)
|
||||
|
||||
(destructor_name "~" @keyword.operator.destructor.cpp)
|
||||
|
||||
"sizeof" @keyword.operator.sizeof.cpp
|
||||
(pointer_expression "&" @keyword.operator.pointer.cpp)
|
||||
@ -381,8 +508,10 @@
|
||||
|
||||
";" @punctuation.terminator.statement.cpp
|
||||
|
||||
"," @punctuation.separator.comma.cpp
|
||||
"->" @keyword.operator.accessor.cpp
|
||||
("," @punctuation.separator.comma.cpp
|
||||
(#set! capture.shy))
|
||||
("->" @keyword.operator.accessor.pointer-access.cpp
|
||||
(#set! capture.shy))
|
||||
|
||||
(parameter_list
|
||||
"(" @punctuation.definition.parameters.begin.bracket.round.cpp
|
||||
@ -407,6 +536,22 @@
|
||||
"[" @punctuation.definition.array.begin.bracket.square.cpp
|
||||
"]" @punctuation.definition.array.end.bracket.square.cpp
|
||||
|
||||
; META
|
||||
; ====
|
||||
|
||||
((compound_statement) @meta.block.cpp
|
||||
(#set! adjust.startAt firstChild.endPosition)
|
||||
(#set! adjust.endAt lastChild.startPosition))
|
||||
|
||||
((enumerator_list) @meta.block.enum.cpp
|
||||
(#set! adjust.startAt firstChild.endPosition)
|
||||
(#set! adjust.endAt lastChild.startPosition))
|
||||
|
||||
((field_declaration_list) @meta.block.field.cpp
|
||||
(#set! adjust.startAt firstChild.endPosition)
|
||||
(#set! adjust.endAt lastChild.startPosition))
|
||||
|
||||
|
||||
; TODO:
|
||||
;
|
||||
; * TM-style grammar has a lot of `mac-classic` scopes. I doubt they'd be
|
||||
|
Binary file not shown.
@ -1,16 +1,30 @@
|
||||
exports.activate = function() {
|
||||
exports.activate = function () {
|
||||
// Highlight macro bodies as C/C++
|
||||
for (const language of ['c', 'cpp']) {
|
||||
for (const nodeType of ['preproc_def', 'preproc_function_def']) {
|
||||
atom.grammars.addInjectionPoint(`source.${language}`, {
|
||||
type: nodeType,
|
||||
language(node) {
|
||||
language() {
|
||||
return language;
|
||||
},
|
||||
content(node) {
|
||||
return node.lastNamedChild;
|
||||
return node.descendantsOfType('preproc_arg');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
exports.consumeHyperlinkInjection = (hyperlink) => {
|
||||
for (const language of ['c', 'cpp']) {
|
||||
hyperlink.addInjectionPoint(`source.${language}`, {
|
||||
types: ['comment', 'string_literal']
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
exports.consumeTodoInjection = (todo) => {
|
||||
for (const language of ['c', 'cpp']) {
|
||||
todo.addInjectionPoint(`source.${language}`, { types: ['comment'] });
|
||||
}
|
||||
};
|
||||
|
@ -10,10 +10,22 @@
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"atom": "*",
|
||||
"node": "*"
|
||||
"node": ">=12"
|
||||
},
|
||||
"dependencies": {
|
||||
"tree-sitter-c": "0.20.2",
|
||||
"tree-sitter-cpp": "0.20.0"
|
||||
},
|
||||
"consumedServices": {
|
||||
"hyperlink.injection": {
|
||||
"versions": {
|
||||
"0.1.0": "consumeHyperlinkInjection"
|
||||
}
|
||||
},
|
||||
"todo.injection": {
|
||||
"versions": {
|
||||
"0.1.0": "consumeTodoInjection"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,11 @@
|
||||
'.source.c, .source.cpp, .source.objc, .source.objcpp':
|
||||
'editor':
|
||||
'commentStart': '// '
|
||||
# Technically, line comments aren't universally valid in C, but all modern
|
||||
# C compilers support them.
|
||||
'commentDelimiters':
|
||||
'block': ['/*', '*/']
|
||||
'line': '//'
|
||||
'increaseIndentPattern': '(?x)
|
||||
^ .* \\{ [^}"\']* $
|
||||
|^ .* \\( [^\\)"\']* $
|
||||
|
@ -39,6 +39,7 @@ parser: 'tree-sitter-clojure'
|
||||
)
|
||||
'''
|
||||
treeSitter:
|
||||
parserSource: 'github:mauricioszabo/tree-sitter-clojure#inner-strings'
|
||||
grammar: 'ts/grammar.wasm'
|
||||
highlightsQuery: [
|
||||
'ts/highlights.scm'
|
||||
|
@ -7,6 +7,7 @@ parser: 'tree-sitter-clojure'
|
||||
fileTypes: [
|
||||
]
|
||||
treeSitter:
|
||||
parserSource: 'github:mauricioszabo/tree-sitter-clojure#inner-strings'
|
||||
grammar: 'ts/grammar.wasm'
|
||||
highlightsQuery: [
|
||||
'ts/edn-only-highlights.scm'
|
||||
|
@ -74,9 +74,12 @@
|
||||
((list_lit
|
||||
"(" @punctuation.section.expression.begin (#is-not? test.descendantOfNodeWithData "clojure.dismissTag")
|
||||
.
|
||||
(sym_lit) @meta.definition.global @keyword.control (#eq? @meta.definition.global "ns")
|
||||
(sym_lit) @meta.definition.global @keyword.control
|
||||
(#eq? @meta.definition.global "ns")
|
||||
.
|
||||
(sym_lit) @meta.definition.global @entity.global
|
||||
; We need to distinguish this `@meta.definition.global` from the one above or
|
||||
; else this query will fail.
|
||||
(sym_lit) @meta.definition.global.__TEXT__ @entity.global
|
||||
")" @punctuation.section.expression.end)
|
||||
@meta.namespace.clojure
|
||||
(#set! isNamespace true))
|
||||
|
@ -5,7 +5,7 @@
|
||||
"main": "lib/main",
|
||||
"engines": {
|
||||
"atom": "*",
|
||||
"node": "*"
|
||||
"node": ">=12"
|
||||
},
|
||||
"repository": "https://github.com/pulsar-edit/pulsar",
|
||||
"license": "MIT",
|
||||
|
@ -2,5 +2,7 @@
|
||||
'editor':
|
||||
'commentStart': '; '
|
||||
'nonWordCharacters': '/\\()"\':,;~^[]{}`'
|
||||
'commentDelimiters':
|
||||
'line': ';'
|
||||
'autocomplete':
|
||||
'extraWordCharacters': '-'
|
||||
|
9
packages/language-clojure/spec/.eslintrc.js
Normal file
9
packages/language-clojure/spec/.eslintrc.js
Normal file
@ -0,0 +1,9 @@
|
||||
module.exports = {
|
||||
env: { jasmine: true },
|
||||
rules: {
|
||||
"node/no-unpublished-require": "off",
|
||||
"node/no-extraneous-require": "off",
|
||||
"no-unused-vars": "off",
|
||||
"no-empty": "off"
|
||||
}
|
||||
};
|
@ -4,7 +4,6 @@ describe("Clojure grammar", function() {
|
||||
|
||||
beforeEach(function() {
|
||||
atom.config.set('core.useTreeSitterParsers', false);
|
||||
atom.config.set('core.useExperimentalModernTreeSitter', false);
|
||||
|
||||
waitsForPromise(() => atom.packages.activatePackage("language-clojure"));
|
||||
|
||||
|
@ -3,9 +3,7 @@ const path = require('path');
|
||||
|
||||
function setConfigForLanguageMode(mode) {
|
||||
let useTreeSitterParsers = mode !== 'textmate';
|
||||
let useExperimentalModernTreeSitter = mode === 'modern-tree-sitter';
|
||||
atom.config.set('core.useTreeSitterParsers', useTreeSitterParsers);
|
||||
atom.config.set('core.useExperimentalModernTreeSitter', useExperimentalModernTreeSitter);
|
||||
}
|
||||
|
||||
describe('Clojure grammars', () => {
|
||||
|
@ -1,6 +1,8 @@
|
||||
'.source.coffee, .source.litcoffee, .source.coffee.md':
|
||||
'editor':
|
||||
'commentStart': '# '
|
||||
'commentDelimiters':
|
||||
'line': '#'
|
||||
'.source.coffee':
|
||||
'editor':
|
||||
'autoIndentOnPaste': false
|
||||
|
@ -1,5 +1,8 @@
|
||||
'.source.cs':
|
||||
'editor':
|
||||
'commentStart': '// '
|
||||
'commentDelimiters':
|
||||
'line': '//'
|
||||
'block': ['/*', '*/']
|
||||
'increaseIndentPattern': '(?x)\n\t\t^ .* \\{ [^}"\']* $\n\t| ^ \\s* \\{ \\} $\n\t'
|
||||
'decreaseIndentPattern': '(?x)\n\t\t^ (.*\\*/)? \\s* \\} ( [^}{"\']* \\{ | \\s* while \\s* \\( .* )? [;\\s]* (//.*|/\\*.*\\*/\\s*)? $\n\t'
|
||||
|
@ -8,6 +8,7 @@ fileTypes: [
|
||||
]
|
||||
|
||||
treeSitter:
|
||||
parserSource: 'github:tree-sitter/tree-sitter-css#98c7b3dceb24f1ee17f1322f3947e55638251c37'
|
||||
grammar: 'tree-sitter/tree-sitter-css.wasm'
|
||||
highlightsQuery: 'tree-sitter/queries/highlights.scm'
|
||||
foldsQuery: 'tree-sitter/queries/folds.scm'
|
||||
|
@ -1,36 +1,36 @@
|
||||
|
||||
; WORKAROUND:
|
||||
|
||||
; NOTE: `tree-sitter-css` recovers poorly from invalidity inside a block when
|
||||
; you're adding a new property-value pair above others in a list. When the user
|
||||
; is typing and the file is temporarily invalid, it will make incorrect guesses
|
||||
; about tokens that occur between the cursor and the end of the block.
|
||||
;
|
||||
; 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.
|
||||
; The fix here is for `tree-sitter-css` to get better at recovering from its
|
||||
; parsing error, but parser authors don't currently have much control over
|
||||
; that. In the meantime, this query is a decent mitigation: it colors the
|
||||
; affected tokens like plain text instead of assuming (nearly always
|
||||
; incorrectly) them to be tag names.
|
||||
;
|
||||
; Ideally, this is temporary, and we can remove it soon. Until then, it makes
|
||||
; syntax highlighting less obnoxious.
|
||||
|
||||
(descendant_selector
|
||||
(tag_name) @_IGNORE_
|
||||
(#set! capture.final true))
|
||||
((tag_name) @_IGNORE_
|
||||
(#is? test.descendantOfType "ERROR")
|
||||
(#set! capture.final))
|
||||
|
||||
(ERROR
|
||||
(attribute_name) @_IGNORE_
|
||||
(#set! capture.final true))
|
||||
(#set! capture.final))
|
||||
|
||||
((ERROR
|
||||
(attribute_name) @invalid.illegal)
|
||||
(#set! capture.final true))
|
||||
(#set! capture.final))
|
||||
|
||||
; 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.
|
||||
; In `::after`, the "after" has a node type of `tag_name`. Unclear whether this
|
||||
; is a bug or intended behavior. 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)
|
||||
@ -61,9 +61,6 @@
|
||||
|
||||
; (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)
|
||||
|
||||
@ -77,10 +74,30 @@
|
||||
(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
|
||||
; Declaration of a namespace:
|
||||
; The "svg" in `@namespace svg url(http://www.w3.org/2000/svg);`
|
||||
(namespace_name) @entity.other.namespace-prefix.css
|
||||
|
||||
;(namespace_name) @entity.other.namespace-prefix.css
|
||||
; A namespaced tag name:
|
||||
; The "svg" in `svg|a {}`.
|
||||
(namespace_selector
|
||||
. (tag_name) @entity.other.namespace-prefix.css
|
||||
"|" @punctuation.separator.namespace.css
|
||||
(#set! capture.final))
|
||||
|
||||
; Not sure if this is intended, but a namespaced attribute in an attribute
|
||||
; selector is construed as two tag-name children of the `attribute_name`.
|
||||
; The "xl" in `[xl|href] {}`.
|
||||
(attribute_name
|
||||
. (tag_name) @entity.other.namespace-prefix.css
|
||||
"|" @punctuation.separator.namespace.css
|
||||
(tag_name) @entity.other.attribute_name.css
|
||||
(#set! capture.final)) @_IGNORE_
|
||||
|
||||
; The "div" in `div.foo {`.
|
||||
(tag_name) @entity.name.tag.css
|
||||
; The "*" in `*[foo="bar"]`.
|
||||
(universal_selector) @entity.name.tag.universal.css
|
||||
|
||||
; The '.' in `.foo`.
|
||||
(class_selector
|
||||
@ -101,27 +118,41 @@
|
||||
(#set! adjust.startAt lastChild.previousSibling.startPosition)
|
||||
(#set! adjust.endAt lastChild.endPosition))
|
||||
|
||||
; Punctuation around the arguments of a pseudo-class or a function.
|
||||
(arguments
|
||||
"(" @punctuation.definition.arguments.begin.bracket.round.css
|
||||
")" @punctuation.definition.arguments.end.bracket.round.css)
|
||||
|
||||
; Punctuation around an attribute selector.
|
||||
(attribute_selector
|
||||
"[" @punctuation.definition.entity.begin.bracket.square.css
|
||||
(attribute_name) @entity.other.attribute-name.css
|
||||
"]" @punctuation.definition.entity.end.bracket.square.css)
|
||||
|
||||
; Operators inside attribute selectors.
|
||||
(attribute_selector
|
||||
["=" "^=" "$=" "~=" "|="] @keyword.operator.pattern.css)
|
||||
|
||||
; The "foo" in `@keyframes foo {`.
|
||||
(keyframes_name) @entity.name.keyframes.css
|
||||
|
||||
; VARIABLES
|
||||
; =========
|
||||
|
||||
; Variable declaration:
|
||||
; The "--link-visited" in `--link-visited: #039;`.
|
||||
(declaration
|
||||
(property_name) @variable.other.assignment.css
|
||||
(#match? @variable.other.assignment.css "^--" )
|
||||
(#set! capture.final true))
|
||||
|
||||
; Variable usage:
|
||||
; The ""--link--visited" in `color: var(--link-visited);`.
|
||||
((function_name) @support.function.var.css
|
||||
(arguments (plain_value) @variable.css)
|
||||
(#eq? @support.function.var.css "var"))
|
||||
|
||||
|
||||
; PROPERTIES
|
||||
; ==========
|
||||
|
||||
@ -145,9 +176,9 @@
|
||||
(#match? @string.quoted.single.css "^'")
|
||||
(#match? @string.quoted.single.css "'$"))
|
||||
|
||||
; The punctuation around quoted strings.
|
||||
((string_value) @punctuation.definition.string.begin.css
|
||||
(#set! adjust.startAndEndAroundFirstMatchOf "^[\"']"))
|
||||
|
||||
((string_value) @punctuation.definition.string.end.css
|
||||
(#set! adjust.startAndEndAroundFirstMatchOf "[\"']$"))
|
||||
|
||||
@ -206,10 +237,6 @@
|
||||
; (#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"))
|
||||
@ -286,16 +313,19 @@
|
||||
((rule_set) @meta.selector.css
|
||||
(#set! adjust.endBeforeFirstMatchOf "{"))
|
||||
|
||||
; Scope the inside of a media query block so that tooling doesn't mistake it
|
||||
; for a property/value pair.
|
||||
(keyword_query) @meta.media-query.css
|
||||
((feature_query) @meta.media-query.css
|
||||
(#set! adjust.startAfterFirstMatchOf "^\\(")
|
||||
(#set! adjust.endBeforeFirstMatchOf "\\)$"))
|
||||
|
||||
(parenthesized_query) @meta.media-query.css
|
||||
|
||||
|
||||
; META
|
||||
; ====
|
||||
|
||||
[
|
||||
(plain_value)
|
||||
(integer_value)
|
||||
(string_value)
|
||||
] @meta.property-value.css
|
||||
|
||||
; `!important` starts out as an ERROR node as it's being typed, but we need it
|
||||
; to be recognized as a possible property value for `autocomplete-css` to be
|
||||
; able to complete it. This should match only when it comes at the end of a
|
||||
|
@ -1 +1,5 @@
|
||||
(selectors) @name
|
||||
((selectors) @name
|
||||
(#set! symbol.tag "Selector"))
|
||||
|
||||
((keyframes_name) @name
|
||||
(#set! symbol.tag "Keyframes"))
|
||||
|
Binary file not shown.
@ -1,21 +1,7 @@
|
||||
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
|
||||
exports.consumeHyperlinkInjection = (hyperlink) => {
|
||||
hyperlink.addInjectionPoint('source.css', {
|
||||
types: ['comment', 'string_value']
|
||||
});
|
||||
|
||||
// Catch things like
|
||||
@ -23,15 +9,18 @@ exports.activate = () => {
|
||||
// @import url(https://www.example.com/style.css);
|
||||
//
|
||||
// where the URL is unquoted.
|
||||
atom.grammars.addInjectionPoint('source.css', {
|
||||
type: 'call_expression',
|
||||
hyperlink.addInjectionPoint('source.css', {
|
||||
types: ['call_expression'],
|
||||
language: () => 'hyperlink',
|
||||
content: (node) => {
|
||||
content(node) {
|
||||
let functionName = node.descendantsOfType('function_value')[0]?.text;
|
||||
if (!functionName === 'url') { return null; }
|
||||
|
||||
return node.descendantsOfType('plain_value');
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
exports.consumeTodoInjection = (todo) => {
|
||||
todo.addInjectionPoint('source.css', { types: ['comment'] });
|
||||
};
|
||||
|
@ -14,5 +14,17 @@
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tree-sitter-css": "^0.19.0"
|
||||
},
|
||||
"consumedServices": {
|
||||
"hyperlink.injection": {
|
||||
"versions": {
|
||||
"0.1.0": "consumeHyperlinkInjection"
|
||||
}
|
||||
},
|
||||
"todo.injection": {
|
||||
"versions": {
|
||||
"0.1.0": "consumeTodoInjection"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,8 @@
|
||||
'editor':
|
||||
'commentStart': '/*'
|
||||
'commentEnd': '*/'
|
||||
'commentDelimiters':
|
||||
'block': ['/*', '*/']
|
||||
'foldEndPattern': '(?<!\\*)\\*\\*/|^\\s*\\}|\\/*\\s*@end\\s*\\*\\/'
|
||||
'autocomplete':
|
||||
'extraWordCharacters': '-'
|
||||
|
@ -39,7 +39,7 @@
|
||||
|
||||
(paragraph) @markup.paragraph.gfm
|
||||
|
||||
(thematic_break) @punctuation.definition.horizontal-rule.gfm
|
||||
(thematic_break) @markup.horizontal-rule.gfm
|
||||
|
||||
(block_quote) @markup.quote.blockquote.gfm
|
||||
((block_quote) @punctuation.definition.blockquote.gfm
|
||||
@ -140,8 +140,9 @@
|
||||
(code_span) @meta.embedded.line.inline-code.gfm @markup.raw.inline.gfm
|
||||
(info_string) @storage.modifier.language._TEXT_.gfm
|
||||
|
||||
(fenced_code_block) @markup.code.fenced.gfm @meta.embedded.block.fenced-code.gfm
|
||||
(indented_code_block) @markup.code.indented.gfm @meta.embedded.block.indented-code.gfm
|
||||
(fenced_code_block
|
||||
(code_fence_content) @markup.raw.block.fenced.gfm) @meta.embedded.block.fenced-code.gfm
|
||||
(indented_code_block) @markup.raw.block.indented.gfm @meta.embedded.block.indented-code.gfm
|
||||
|
||||
|
||||
; BOLD/ITALIC/OTHER
|
||||
|
@ -3,46 +3,54 @@
|
||||
(atx_h1_marker)
|
||||
(heading_content) @name) @definition.heading
|
||||
(#set! symbol.strip "(^\\s*|\\s*$)")
|
||||
(#set! symbol.prepend "· "))
|
||||
(#set! symbol.prepend "· ")
|
||||
(#set! symbol.icon "chevron-right"))
|
||||
|
||||
((atx_heading
|
||||
(atx_h2_marker)
|
||||
(heading_content) @name) @definition.heading
|
||||
(#set! symbol.strip "(^\\s*|\\s*$)")
|
||||
(#set! symbol.prepend "·· "))
|
||||
(#set! symbol.prepend "·· ")
|
||||
(#set! symbol.icon "chevron-right"))
|
||||
|
||||
((atx_heading
|
||||
(atx_h3_marker)
|
||||
(heading_content) @name) @definition.heading
|
||||
(#set! symbol.strip "(^\\s*|\\s*$)")
|
||||
(#set! symbol.prepend "··· "))
|
||||
(#set! symbol.prepend "··· ")
|
||||
(#set! symbol.icon "chevron-right"))
|
||||
|
||||
((atx_heading
|
||||
(atx_h4_marker)
|
||||
(heading_content) @name) @definition.heading
|
||||
(#set! symbol.strip "(^\\s*|\\s*$)")
|
||||
(#set! symbol.prepend "···· "))
|
||||
(#set! symbol.prepend "···· ")
|
||||
(#set! symbol.icon "chevron-right"))
|
||||
|
||||
((atx_heading
|
||||
(atx_h5_marker)
|
||||
(heading_content) @name) @definition.heading
|
||||
(#set! symbol.strip "(^\\s*|\\s*$)")
|
||||
(#set! symbol.prepend "····· "))
|
||||
(#set! symbol.prepend "····· ")
|
||||
(#set! symbol.icon "chevron-right"))
|
||||
|
||||
((atx_heading
|
||||
(atx_h6_marker)
|
||||
(heading_content) @name) @definition.heading
|
||||
(#set! symbol.strip "(^\\s*|\\s*$)")
|
||||
(#set! symbol.prepend "······ "))
|
||||
(#set! symbol.prepend "······ ")
|
||||
(#set! symbol.icon "chevron-right"))
|
||||
|
||||
((setext_heading
|
||||
(heading_content) @name) @definition.heading
|
||||
(setext_h1_underline)
|
||||
(#set! symbol.strip "(^\\s*|\\s*$)")
|
||||
(#set! symbol.prepend "· "))
|
||||
(#set! symbol.prepend "· ")
|
||||
(#set! symbol.icon "chevron-right"))
|
||||
|
||||
((setext_heading
|
||||
(heading_content) @name) @definition.heading
|
||||
(setext_h2_underline)
|
||||
(#set! symbol.strip "(^\\s*|\\s*$)")
|
||||
(#set! symbol.prepend "·· "))
|
||||
(#set! symbol.prepend "·· ")
|
||||
(#set! symbol.icon "chevron-right"))
|
||||
|
@ -38,30 +38,31 @@ exports.activate = () => {
|
||||
includeChildren: true
|
||||
});
|
||||
|
||||
// Highlight inline HTML within paragraphs.
|
||||
atom.grammars.addInjectionPoint('source.gfm.embedded', {
|
||||
type: 'paragraph',
|
||||
language(node) {
|
||||
let html = node.descendantsOfType([
|
||||
'html_open_tag',
|
||||
'html_close_tag',
|
||||
'html_self_closing_tag'
|
||||
]);
|
||||
if (html.length === 0) { return null; }
|
||||
return 'html';
|
||||
},
|
||||
for (let nodeType of ['paragraph', 'table_cell']) {
|
||||
atom.grammars.addInjectionPoint('source.gfm.embedded', {
|
||||
type: nodeType,
|
||||
language(node) {
|
||||
let html = node.descendantsOfType([
|
||||
'html_open_tag',
|
||||
'html_close_tag',
|
||||
'html_self_closing_tag'
|
||||
]);
|
||||
if (html.length === 0) { return null; }
|
||||
return 'html';
|
||||
},
|
||||
|
||||
content(node) {
|
||||
let html = node.descendantsOfType([
|
||||
'html_open_tag',
|
||||
'html_close_tag',
|
||||
'html_self_closing_tag'
|
||||
]);
|
||||
return html;
|
||||
},
|
||||
content(node) {
|
||||
let html = node.descendantsOfType([
|
||||
'html_open_tag',
|
||||
'html_close_tag',
|
||||
'html_self_closing_tag'
|
||||
]);
|
||||
return html;
|
||||
},
|
||||
|
||||
includeChildren: true
|
||||
});
|
||||
includeChildren: true
|
||||
});
|
||||
}
|
||||
|
||||
// All code blocks of the form
|
||||
//
|
||||
@ -87,3 +88,40 @@ exports.activate = () => {
|
||||
includeChildren: true
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// Since this parser isn't guaranteed to detect all URLs in paragraphs (see
|
||||
// https://github.com/pulsar-edit/pulsar/issues/885), we'll inject the
|
||||
// `hyperlink` parser into `text` nodes in paragraphs when there appear to be
|
||||
// URLs in them.
|
||||
exports.consumeHyperlinkInjection = (hyperlink) => {
|
||||
|
||||
function textChildren(node) {
|
||||
let results = [];
|
||||
for (let i = 0; i < node.namedChildCount; i++) {
|
||||
let child = node.child(i);
|
||||
if (child.type === 'text') {
|
||||
results.push(child);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
hyperlink.addInjectionPoint('source.gfm.embedded', {
|
||||
types: ['paragraph'],
|
||||
// Override the language callback so that it doesn't test URLs that are
|
||||
// already handled in `uri_autolink` nodes.
|
||||
language(node) {
|
||||
for (let child of textChildren(node)) {
|
||||
if (hyperlink.test(child)) {
|
||||
return 'hyperlink';
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
content(node) {
|
||||
return textChildren(node);
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
|
@ -10,5 +10,12 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"coffeescript": "^1.7.0"
|
||||
}
|
||||
},
|
||||
"consumedServices": {
|
||||
"hyperlink.injection": {
|
||||
"versions": {
|
||||
"0.1.0": "consumeHyperlinkInjection"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,3 +3,5 @@
|
||||
'softWrap': true
|
||||
'commentStart': '<!-- '
|
||||
'commentEnd': ' -->'
|
||||
'commentDelimiters':
|
||||
'block': ['<!--', '-->']
|
||||
|
12
packages/language-gfm/spec/.eslintrc.js
Normal file
12
packages/language-gfm/spec/.eslintrc.js
Normal file
@ -0,0 +1,12 @@
|
||||
module.exports = {
|
||||
env: { jasmine: true },
|
||||
globals: {
|
||||
waitsForPromise: true
|
||||
},
|
||||
rules: {
|
||||
"node/no-unpublished-require": "off",
|
||||
"node/no-extraneous-require": "off",
|
||||
"no-unused-vars": "off",
|
||||
"no-empty": "off"
|
||||
}
|
||||
};
|
@ -3,6 +3,7 @@ describe("GitHub Flavored Markdown grammar", function() {
|
||||
let grammar = null;
|
||||
|
||||
beforeEach(function() {
|
||||
atom.config.set('core.useTreeSitterParsers', false);
|
||||
waitsForPromise(() => atom.packages.activatePackage("language-gfm"));
|
||||
|
||||
runs(() => grammar = atom.grammars.grammarForScopeName("source.gfm"));
|
||||
|
@ -11,6 +11,7 @@ comments:
|
||||
start: '// '
|
||||
|
||||
treeSitter:
|
||||
parserSource: 'github:tree-sitter/tree-sitter-go#ff86c7f1734873c8c4874ca4dd95603695686d7a'
|
||||
grammar: 'tree-sitter-go/tree-sitter-go.wasm'
|
||||
highlightsQuery: 'tree-sitter-go/highlights.scm'
|
||||
foldsQuery: 'tree-sitter-go/folds.scm'
|
||||
|
@ -47,6 +47,8 @@
|
||||
|
||||
[
|
||||
"struct"
|
||||
"interface"
|
||||
"map"
|
||||
] @storage.type._TYPE_.go
|
||||
|
||||
(struct_type
|
||||
@ -55,13 +57,14 @@
|
||||
(field_identifier) @entity.other.attribute-name.go)))
|
||||
|
||||
(keyed_element
|
||||
(field_identifier) @entity.other.attribute-name.go
|
||||
.
|
||||
":" @punctuation.separator.key-value.go)
|
||||
. (literal_element) @entity.other.attribute-name.go)
|
||||
|
||||
(keyed_element ":" @punctuation.separator.key-value.go)
|
||||
|
||||
[
|
||||
"break"
|
||||
"case"
|
||||
"chan"
|
||||
"continue"
|
||||
"default"
|
||||
"defer"
|
||||
@ -78,7 +81,10 @@
|
||||
] @keyword.control._TYPE_.go
|
||||
|
||||
|
||||
; Function names: the "foo" in `func foo() {`
|
||||
(function_declaration (identifier) @entity.name.function.go)
|
||||
; Method names: the "Foo" in `func (x Bar) Foo {`
|
||||
(method_declaration (field_identifier) @entity.name.function.method.go)
|
||||
|
||||
(call_expression
|
||||
(identifier) @support.function.builtin.go
|
||||
@ -252,7 +258,8 @@
|
||||
|
||||
";" @punctuation.terminator.go
|
||||
"," @punctuation.separator.comma.go
|
||||
":" @punctuation.separator.colon.go
|
||||
(":" @punctuation.separator.colon.go
|
||||
(#set! capture.shy))
|
||||
|
||||
(parameter_list
|
||||
"(" @punctuation.definition.parameters.begin.bracket.round.go
|
||||
|
Binary file not shown.
10
packages/language-go/lib/main.js
Normal file
10
packages/language-go/lib/main.js
Normal file
@ -0,0 +1,10 @@
|
||||
|
||||
exports.consumeHyperlinkInjection = (hyperlink) => {
|
||||
hyperlink.addInjectionPoint('source.go', {
|
||||
types: ['comment', 'interpreted_string_literal', 'raw_string_literal']
|
||||
});
|
||||
};
|
||||
|
||||
exports.consumeTodoInjection = (todo) => {
|
||||
todo.addInjectionPoint('source.go', { types: ['comment'] });
|
||||
};
|
@ -1,6 +1,7 @@
|
||||
{
|
||||
"name": "language-go",
|
||||
"description": "Go language support in Atom",
|
||||
"main": "lib/main",
|
||||
"keywords": [
|
||||
"tree-sitter"
|
||||
],
|
||||
@ -13,5 +14,17 @@
|
||||
"repository": "https://github.com/pulsar-edit/pulsar",
|
||||
"dependencies": {
|
||||
"tree-sitter-go": "0.19.1"
|
||||
},
|
||||
"consumedServices": {
|
||||
"hyperlink.injection": {
|
||||
"versions": {
|
||||
"0.1.0": "consumeHyperlinkInjection"
|
||||
}
|
||||
},
|
||||
"todo.injection": {
|
||||
"versions": {
|
||||
"0.1.0": "consumeTodoInjection"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,9 @@
|
||||
'.source.go':
|
||||
'editor':
|
||||
'commentStart': '// '
|
||||
'commentDelimiters':
|
||||
'block': ['/*', '*/']
|
||||
'line': '//'
|
||||
'increaseIndentPattern': '^.*(\\bcase\\b.*:|\\bdefault\\b:|(\\b(func|if|else|switch|select|for|struct)\\b.*)?{[^}]*|\\([^)]*)$'
|
||||
'decreaseIndentPattern': '^\\s*(\\bcase\\b.*:|\\bdefault\\b:|}[),]?|\\)[,]?)$'
|
||||
'decreaseNextIndentPattern': '^\\s*[^\\s()}]+(?<m>[^()]*\\((?:\\g<m>[^()]*|[^()]*)\\))*[^()]*\\)[,]?$'
|
||||
|
@ -11,6 +11,7 @@ fileTypes: [
|
||||
injectionRegex: '^(ejs|EJS)$'
|
||||
|
||||
treeSitter:
|
||||
parserSource: 'github:tree-sitter/tree-sitter-embedded-template#203f7bd3c1bbfbd98fc19add4b8fcb213c059205'
|
||||
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'
|
||||
|
@ -11,6 +11,7 @@ fileTypes: [
|
||||
injectionRegex: '^(erb|ERB)$'
|
||||
|
||||
treeSitter:
|
||||
parserSource: 'github:tree-sitter/tree-sitter-embedded-template#203f7bd3c1bbfbd98fc19add4b8fcb213c059205'
|
||||
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'
|
||||
|
@ -6,6 +6,7 @@ parser: 'tree-sitter-html'
|
||||
injectionRegex: '(HTML|html|Html)$'
|
||||
|
||||
treeSitter:
|
||||
parserSource: 'github:tree-sitter/tree-sitter-html#d742025fa2d8e6100f134a6ea990443aa1f074b3'
|
||||
grammar: 'tree-sitter-html/tree-sitter-html.wasm'
|
||||
highlightsQuery: 'tree-sitter-html/highlights.scm'
|
||||
foldsQuery: 'tree-sitter-html/folds.scm'
|
||||
@ -20,3 +21,4 @@ fileTypes: [
|
||||
comments:
|
||||
start: '<!-- '
|
||||
end: ' -->'
|
||||
block: ['<!--', '-->']
|
||||
|
Binary file not shown.
@ -1,10 +1,74 @@
|
||||
; When dealing with a self-closing element that spans multiple lines, this lets
|
||||
; us fold the attribute list.
|
||||
;
|
||||
; This query captures elements that happen to be self-closing but don't end
|
||||
; with an XHTML-style ` />`. Because `tree-sitter-html` doesn't distinguish
|
||||
; these from elements that can have content, we have to check the tag name to
|
||||
; know how to treat these.
|
||||
|
||||
((element
|
||||
(start_tag
|
||||
(tag_name) @_IGNORE_) @fold)
|
||||
(#match? @_IGNORE_ "^(area|base|br|col|embed|hr|img|input|keygen|link|meta|param|source|track|wbr)$")
|
||||
)
|
||||
|
||||
; This one captures the XHTML-style nodes.
|
||||
(self_closing_tag) @fold
|
||||
|
||||
|
||||
; TODO: Right now, the fold cache doesn't work properly when a given range
|
||||
; satisfies more than one fold. We should employ `ScopeResolver` to fix this.
|
||||
|
||||
; Fold up all of
|
||||
;
|
||||
; <div
|
||||
; foo="bar"
|
||||
; baz="thud">
|
||||
;
|
||||
; </div>
|
||||
;
|
||||
; with the fold indicator appearing on whichever line has the `>` that closes
|
||||
; the opening tag.
|
||||
;
|
||||
; Usually this'll be the same line on which the tag opened; but when it isn't,
|
||||
; this allows for the attribute list of the opening element to be folded
|
||||
; separately from the element's contents.
|
||||
;
|
||||
|
||||
(element
|
||||
(start_tag
|
||||
(tag_name) @_IGNORE_
|
||||
">" @fold)
|
||||
(#not-match? @_IGNORE_ "^(area|base|br|col|embed|hr|img|input|keygen|link|meta|param|source|track|wbr)$")
|
||||
(#set! fold.endAt parent.parent.lastNamedChild.startPosition)
|
||||
(#set! fold.adjustToEndOfPreviousRow true)
|
||||
)
|
||||
|
||||
|
||||
; When we have…
|
||||
;
|
||||
; <div
|
||||
; foo="bar"
|
||||
; baz="thud"
|
||||
; >
|
||||
;
|
||||
; </div>
|
||||
;
|
||||
; …we can put a fold indicator on the line with `<div` and use it to fold up
|
||||
; all of a start tag's attributes.
|
||||
;
|
||||
; We keep the end of the fold on a separate line because otherwise we lose the
|
||||
; ability to independently toggle the folding of the element's contents.
|
||||
;
|
||||
(element
|
||||
(start_tag
|
||||
(tag_name) @_IGNORE_) @fold
|
||||
(#not-match? @_IGNORE_ "^(area|base|br|col|embed|hr|img|input|keygen|link|meta|param|source|track|wbr)$")
|
||||
(#set! fold.endAt lastChild.startPosition)
|
||||
(#set! fold.adjustToEndOfPreviousRow true))
|
||||
|
||||
; (element) @fold
|
||||
; (style_element) @fold
|
||||
; (script_element) @fold
|
||||
|
||||
[
|
||||
(element)
|
||||
(script_element)
|
||||
(style_element)
|
||||
] @fold
|
||||
|
@ -105,7 +105,14 @@
|
||||
; ----------
|
||||
|
||||
(attribute "=" @punctuation.separator.key-value.html)
|
||||
(attribute_name) @entity.other.attribute-name
|
||||
(attribute_name) @entity.other.attribute-name.html
|
||||
|
||||
; 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
|
||||
|
||||
|
||||
; Single- and double-quotes around attribute values.
|
||||
((quoted_attribute_value ["\"" "'"] @punctuation.definition.string.begin.html)
|
||||
@ -114,11 +121,6 @@
|
||||
((quoted_attribute_value ["\"" "'"] @punctuation.definition.string.end.html)
|
||||
(#is? test.last 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
|
||||
@ -135,3 +137,8 @@
|
||||
; ====
|
||||
|
||||
(entity) @constant.character.entity.html
|
||||
|
||||
; Helpers for autocomplete-html
|
||||
|
||||
((element) @meta.tag.incomplete.html
|
||||
(#not-match? @meta.tag.incomplete.html ">$"))
|
||||
|
@ -1,7 +1,7 @@
|
||||
|
||||
((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"))
|
||||
(#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.
|
||||
|
Binary file not shown.
@ -1,4 +1,4 @@
|
||||
exports.activate = function() {
|
||||
exports.activate = function () {
|
||||
atom.grammars.addInjectionPoint('text.html.basic', {
|
||||
type: 'script_element',
|
||||
language() {
|
||||
@ -19,38 +19,6 @@ 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', {
|
||||
@ -95,3 +63,14 @@ exports.activate = function() {
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
exports.consumeHyperlinkInjection = (hyperlink) => {
|
||||
// TODO: Inject hyperlink grammar into plain text?
|
||||
hyperlink.addInjectionPoint('text.html.basic', {
|
||||
types: ['comment', 'attribute_value']
|
||||
});
|
||||
};
|
||||
|
||||
exports.consumeTodoInjection = (todo) => {
|
||||
todo.addInjectionPoint('text.html.basic', { types: ['comment'] });
|
||||
};
|
||||
|
@ -19,5 +19,17 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"dedent": "^0.7.0"
|
||||
},
|
||||
"consumedServices": {
|
||||
"hyperlink.injection": {
|
||||
"versions": {
|
||||
"0.1.0": "consumeHyperlinkInjection"
|
||||
}
|
||||
},
|
||||
"todo.injection": {
|
||||
"versions": {
|
||||
"0.1.0": "consumeTodoInjection"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user