Merge branch 'master' into pr/264

This commit is contained in:
confused-Techie 2023-08-29 19:09:21 -07:00
commit 338361803d
819 changed files with 131168 additions and 53403 deletions

View File

@ -1,57 +1,66 @@
env:
PYTHON_VERSION: 3.10
GITHUB_TOKEN: ENCRYPTED[!b0ff4671044672be50914a3a10b49af642bd8e0e681a6f4e5855ec5230a5cf9afbc53d9e90239b8d2c79455f014f383f!]
# The above token, is a GitHub API Token, that allows us to download RipGrep without concern of API limits
linux_task:
alias: linux
container:
image: node:16-slim
memory: 8G
prepare_script:
- apt-get update
- export DEBIAN_FRONTEND="noninteractive"
- apt-get install -y
rpm
build-essential
git
libsecret-1-dev
fakeroot
libx11-dev
libxkbfile-dev
libgdk-pixbuf2.0-dev
libgtk-3-dev
libxss-dev
libasound2-dev
libnss3
xvfb
- git submodule init
- git submodule update
- sed -i -e "s/[0-9]*-dev/`date -u +%Y%m%d%H`/g" package.json
install_script:
- yarn install || yarn install
build_script:
- yarn build
- yarn run build:apm
build_binary_script:
- yarn dist || yarn dist
binary_artifacts:
path: ./binaries/*
test_script:
- Xvfb :99 & DISPLAY=:99 PLAYWRIGHT_JUNIT_OUTPUT_NAME=report.xml npx playwright test --reporter=junit,list
always:
videos_artifacts:
path: ./tests/videos/**
junit_artifacts:
path: report.xml
type: text/xml
format: junit
# linux_task:
# alias: linux
# container:
# image: node:16-slim
# memory: 8G
# prepare_script:
# - apt-get update
# - export DEBIAN_FRONTEND="noninteractive"
# - apt-get install -y
# ffmpeg
# rpm
# build-essential
# git
# libsecret-1-dev
# fakeroot
# libx11-dev
# libxkbfile-dev
# libgdk-pixbuf2.0-dev
# libgtk-3-dev
# libxss-dev
# libasound2-dev
# libnss3
# xvfb
# - git submodule init
# - git submodule update
# - sed -i -e "s/[0-9]*-dev/`date -u +%Y%m%d%H`/g" package.json
# install_script:
# - yarn install --ignore-engines || yarn install --ignore-engines
# build_script:
# - yarn build
# - yarn run build:apm
# build_binary_script:
# - yarn dist || yarn dist
# binary_artifacts:
# path: ./binaries/*
# test_script:
# - 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
# always:
# videos_artifacts:
# path: ./tests/videos/**
# junit_artifacts:
# path: report.xml
# type: text/xml
# format: junit
arm_linux_task:
alias: linux
only_if: $CIRRUS_CRON != "" || $CIRRUS_TAG == "regular_release"
arm_container:
image: node:16-slim
memory: 8G
env:
USE_SYSTEM_FPM: 'true'
ROLLING_UPLOAD_TOKEN: ENCRYPTED[690950798401ec3715e9d20ac29a0859d3c58097038081ff6afeaf4721e661672d34eb952d8a6442bc7410821ab8545a]
prepare_script:
- apt-get update
- export DEBIAN_FRONTEND="noninteractive"
@ -59,6 +68,7 @@ arm_linux_task:
gnupg2
procps
curl
ruby
rpm
build-essential
git
@ -72,27 +82,28 @@ arm_linux_task:
libasound2-dev
libnss3
xvfb
- gpg2 --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB
- \curl -sSL https://get.rvm.io | bash -s stable
- source /etc/profile.d/rvm.sh
- rvm install ruby
- gem install fpm
- git submodule init
- git submodule update
- sed -i -e "s/[0-9]*-dev/`date -u +%Y%m%d%H`/g" package.json
install_script:
- yarn install || yarn install
- yarn install --ignore-engines || yarn install --ignore-engines
build_script:
- yarn build
- yarn run build:apm
- rm -Rf node-modules/electron && yarn install --check-files
build_binary_script:
- source /etc/profile.d/rvm.sh
- yarn dist || yarn dist
binary_artifacts:
path: ./binaries/*
test_script:
- rm -R node_modules/electron; yarn install --check-files
- ./binaries/*AppImage --appimage-extract
- export BINARY_NAME='squashfs-root/pulsar'
- Xvfb :99 & DISPLAY=:99 PLAYWRIGHT_JUNIT_OUTPUT_NAME=report.xml npx playwright test --reporter=junit,list
rolling_upload_script:
- cd ./script/rolling-release-scripts
- npm install
- node ./rolling-release-binary-upload.js cirrus
always:
videos_artifacts:
path: ./tests/videos/**
@ -103,6 +114,7 @@ arm_linux_task:
silicon_mac_task:
alias: mac
only_if: $CIRRUS_CRON != "" || $CIRRUS_TAG == "regular_release"
macos_instance:
image: ghcr.io/cirruslabs/macos-monterey-xcode:14
memory: 8G
@ -112,6 +124,7 @@ silicon_mac_task:
APPLEID: ENCRYPTED[549ce052bd5666dba5245f4180bf93b74ed206fe5e6e7c8f67a8596d3767c1f682b84e347b326ac318c62a07c8844a57]
APPLEID_PASSWORD: ENCRYPTED[774c3307fd3b62660ecf5beb8537a24498c76e8d90d7f28e5bc816742fd8954a34ffed13f9aa2d1faf66ce08b4496e6f]
TEAM_ID: ENCRYPTED[11f3fedfbaf4aff1859bf6c105f0437ace23d84f5420a2c1cea884fbfa43b115b7834a463516d50cb276d4c4d9128b49]
ROLLING_UPLOAD_TOKEN: ENCRYPTED[690950798401ec3715e9d20ac29a0859d3c58097038081ff6afeaf4721e661672d34eb952d8a6442bc7410821ab8545a]
prepare_script:
- brew install node@16 yarn git python@$PYTHON_VERSION
- git submodule init
@ -121,7 +134,7 @@ silicon_mac_task:
- 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"
- yarn install || yarn install
- yarn install --ignore-engines || yarn install --ignore-engines
build_script:
- export PATH="/opt/homebrew/bin:/opt/homebrew/opt/node@16/bin:$PATH"
- yarn build
@ -133,7 +146,15 @@ silicon_mac_task:
path: ./binaries/*
test_script:
- export PATH="/opt/homebrew/bin:/opt/homebrew/opt/node@16/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"
- cd ./script/rolling-release-scripts
- npm install
- node ./rolling-release-binary-upload.js cirrus
always:
videos_artifacts:
path: ./tests/videos/**
@ -142,72 +163,89 @@ silicon_mac_task:
type: text/xml
format: junit
intel_mac_task:
alias: mac
macos_instance:
image: ghcr.io/cirruslabs/macos-monterey-xcode:14
memory: 8G
env:
CSC_LINK: ENCRYPTED[0078015a03bb6cfdbd80113ae5bbb6f448fd4bbbc40efd81bf2cb1554373046b475a4d7c77e3e3e82ac1ce2f7e3d2da5]
CSC_KEY_PASSWORD: ENCRYPTED[82bb72653d39578035ed1860ab4978703d50bd326d925a146ff08782f987ceb37ac2d8dbace52dec2b0e2ef92debf097]
APPLEID: ENCRYPTED[549ce052bd5666dba5245f4180bf93b74ed206fe5e6e7c8f67a8596d3767c1f682b84e347b326ac318c62a07c8844a57]
APPLEID_PASSWORD: ENCRYPTED[774c3307fd3b62660ecf5beb8537a24498c76e8d90d7f28e5bc816742fd8954a34ffed13f9aa2d1faf66ce08b4496e6f]
TEAM_ID: ENCRYPTED[11f3fedfbaf4aff1859bf6c105f0437ace23d84f5420a2c1cea884fbfa43b115b7834a463516d50cb276d4c4d9128b49]
prepare_script:
- sudo rm -rf /Library/Developer/CommandLineTools
- 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"
- arch -x86_64 brew install node@16 yarn git python@$PYTHON_VERSION
- ln -s /usr/local/bin/python$PYTHON_VERSION /usr/local/bin/python
- 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"
- arch -x86_64 npx yarn install || arch -x86_64 npx yarn install
build_script:
- export PATH="/usr/local/opt/node@16/bin:/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"
- arch -x86_64 npx yarn dist || arch -x86_64 npx yarn dist
binary_artifacts:
path: ./binaries/*
test_script:
- export PATH="/usr/local/opt/node@16/bin:/usr/local/bin:$PATH"
- PLAYWRIGHT_JUNIT_OUTPUT_NAME=report.xml arch -x86_64 npx playwright test --reporter=junit,list
always:
videos_artifacts:
path: ./tests/videos/**
junit_artifacts:
path: report.xml
type: text/xml
format: junit
# intel_mac_task:
# alias: mac
# macos_instance:
# image: ghcr.io/cirruslabs/macos-monterey-xcode:14
# memory: 8G
# env:
# CSC_LINK: ENCRYPTED[0078015a03bb6cfdbd80113ae5bbb6f448fd4bbbc40efd81bf2cb1554373046b475a4d7c77e3e3e82ac1ce2f7e3d2da5]
# CSC_KEY_PASSWORD: ENCRYPTED[82bb72653d39578035ed1860ab4978703d50bd326d925a146ff08782f987ceb37ac2d8dbace52dec2b0e2ef92debf097]
# APPLEID: ENCRYPTED[549ce052bd5666dba5245f4180bf93b74ed206fe5e6e7c8f67a8596d3767c1f682b84e347b326ac318c62a07c8844a57]
# APPLEID_PASSWORD: ENCRYPTED[774c3307fd3b62660ecf5beb8537a24498c76e8d90d7f28e5bc816742fd8954a34ffed13f9aa2d1faf66ce08b4496e6f]
# TEAM_ID: ENCRYPTED[11f3fedfbaf4aff1859bf6c105f0437ace23d84f5420a2c1cea884fbfa43b115b7834a463516d50cb276d4c4d9128b49]
# prepare_script:
# - sudo rm -rf /Library/Developer/CommandLineTools
# - 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"
# - arch -x86_64 brew install node@16 yarn git python@$PYTHON_VERSION
# - ln -s /usr/local/bin/python$PYTHON_VERSION /usr/local/bin/python
# - 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"
# - 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"
# - 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"
# - arch -x86_64 npx yarn dist || arch -x86_64 npx yarn dist
# binary_artifacts:
# path: ./binaries/*
# test_script:
# - export PATH="/usr/local/opt/node@16/bin:/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`
# - PLAYWRIGHT_JUNIT_OUTPUT_NAME=report.xml arch -x86_64 npx playwright test --reporter=junit,list
# always:
# videos_artifacts:
# path: ./tests/videos/**
# junit_artifacts:
# path: report.xml
# type: text/xml
# format: junit
windows_task:
alias: windows
windows_container:
image: cirrusci/windowsservercore:visualstudio2022-2022.06.23
env:
CIRRUS_SHELL: bash
PATH: C:\Python310\Scripts\;C:\Python310\;%PATH%;C:\Program Files\nodejs\;C:\Program Files\Git\cmd;C:\Users\User\AppData\Local\Microsoft\WindowsApps;C:\Users\User\AppData\Roaming\npm;C:\Program Files\Microsoft Visual Studio\2022\Community\Msbuild\Current\Bin\
prepare_script:
- choco install nodejs --version=14.15.0 -y
- choco install python --version=3.10.3 -y
- choco install git visualstudio2019-workload-vctools -y
- git submodule init
- git submodule update
- npm config set python 'C:\Python310\python.exe'
install_script:
- npx yarn install --ignore-engines || sleep 1 && npx yarn install --ignore-engines || echo "There is a reason for so many tries"
build_script:
- npx yarn build:apm
- npx yarn build || npx yarn build || npx yarn build
build_binary_script:
- sed -i -e "s/[0-9]*-dev/`date -u +%Y%m%d%H`/g" package.json
- npx yarn dist || npx yarn dist || npx yarn dist
binary_artifacts:
path: .\binaries\*
# windows_task:
# alias: windows
# timeout_in: 90m
# windows_container:
# image: cirrusci/windowsservercore:visualstudio2022-2022.06.23
# env:
# CIRRUS_SHELL: bash
# PATH: C:\Python310\Scripts\;C:\Python310\;%PATH%;C:\Program Files\nodejs\;C:\Program Files\Git\cmd;C:\Users\User\AppData\Local\Microsoft\WindowsApps;C:\Users\User\AppData\Roaming\npm;C:\Program Files\Microsoft Visual Studio\2022\Community\Msbuild\Current\Bin\
# prepare_script:
# - choco install nodejs --version=16.16.0 -y
# - choco install python --version=3.10.3 -y
# - choco install git visualstudio2019-workload-vctools -y
# - git submodule init
# - git submodule update
# - npm config set python 'C:\Python310\python.exe'
# install_script:
# - npx yarn install --ignore-engines
# || rm -R node_modules && npx yarn install --ignore-engines
# || rm -R node_modules && npx yarn install --ignore-engines
# build_script:
# - npx yarn build:apm
# - npx yarn build || npx yarn build || npx yarn build
# build_binary_script:
# - sed -i -e "s/[0-9]*-dev/`date -u +%Y%m%d%H`/g" package.json
# - npx yarn dist || npx yarn dist || npx yarn dist
# binary_artifacts:
# path: .\binaries\*
# test_script:
# - mkdir extracted; tar -xf binaries/*zip -C ./extracted/
# - export BINARY_NAME=./extracted/Pulsar.exe
# - PLAYWRIGHT_JUNIT_OUTPUT_NAME=report.xml npx playwright test --reporter=junit,list || echo "Yeah, tests failed, Windows is like this"
# always:
# videos_artifacts:
# path: ./tests/videos/**
# junit_artifacts:
# path: report.xml
# type: text/xml
# format: junit

View File

@ -7,13 +7,18 @@ module.exports = {
extends: [
"eslint:recommended",
"plugin:node/recommended",
"plugin:jsdoc/recommended"
// "plugin:jsdoc/recommended"
],
overrides: [],
parserOptions: {
ecmaVersion: "latest"
},
rules: {
"space-before-function-paren": ["error", {
anonymous: "always",
asyncArrow: "always",
named: "never"
}],
"node/no-unpublished-require": [
"error",
{

2
.gitattributes vendored
View File

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

174
.github/renovate.json vendored
View File

@ -1,20 +1,178 @@
{
"schedule": ["every weekend"],
"extends": [ "config:base", ":dependencyDashboardApproval"],
"constraints": {
"node": "< 16"
},
"labels": ["dependencies"],
"separateMajorMinor": "false",
"ignorePaths": [
"packages/autocomplete-atom-api/spec/fixtures/",
"packages/dev-live-reload/spec/fixtures/",
"packages/incompatible-packages/spec/fixtures/",
"packages/settings-view/spec/fixtures/",
"spec/fixtures/"
],
"packageRules": [
{
"description": "Group all DevDependencies for the Core Editor",
"matchDepTypes": ["devDependencies"],
"matchUpdateTypes": ["major", "minor", "patch", "pin", "digest", "lockFileMaintenance", "rollback", "bump"],
"groupName": "devDependencies",
"semanticCommitType": "chore",
"automerge": true
"groupName": "Core DevDependencies",
"matchPaths": ["package.json"]
},
{
"description": "Group all Dependencies for the Core Editor",
"matchDepTypes": ["dependencies"],
"matchUpdateTypes": ["major", "minor", "patch", "pin", "digest", "lockFileMaintenance", "rollback", "bump"],
"groupName": "dependencies",
"semanticCommitType": "fix"
"groupName": "Core Dependencies",
"matchPaths": ["package.json"]
},
{
"description": "Group all of our Syntax Themes and UI Themes",
"groupName": "Core Syntax & UI Themes",
"matchPaths": [
"packages/*-syntax/**", "packages/*-ui/**", "packages/*-theme/**"
]
},
{
"description": "Group all of our Languages",
"groupName": "Core Languages",
"matchPaths": [
"packages/language-*/**"
]
},
{
"matchPaths": [ "packages/about/**" ], "groupName": "about package"
},
{
"matchPaths": [ "packages/archive-view/**" ], "groupName": "archive-view package"
},
{
"matchPaths": [ "packages/autocomplete-atom-api/**" ], "groupName": "autocomplete-atom-api"
},
{
"matchPaths": [ "packages/autocomplete-css/**" ], "groupName": "autocomplete-css package"
},
{
"matchPaths": [ "packages/autocomplete-html/**" ], "groupName": "autocomplete-html package"
},
{
"matchPaths": [ "packages/autocomplete-plus/**" ], "groupName": "autocomplete-plus package"
},
{
"matchPaths": [ "packages/autocomplete-snippets/**" ], "groupName": "autocomplete-snippets package"
},
{
"matchPaths": [ "packages/autoflow/**" ], "groupName": "autoflow package"
},
{
"matchPaths": [ "packages/autosave/**" ], "groupName": "autosave package"
},
{
"matchPaths": [ "packages/background-tips/**" ], "groupName": "background-tips package"
},
{
"matchPaths": [ "packages/bookmarks/**" ], "groupName": "bookmarks package"
},
{
"matchPaths": [ "packages/bracket-matcher/**" ], "groupName": "bracket-matcher package"
},
{
"matchPaths": [ "packages/command-palette/**" ], "groupName": "command-palette package"
},
{
"matchPaths": [ "packages/dalek/**" ], "groupName": "dalek package"
},
{
"matchPaths": [ "packages/deprecation-cop/**" ], "groupName": "deprecation-cop package"
},
{
"matchPaths": [ "packages/dev-live-reload/**" ], "groupName": "dev-live-reload package"
},
{
"matchPaths": [ "packages/encoding-selector/**" ], "groupName": "encoding-selector package"
},
{
"matchPaths": [ "packages/exception-reporting/**" ], "groupName": "exception-reporting package"
},
{
"matchPaths": [ "packages/find-and-replace/**" ], "groupName": "find-and-replace package"
},
{
"matchPaths": [ "packages/fuzzy-finder/**" ], "groupName": "fuzzy-finder package"
},
{
"matchPaths": [ "packages/git-diff/**" ], "groupName": "git-diff package"
},
{
"matchPaths": [ "packages/go-to-line/**" ], "groupName": "go-to-line package"
},
{
"matchPaths": [ "packages/grammar-selector/**" ], "groupName": "grammar-selector package"
},
{
"matchPaths": [ "packages/image-view/**" ], "groupName": "image-view package"
},
{
"matchPaths": [ "packages/incompatible-packages/**" ], "groupName": "incompatible-packages package"
},
{
"matchPaths": [ "packages/keybinding-resolver/**" ], "groupName": "keybinding-resolver package"
},
{
"matchPaths": [ "packages/line-ending-selector/**" ], "groupName": "line-ending-selector package"
},
{
"matchPaths": [ "packages/link/**" ], "groupName": "link package"
},
{
"matchPaths": [ "packages/markdown-preview/**" ], "groupName": "markdown-preview package"
},
{
"matchPaths": [ "packages/notifications/**" ], "groupName": "notifications package"
},
{
"matchPaths": [ "packages/open-on-github/**" ], "groupName": "open-on-github package"
},
{
"matchPaths": [ "packages/package-generator/**" ], "groupName": "package-generator package"
},
{
"matchPaths": [ "packages/pulsar-updater/**" ], "groupName": "pulsar-updater package"
},
{
"matchPaths": [ "packages/settings-view/**" ], "groupName": "settings-view package"
},
{
"matchPaths": [ "packages/spell-check/**" ], "groupName": "spell-check package"
},
{
"matchPaths": [ "packages/status-bar/**" ], "groupName": "status-bar package"
},
{
"matchPaths": [ "packages/styleguide/**" ], "groupName": "styleguide package"
},
{
"matchPaths": [ "packages/symbols-view/**" ], "groupName": "symbols-view package"
},
{
"matchPaths": [ "packages/tabs/**" ], "groupName": "tabs package"
},
{
"matchPaths": [ "packages/timecop/**" ], "groupName": "timecop package"
},
{
"matchPaths": [ "packages/tree-view/**" ], "groupName": "tree-view package"
},
{
"matchPaths": [ "packages/update-package-dependencies/**" ], "groupName": "update-package-dependencies package"
},
{
"matchPaths": [ "packages/welcome/**" ], "groupName": "welcome package"
},
{
"matchPaths": [ "packages/whitespace/**" ], "groupName": "whitespace package"
},
{
"matchPaths": [ "packages/wrap-guide/**" ], "groupName": "wrap-guide package"
}
]
}

145
.github/workflows/build.yml vendored Normal file
View File

@ -0,0 +1,145 @@
name: Build Pulsar Binaries
on:
push:
branches:
- 'master'
pull_request:
workflow_dispatch:
env:
# Variables needed for build information
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PYTHON_VERSION: '3.10'
NODE_VERSION: 16
ROLLING_UPLOAD_TOKEN: ${{ secrets.ROLLING_RELEASE_UPLOAD_TOKEN }}
# Below variables allow us to quickly control visual tests for each platform
RUN_WINDOWS_VT: false
RUN_LINUX_VT: true
RUN_MACOS_VT: true
jobs:
build:
strategy:
matrix:
os: [ ubuntu-latest, windows-latest, macos-latest ]
fail-fast: false
runs-on: ${{ matrix.os }}
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: Setup Python
uses: actions/setup-python@v4
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Setup Git Submodule
run: |
git submodule init
git submodule update
- 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
- name: Check Pulsar Version - Windows
if: ${{ runner.os == 'Windows' }}
run: (Get-Content package.json) -replace '[0-9]*-dev', (date -u +%Y%m%d%H) | Set-Content -Path package.json
- name: Install Pulsar Dependencies
uses: nick-fields/retry@943e742917ac94714d2f408a0e8320f2d1fcafcd
with:
timeout_minutes: 30
max_attempts: 3
retry_on: error
command: yarn install --ignore-engines
on_retry_command: rm -R node_modules
- name: Build Pulsar
uses: nick-fields/retry@943e742917ac94714d2f408a0e8320f2d1fcafcd
with:
timeout_minutes: 30
max_attempts: 3
retry_on: error
command: |
yarn build
yarn run build:apm
# macOS Signing Stuff
- name: Build Pulsar Binaries (macOS)
if: ${{ runner.os == 'macOS' }}
env:
CSC_LINK: ${{ secrets.CSC_LINK }}
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
APPLEID: ${{ secrets.APPLEID }}
APPLEID_PASSWORD: ${{ secrets.APPLEID_PASSWORD }}
TEAM_ID: ${{ secrets.TEAM_ID }}
uses: nick-fields/retry@943e742917ac94714d2f408a0e8320f2d1fcafcd
with:
timeout_minutes: 30
max_attempts: 3
retry_on: error
command: yarn dist
- name: Build Pulsar Binaries
if: ${{ runner.os != 'macOS' }}
uses: nick-fields/retry@943e742917ac94714d2f408a0e8320f2d1fcafcd
with:
timeout_minutes: 30
max_attempts: 3
retry_on: error
command: yarn dist
- 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
run: |
mkdir extracted; tar -xf binaries/*zip -C ./extracted/
export BINARY_NAME=./extracted/Pulsar.exe
PLAYWRIGHT_JUNIT_OUTPUT_NAME=report.xml npx playwright test --reporter=junit,list || echo "Yeah, tests failed, Windows is like this"
- name: Test Binary - macOS
if: runner.os == 'macOS' && env.RUN_MACOS_VT == true
run: |
export PATH="/usr/local/opt/node@16/bin:/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`
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' }}
# 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
uses: actions/upload-artifact@v3
with:
name: ${{ matrix.os }} Videos
path: ./tests/videos/**

View File

@ -4,7 +4,7 @@ on:
push:
branches: [ "master" ]
workflow_dispatch:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -1,13 +1,15 @@
name: Editor tests
on:
- pull_request
pull_request:
push:
branches: ['master']
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ATOM_JASMINE_REPORTER: list
jobs:
tests:
name: tests
name: Tests
if: |
!startsWith(github.event.pull_request.title, '[skip-ci]') &&
!startsWith(github.event.pull_request.title, '[skip-editor-ci]')
@ -19,20 +21,20 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- name: Checkout the latest code
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Setup node
uses: actions/setup-node@v2-beta
uses: actions/setup-node@v3
with:
node-version: 16
- name: install dependencies
- name: Install Dependencies
run: yarn install
- name: build dependencies
- name: Build Dependencies
run: yarn build
- name: Run tests
uses: GabrielBB/xvfb-action@v1
- name: Run Tests
uses: coactions/setup-xvfb@v1.0.1
with:
run: node script/run-tests.js spec

View File

@ -1,6 +1,8 @@
name: Package tests for Pulsar on Linux
name: Package tests
on:
- pull_request
pull_request:
push:
branches: ['master']
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ATOM_JASMINE_REPORTER: list
@ -13,10 +15,10 @@ jobs:
runs-on: ubuntu-20.04
steps:
- name: Checkout the latest code
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Setup node
uses: actions/setup-node@v2-beta
uses: actions/setup-node@v3
with:
node-version: 16
@ -39,6 +41,15 @@ jobs:
path: pulsar.deb
key: pulsar-${{ github.sha }}
- name: Cache dependencies
id: cache-dependencies
uses: actions/cache@v3
with:
path: |
node_modules
packages
key: dependencies-${{ github.sha }}
test:
name: Package
needs: setup
@ -93,12 +104,13 @@ jobs:
- package: "notifications"
- package: "open-on-github"
- package: "package-generator"
- package: "pulsar-updater"
- package: "settings-view"
- package: "snippets"
- package: "spell-check"
- package: "status-bar"
- package: "styleguide"
- package: "symbols-view"
# - package: "symbols-view"
- package: "tabs"
- package: "timecop"
- package: "tree-view"
@ -143,15 +155,27 @@ jobs:
steps:
- name: Checkout the latest code
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Setup NodeJS
uses: actions/setup-node@v3
with:
node-version: 16
- name: Restore dependencies from Cache
id: restore-dependencies
uses: actions/cache@v3
with:
path: |
node_modules
packages
key: dependencies-${{ github.sha }}
- name: Install Dependencies
if: steps.restore-dependencies.outputs.cache-hit != 'true'
run: yarn install || yarn install
- name: Build Dependencies
if: steps.restore-dependencies.outputs.cache-hit != 'true'
run: yarn build || yarn build
- name: Restore pulsar from Cache

2
.gitignore vendored
View File

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

2
.yarnrc Normal file
View File

@ -0,0 +1,2 @@
enableTelemetry false
ignore-engines true

View File

@ -6,11 +6,227 @@
## [Unreleased]
## 1.108.0
- Restored ability for `less` files in packages to use inline JavaScript inside backticks.
- Fixed a syntax highlighting issue inside the `styleguide` package.
- Fixed an issue with rubygems timing out on ARM Linux workflow.
- Rewrote Tree-sitter scope predicates to use `#is?` and `#is-not?` where applicable.
- Ensure that project-specific setting overrides don't leak to the user's config file when the settings UI is visited.
- Added a feature in `markdown-preview` that adds support for Linguist, Chroma, Rouge, and HighlightJS for language identifiers in fenced code blocks.
- Fixed the `TextMate` `language-toml` grammar to properly support whitespace where-ever it may appear.
- Added a Tree-Sitter grammar for YAML files.
- Added a new core package `pulsar-updater` to help users update Pulsar.
- Added `ppm` and `ppm.cmd` binaries/launchers within ppm. This allows easier integration of correctly named binaries on more systems in more contexts (especially Windows). Existing `apm` and `apm.cmd` binaries/launchers are still there for the time being.
- Added a modern Tree-Sitter grammar for Markdown files.
### Pulsar
- Added: Add the Tree-Sitter Markdown grammar [@savetheclocktower](https://github.com/pulsar-edit/pulsar/pull/659)
- Fixed: [pulsar-updater] Correct deb-get instructions ( + readme change) [@Daeraxa](https://github.com/pulsar-edit/pulsar/pull/669)
- Added: Tree-sitter running fixes [@savetheclocktower](https://github.com/pulsar-edit/pulsar/pull/660)
- Added: Add `pulsar-updater` as a core bundled Package [@confused-Techie](https://github.com/pulsar-edit/pulsar/pull/656)
- Added: Manual Decaf Bundle (`autocomplete-atom-api`, `autoflow`, `deprecation-cop`) Source [@confused-Techie](https://github.com/pulsar-edit/pulsar/pull/664)
- Bumped: [Time Sensitive] Update Cirrus Encrypted token for GitHub Access [@confused-Techie](https://github.com/pulsar-edit/pulsar/pull/666)
- Added: [core]: Transforming Deprecated Math Usage - Support for Variables [@confused-Techie](https://github.com/pulsar-edit/pulsar/pull/653)
- Added: Add Tree-sitter grammar for YAML [@savetheclocktower](https://github.com/pulsar-edit/pulsar/pull/634)
- Fixed: [language-toml] Add whitespace rule to values [@arite](https://github.com/pulsar-edit/pulsar/pull/646)
- Added: [markdown-preview]: Support for nested table objects in Yaml Frontmatter [@confused-Techie](https://github.com/pulsar-edit/pulsar/pull/629)
- Added: [markdown-preview]: Revamp Fenced Code Block Language Identifiers [@confused-Techie](https://github.com/pulsar-edit/pulsar/pull/622)
- Bumped: ppm: Update submodule to 49c8ced8f9552bb4aeb279130 [@DeeDeeG](https://github.com/pulsar-edit/pulsar/pull/654)
- Fixed: [settings-view] Don't let project-specific settings pollute the UI [@savetheclocktower](https://github.com/pulsar-edit/pulsar/pull/655)
- Added: [modern-tree-sitter] Overhaul Tree-sitter scope tests [@savetheclocktower](https://github.com/pulsar-edit/pulsar/pull/652)
- Fixed: fix(arm): use rubygems from APT [@cat-master21](https://github.com/pulsar-edit/pulsar/pull/651)
- Added: [language-*]: Manual Spec Decaf (Part 1) [@confused-Techie](https://github.com/pulsar-edit/pulsar/pull/632)
- Fixed: [styleguide] Fix error when styleguide is shown... [@savetheclocktower](https://github.com/pulsar-edit/pulsar/pull/648)
- Bumped: Bump `less-cache` to 2.0.1 [@savetheclocktower](https://github.com/pulsar-edit/pulsar/pull/644)
### ppm
- Added: Add 'ppm' bins to complement existing 'apm' bins [@DeeDeeG](https://github.com/pulsar-edit/ppm/pull/80)
- Fixed: Replace "apm" by "ppm" in help messages. [@azuledu](https://github.com/pulsar-edit/ppm/pull/62)
- Bumped: Update OS, actions, node [@Spiker985](https://github.com/pulsar-edit/ppm/pull/57)
## 1.107.1
- Updated the `github` package to resolve incompatibility in Style Sheets against Less v4
### Pulsar
- Bumped: deps: Update github package to v0.36.17-pretranspiled [@DeeDeeG](https://github.com/pulsar-edit/pulsar/pull/639)
### github
- Fixed: Fix Less Syntax [@confused-Techie](https://github.com/pulsar-edit/github/pull/34)
## 1.107.0
- Fixed a number of issues with the experimental modern Tree-sitter grammar mode
- Pulsar can now be added to the PATH on Windows, via the "System" pane within Settings View.
- Bumped `less-cache` to `v2.0.0` which uses `less@4.1.3`. This adds many new features of Less, while causing breaking changes to existing Less StyleSheets. Read more about these changes [here](https://github.com/pulsar-edit/less-cache/releases/tag/v2.0.0). Pulsar will attempt to automatically repair any breaking changes in any package style sheets, while emitting deprecations.
- Fixed a bug that would render files unable to be clicked with sticky headers enabled on One-Dark and One-Light themes.
- Added a Modern Tree-Sitter TOML Grammar.
- Added a new API endpoint within Pulsar of `atom.versionSatisifes()` to allow packages to safely check the version of Pulsar, instead of having to do so themselves.
- An issue in a downstream dependency has been resolved that improperly flagged Pulsar as malicious.
### Pulsar
- Added: Improved Windows Install (`PATH`, `ATOM_HOME`, `InstallLocation`) [@confused-Techie](https://github.com/pulsar-edit/pulsar/pull/604)
- Fixed: Running PR for Tree-Sitter fixes [@savetheclocktower](https://github.com/pulsar-edit/pulsar/pull/555)
- Added: [autocomplete-css]: Manual Decaf of Source [@confused-Techie](https://github.com/pulsar-edit/pulsar/pull/631)
- Fixed: [welcome]: Ensure Changelog Always Shows if enabled, and version hasn't been dismissed [@confused-Techie](https://github.com/pulsar-edit/pulsar/pull/607)
- Bumped: [autocomplete-plus] Maintenance - Deps bumps, remove CoffeeScript files [@confused-Techie](https://github.com/pulsar-edit/pulsar/pull/630)
- Fixed: Fix tree-view sticky headers of one-dark & one-light themes [@asiloisad](https://github.com/pulsar-edit/pulsar/pull/599)
- Fixed: [spell-check]: Remove usage of reserved word [@confused-Techie](https://github.com/pulsar-edit/pulsar/pull/620)
- Added: [core]: Implement API on `atom.` to compare Pulsar Versions [@confused-Techie](https://github.com/pulsar-edit/pulsar/pull/588)
- Added: [settings-view]: Manual Decaf (source) [@confused-Techie](https://github.com/pulsar-edit/pulsar/pull/558)
- Bumped: [core]: Bump `less-cache` to `v2.0.0` Upgrades `less` to `4.1.3` [@confused-Techie](https://github.com/pulsar-edit/pulsar/pull/611)
- Added: [core]: Bundle `spell-check` [@confused-Techie](https://github.com/pulsar-edit/pulsar/pull/614)
- Bumped: Update dependency semver to v7.5.2 [SECURITY] [@renovate](https://github.com/pulsar-edit/pulsar/pull/609)
- Added: [modern-tree-sitter] Add TOML tree-sitter grammar [@savetheclocktower](https://github.com/pulsar-edit/pulsar/pull/617)
- Fixed: [language-toml]: Allow spaces within Array [@confused-Techie](https://github.com/pulsar-edit/pulsar/pull/610)
- Fixed: Pin `es5-ext` to `pulsar-edit/es5-ext` removing code flagged as malicious [@confused-Techie](https://github.com/pulsar-edit/pulsar/pull/608)
- Bumped: [git-diff] Bump all Deps [@confused-Techie](https://github.com/pulsar-edit/pulsar/pull/603)
- Bumped: Update dependency semver [SECURITY] [@renovate](https://github.com/pulsar-edit/pulsar/pull/605)
- Fixed: [autocomplete-css] Get tests passing for new CSS tree-sitter grammar [@savetheclocktower](https://github.com/pulsar-edit/pulsar/pull/601)
- Bumped: [dalek] Bump dependencies to latest, fix links [@confused-Techie](https://github.com/pulsar-edit/pulsar/pull/602)
- Bumped: Update dependency marked to v5.0.3 [@mauricioszabo](https://github.com/pulsar-edit/pulsar/pull/568)
### less-cache
- Bumped: Bump `1.1.1` => `2.0.0` [@confused-Techie](https://github.com/pulsar-edit/less-cache/pull/5)
- Bumped: Bump `less` `3.12.2` => `4.1.3` [@confused-Techie](https://github.com/pulsar-edit/less-cache/pull/4)
- Added: Repository Cleanup + CoffeeScript tool Removal (Depends on #2) [@confused-Techie](https://github.com/pulsar-edit/less-cache/pull/3)
- Added: Manual decaf of source files [@confused-Techie](https://github.com/pulsar-edit/less-cache/pull/2)
- Added: Implement Repo Tests [@confused-Techie](https://github.com/pulsar-edit/less-cache/pull/1)
## 1.106.0
- Fixed bug that happens on some systems when trying to launch Pulsar using the Cinnamon desktop environment
- Added a modern implementation of Tree-sitter grammars behind an experimental flag. Enable the “Use Modern Tree-Sitter Implementation” in the Core settings to try it out
- Bugfix: fixed Clojure indentation on tree-sitter
- Improved the Clojure language support by migrating it to tree-sitter and support block comments, quoting, and other advanced features on modern tree-sitter implementation
- Fixed a bug that could cause images to not appear the first time opening them
- `autocomplete-css` Completions are now sorted in a way that may match what users expect
- Added a "Log Out" menu item for the `github` package
### Pulsar
- Updated: deps: Bump github to v0.36.16-pretranspiled [@DeeDeeG](https://github.com/pulsar-edit/pulsar/pull/592)
- Removed: Mostly remove `request` [@confused-Techie](https://github.com/pulsar-edit/pulsar/pull/474)
- Fixed: Fix: Image doesn't appear at first open [@asiloisad](https://github.com/pulsar-edit/pulsar/pull/579)
- Removed: Remove specific cinnamon condition [@mauricioszabo](https://github.com/pulsar-edit/pulsar/pull/563)
- Fixed: Fix of Clojure's indentation rules by removing query file [@mauricioszabo](https://github.com/pulsar-edit/pulsar/pull/584)
- Fixed: Update links in settings page [@Daeraxa](https://github.com/pulsar-edit/pulsar/pull/570)
- Added: [autocomplete-css] Sort `completions.json` [@confused-Techie](https://github.com/pulsar-edit/pulsar/pull/552)
- Fixed: Fixes on "comment block" for Clojure grammar [@mauricioszabo](https://github.com/pulsar-edit/pulsar/pull/573)
- Added: Hardcode NSIS GUID [@confused-Techie](https://github.com/pulsar-edit/pulsar/pull/566)
- Fixed: Make yarn sane [@mauricioszabo](https://github.com/pulsar-edit/pulsar/pull/567)
- Fixed: Huge improvement on Clojure highlighting [@mauricioszabo](https://github.com/pulsar-edit/pulsar/pull/553)
- Removed: Removed unused_require method [@mauricioszabo](https://github.com/pulsar-edit/pulsar/pull/561)
- Bumped: Update dependency underscore to 1.12.1 [SECURITY] [@renovate](https://github.com/pulsar-edit/pulsar/pull/504)
- Added: Add modern tree-sitter support behind an experimental flag [@savetheclocktower](https://github.com/pulsar-edit/pulsar/pull/472)
- Added: Make CHANGELOG easier to merge and update dompurify [@mauricioszabo](https://github.com/pulsar-edit/pulsar/pull/537)
- Added: js operators [@icecream17](https://github.com/pulsar-edit/pulsar/pull/79)
- Bumped: Update dependency postcss to v8.2.13 [SECURITY] [@renovate](https://github.com/pulsar-edit/pulsar/pull/514)
### github
- Added: Add logout menu option [@Daeraxa](https://github.com/pulsar-edit/github/pull/27)
- Updated: ci: Bump action dependencies [@Spiker985](https://github.com/pulsar-edit/github/pull/19)
## 1.105.0
- Rebranded notifications, using our backend to find new versions of package,
and our github repository to find issues on Pulsar. Also fixed the "view issue"
and "create issue" buttons that were not working
- Bumped to latest version of `second-mate`, fixing a memory usage issue in `vscode-oniguruma`
- Removed a cache for native modules - fix bugs where an user rebuilds a native
module outside of Pulsar, but Pulsar refuses to load anyway
- Removed `nslog` dependency
- Fixed an error where the GitHub package tried to interact with a diff view after it was closed
- Fixed RPM installation failure when Atom was installed on the same machine
- Added a new set of Package `activationHooks`, `...:uri-opened` lets a package activate when any URI is opened within Pulsar, and `...:file-name-opened` lets a package activate when any specific filename is opened within Pulsar.
### Pulsar
- Added: Add new `...:uri-opened` && `...:file-name-opened` Package Activation Hook [@confused-Techie](https://github.com/pulsar-edit/pulsar/pull/518)
- Fixed: Properly localize Download/Stargazer Counts within `settings-view` [@confused-Techie](https://github.com/pulsar-edit/pulsar/pull/526)
- Added: Add bookmarks service for consumption by other packages [@savetheclocktower](https://github.com/pulsar-edit/pulsar/pull/525)
- Added: Bundle notifications [@mauricioszabo](https://github.com/pulsar-edit/pulsar/pull/529)
- Fixed: Fix Ripgrep download issues in CirrusCI [@confused-Techie](https://github.com/pulsar-edit/pulsar/pull/530)
- Removed: Revert Incorrect Commit [@confused-Techie](https://github.com/pulsar-edit/pulsar/pull/528)
- Fixed: Making CI green, hopefully [@mauricioszabo](https://github.com/pulsar-edit/pulsar/pull/523)
- Bumped: Bump `second-mate` to 96866771 [@savetheclocktower](https://github.com/pulsar-edit/pulsar/pull/524)
- Removed: Remove cache of incompatible native packages [@mauricioszabo](https://github.com/pulsar-edit/pulsar/pull/493)
- Added: Simplify and bundle fuzzy-finder [@mauricioszabo](https://github.com/pulsar-edit/pulsar/pull/515)
- Added: Bundle find and replace [@mauricioszabo](https://github.com/pulsar-edit/pulsar/pull/517)
- Added: Bundle tree view [@confused-Techie](https://github.com/pulsar-edit/pulsar/pull/513)
- Added: Bundle `autocomplete-atom-api` [@confused-Techie](https://github.com/pulsar-edit/pulsar/pull/476)
- Added: Add FPM option to stop rpm buildid clash [@Daeraxa](https://github.com/pulsar-edit/pulsar/pull/505)
- Bumped: chore(deps): update dependency minimist [security] [@renovate](https://github.com/pulsar-edit/pulsar/pull/502)
- Fixed: Disable Failing Tests [@confused-Techie](https://github.com/pulsar-edit/pulsar/pull/477)
- Bumped: chore(deps): update dependency ajv to 6.12.3 [security] [@renovate](https://github.com/pulsar-edit/pulsar/pull/501)
- Bumped: chore(deps): update dependency async to 3.2.2 [security] [@renovate](https://github.com/pulsar-edit/pulsar/pull/495)
- Added: Add "icon only" class to settings view icon [@Daeraxa](https://github.com/pulsar-edit/pulsar/pull/456)
- Bumped: chore(deps): update dependency minimatch [security] [@renovate](https://github.com/pulsar-edit/pulsar/pull/496)
- Removed: Remove `nslog` dependency [@mauricioszabo](https://github.com/pulsar-edit/pulsar/pull/494)
- Added: Setup Renovate [@confused-Techie](https://github.com/pulsar-edit/pulsar/pull/469)
- Fixed: Don't mark diff ranges on a destroyed buffer [@savetheclocktower](https://github.com/pulsar-edit/pulsar/pull/481)
- Added: First Architectural Design Records [@mauricioszabo](https://github.com/pulsar-edit/pulsar/pull/480)
- Bumped: use pular's `typscript-simple` fork, which bumps `typescript` to 5.0.3 [@Meadowsys](https://github.com/pulsar-edit/pulsar/pull/458)
- Added: CI: cache and restore dependencies, plus skip rebuilding all over the place (saves a lot of time) [@DeeDeeG](https://github.com/pulsar-edit/pulsar/pull/492)
### notifications
- Fixed: Cleanup and rename [@Sertonix](https://github.com/pulsar-edit/notifications/pull/1)
- Added: reject promise with Error instance [@Sertonix](https://github.com/pulsar-edit/notifications/pull/2)
- Added: Add our Testing Action [@confused-Techie](https://github.com/pulsar-edit/notifications/pull/3)
- Fixed: Change atom strings to pulsar [@mdibella-dev](https://github.com/pulsar-edit/notifications/pull/4)
- Bumped: Bump to v3.2 of action-pulsar-dependency [@confused-Techie](https://github.com/pulsar-edit/notifications/pull/5)
- Fixed: Fix all Tests [@confused-Techie](https://github.com/pulsar-edit/notifications/pull/6)
## 1.104.0
- The settings-view package now lists a packages snippets more accurately
- Fixed some issues with some packages with WebComponents v0 (tablr package
should work now) by internalizing and patching document-register-element
- Migrated away from `node-oniguruma` in favor of `vscode-oniguruma` (WASM
version). This fixes issues with Electron 21
- Ensured new WASM packages will work on Apple Silicon
- Completions for HTML will now be as bleeding edge as possible.
### Pulsar
- Added: `settings-view` Support for Badges [@confused-Techie](https://github.com/pulsar-edit/pulsar/pull/451)
- Removed: remove weird duplicate accented fixture file (hopefully?) [@Meadowsys](https://github.com/pulsar-edit/pulsar/pull/488)
- Added: Add optional entitlements monkey-patch [@confused-Tecie](https://github.com/pulsar-edit/pulsar/pull/483)
- Added: Decaf `wrap-guide` [@confused-Techie](https://github.com/pulsar-edit/pulsar/pull/443)
- Added: Additional Bundling of Core Packages [@confused-Techie](https://github.com/pulsar-edit/pulsar/pull/424)
- Added: add allow-jit entitlement (fixes Apple Silicon builds) [@Meadowsys](https://github.com/pulsar-edit/pulsar/pull/454)
- Removed: Revert "Create i18n API" [@mauricioszabo](https://github.com/pulsar-edit/pulsar/pull/471)
- Added: Build first, and test later [@mauricioszabo](https://github.com/pulsar-edit/pulsar/pull/463)
- Update: [settings-view] Update package snippets view to reflect new features [@savetheclocktower](https://github.com/pulsar-edit/pulsar/pull/406)
- Added: Create i18n API [@Meadowsys](https://github.com/pulsar-edit/pulsar/pull/446)
- Added: Add Automated updating of `autocomplete-html` `completions.json` [@confused-Techie](https://github.com/pulsar-edit/pulsar/pull/405)
- Fixed: docs: fix markdown links in packages README [@oakmac](https://github.com/pulsar-edit/pulsar/pull/450)
- Fixed: Patch document register element [@mauricioszabo](https://github.com/pulsar-edit/pulsar/pull/438)
- Added: Using "second-mate" [@mauricioszabo](https://github.com/pulsar-edit/pulsar/pull/435)
- Fixed: Fix spacing of PHP's "for ..." snippet [@machitgarha](https://github.com/pulsar-edit/pulsar/pull/440)
- Update: Update resources metadata [@Spiker985](https://github.com/pulsar-edit/pulsar/pull/414)
- Fixed: Cirrus: Windows: install ppm deps with Yarn [@DeeDeeG](https://github.com/pulsar-edit/pulsar/pull/434)
- Added: made cirrus build scripts consistent [@Sertonix](https://github.com/pulsar-edit/pulsar/pull/239)
- Update: Update package.json author [@Daeraxa](https://github.com/pulsar-edit/pulsar/pull/432)
### second-mate
- Added: Migrate to vscode-oniguruma [@mauricioszabo](https://github.com/pulsar-edit/second-mate/pull/1)
### autosave
- Removed: removed fs-plus dependency [@Sertonix](https://github.com/pulsar-edit/autosave/pull/2)
- Update: Cleanup and rename [@Sertonix](https://github.com/pulsar-edit/autosave/pull/1)
### bracket-matcher
- Fixed: Fixing test that need to run locally [@mauricioszabo](https://github.com/pulsar-edit/bracket-matcher/pull/3)
- Update: cleanup .md and rename repo url [@Sertonix](https://github.com/pulsar-edit/bracket-matcher/pull/2)
- Update: Rename A[a]tom -> P[p]ulsar [@Spiker985](https://github.com/pulsar-edit/bracket-matcher/pull/1)
### timecop
- Update: cleanup and rename [@Sertonix](https://github.com/pulsar-edit/timecop/pull/1)
### keybinding-resolver
- Update: Cleanup and rename [@Sertonix](https://github.com/pulsar-edit/keybinding-resolver/pull/1)
## 1.103.0

View File

@ -1,9 +0,0 @@
preserve_hierarchy: true
commit_message: "[skip ci] Translations from Crowdin"
append_commit_message: false
files:
- source: /i18n/en.json
translation: /i18n/%locale%.json
- source: /packages/**/i18n/en.json
translation: /packages/**/i18n/%locale%.json

View File

@ -158,6 +158,16 @@ style: Exclusively used for the <code>style</code> attribute</p>
<dd></dd>
<dt><a href="#assert">assert</a></dt>
<dd></dd>
<dt><a href="#path">path</a></dt>
<dd></dd>
<dt><a href="#path">path</a></dt>
<dd></dd>
<dt><a href="#path">path</a></dt>
<dd></dd>
<dt><a href="#_">_</a></dt>
<dd></dd>
<dt><a href="#path">path</a></dt>
<dd></dd>
</dl>
## Functions
@ -169,6 +179,8 @@ style: Exclusively used for the <code>style</code> attribute</p>
<dd></dd>
<dt><a href="#conditionPromise">conditionPromise()</a></dt>
<dd></dd>
<dt><a href="#conditionPromise">conditionPromise()</a></dt>
<dd></dd>
<dt><a href="#destroy">destroy()</a></dt>
<dd></dd>
<dt><a href="#destroyChildren">destroyChildren()</a></dt>
@ -185,6 +197,12 @@ style: Exclusively used for the <code>style</code> attribute</p>
<dd></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>
<dd></dd>
<dt><a href="#beforeEach">beforeEach()</a></dt>
<dd></dd>
<dt><a href="#conditionPromise">conditionPromise()</a></dt>
<dd></dd>
<dt><a href="#beforeEach">beforeEach()</a></dt>
@ -508,6 +526,31 @@ This file aims to run some short simple tests against `update.js`. Focusing
## assert
**Kind**: global constant
**Babel**:
<a name="path"></a>
## path
**Kind**: global constant
**Babel**:
<a name="path"></a>
## path
**Kind**: global constant
**Babel**:
<a name="path"></a>
## path
**Kind**: global constant
**Babel**:
<a name="_"></a>
## \_
**Kind**: global constant
**Babel**:
<a name="path"></a>
## path
**Kind**: global constant
**Babel**:
<a name="beforeEach"></a>
## beforeEach()
@ -520,6 +563,11 @@ This file aims to run some short simple tests against `update.js`. Focusing
**Babel**:
<a name="conditionPromise"></a>
## conditionPromise()
**Kind**: global function
**Babel**:
<a name="conditionPromise"></a>
## conditionPromise()
**Kind**: global function
**Babel**:
@ -572,6 +620,21 @@ 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="beforeEach"></a>
## beforeEach()
**Kind**: global function
**Babel**:
<a name="beforeEach"></a>
## beforeEach()
**Kind**: global function
**Babel**:
<a name="beforeEach"></a>
## beforeEach()
**Kind**: global function
**Babel**:

View File

@ -1,14 +1,18 @@
# Atom Docs
# Pulsar Docs
![Atom](https://cloud.githubusercontent.com/assets/72919/2874231/3af1db48-d3dd-11e3-98dc-6066f8bc766f.png)
Most of the Atom user and developer documentation is contained in the [Atom Flight Manual](https://github.com/atom/flight-manual.atom.io).
Most of the Pulsar/Atom user and developer documentation is contained on the [Pulsar Website](https://pulsar-edit.dev/docs/launch-manual/).
While the Pulsar website does not yet have the Pulsar API documentation, this is partially available within [Pulsar API Documentation](./Pulsar-API-Documentation.md) or otherwise the original docs are available from community members [here](https://atom-flight-manual-archive.github.io/).
There is also general guidance on the internal [stucture and behavior](./architecture/README.md) of Pulsar available.
## Build documentation
Instructions for building Atom on various platforms from source.
* Moved to [the Flight Manual](https://flight-manual.atom.io/hacking-atom/sections/hacking-on-atom-core/)
* Moved to [the Flight Manual](https://pulsar-edit.dev/docs/launch-manual/sections/core-hacking/)
* Linux
* macOS
* Windows
@ -16,5 +20,3 @@ Instructions for building Atom on various platforms from source.
## Other documentation
[Native Profiling on macOS](./native-profiling.md)
The other documentation that was listed here previously has been moved to [the Flight Manual](https://flight-manual.atom.io).

View File

@ -158,6 +158,16 @@ style: Exclusively used for the <code>style</code> attribute</p>
<dd></dd>
<dt><a href="#assert">assert</a></dt>
<dd></dd>
<dt><a href="#path">path</a></dt>
<dd></dd>
<dt><a href="#path">path</a></dt>
<dd></dd>
<dt><a href="#path">path</a></dt>
<dd></dd>
<dt><a href="#_">_</a></dt>
<dd></dd>
<dt><a href="#path">path</a></dt>
<dd></dd>
</dl>
## Functions
@ -169,6 +179,8 @@ style: Exclusively used for the <code>style</code> attribute</p>
<dd></dd>
<dt><a href="#conditionPromise">conditionPromise()</a></dt>
<dd></dd>
<dt><a href="#conditionPromise">conditionPromise()</a></dt>
<dd></dd>
<dt><a href="#destroy">destroy()</a></dt>
<dd></dd>
<dt><a href="#destroyChildren">destroyChildren()</a></dt>
@ -185,6 +197,12 @@ style: Exclusively used for the <code>style</code> attribute</p>
<dd></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>
<dd></dd>
<dt><a href="#beforeEach">beforeEach()</a></dt>
<dd></dd>
<dt><a href="#conditionPromise">conditionPromise()</a></dt>
<dd></dd>
<dt><a href="#beforeEach">beforeEach()</a></dt>
@ -516,6 +534,31 @@ This file aims to run some short simple tests against `update.js`. Focusing
## assert
**Kind**: global constant
**Babel**:
<a name="path"></a>
## path
**Kind**: global constant
**Babel**:
<a name="path"></a>
## path
**Kind**: global constant
**Babel**:
<a name="path"></a>
## path
**Kind**: global constant
**Babel**:
<a name="_"></a>
## \_
**Kind**: global constant
**Babel**:
<a name="path"></a>
## path
**Kind**: global constant
**Babel**:
<a name="beforeEach"></a>
## beforeEach()
@ -528,6 +571,11 @@ This file aims to run some short simple tests against `update.js`. Focusing
**Babel**:
<a name="conditionPromise"></a>
## conditionPromise()
**Kind**: global function
**Babel**:
<a name="conditionPromise"></a>
## conditionPromise()
**Kind**: global function
**Babel**:
@ -580,6 +628,21 @@ 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="beforeEach"></a>
## beforeEach()
**Kind**: global function
**Babel**:
<a name="beforeEach"></a>
## beforeEach()
**Kind**: global function
**Babel**:
<a name="beforeEach"></a>
## beforeEach()
**Kind**: global function
**Babel**:

View File

@ -0,0 +1,10 @@
# Pulsar Architecture
This directory contains a collection of files and diagrams, that aim to easily explain some of the core components or behaviors of Pulsar.
Remember that keeping this charts up to date is best effort, and the age of these files should be taken into consideration.
## Contents
- [Startup Overview](./overview.md)
- ['The World'](./the-world.md)
- [Package Preload](./package-preload.md)

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 30 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 30 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -0,0 +1,169 @@
# Pulsar's High Level Startup Overview
It's no secret that Pulsar since inherited from Atom, is a big and complex application.
With many discrete, moving aspects, that not all developers have a concrete grasp on.
The goal of this document is to make the architecture of Pulsar, as well as the logical flow
more understandable and approachable.
This will be accomplished through a series of illustrations detailing the functional decomposition and detailed logical flow of Pulsar and it's parts, as well as lists of what's accomplished in each part.
This document is aimed at those roughly familiar with the large scale goals and features of Pulsar, as well as those with a basic understanding of the package model used to power much of Pulsar's functionality.
![Pulsar Overview MermaidJS Image](./assets/pulsar-overview.svg "Pulsar Overview")
<details>
<summary>
MermaidJS to create image above
</summary>
```
flowchart TD
id1("`Initialization
*./src/main-process/main.js*`") --> id2("`Startup
*./src/main-process/start.js*`")
id2 --> id3("`Main Process Tests`")
id2 --> id4("`Application Startup
*./src/main-process/atom-application.js*`")
id2 --> id5("`Startup w/ Squirrel
*./src/main-process/squirrel-update.js*`")
id4 --> id6("`Test Runner
*./src/initialize-test-window.js*`")
id4 --> id7("`Initialize Application Window
*./src/initialize-application-window.js*`")
id7 --> id9("`'The World'
*./src/atom-environment.js*`")
id7 --> id10("`ApplicationDelegate
*./src/application-delegate.js*`")
id7 --> id8("`Clipboard
*./src/clipboard.js*`")
id8 --> id9
id10 --> id9
```
</details>
---
To further outline what occurs in the steps above:
## Initialization
Startup of Pulsar occurs within `./src/main-process/main.js`.
Which Determines:
- `resourcePath`
- `devResourcePath`
- `stableResourcePath`
- `defaultRepositoryPath`
Which Sets:
- Application Start Time
Which Does:
- Initializes Startup of `./src/main-process/start.js`
## Startup
The more general startup handling of Pulsar occurs within `./src/main-process/start.js`.
Which Sets:
- Shell Start Time
- `app.allowRendererProcessReuse`: `false`
- `app.commandLine.appendSwitch('enable-experimental-web-platform-features')`
- `app.commandLine.appendSwitch('force-color-profile', config.get('core.colorProfile'))`
- `app.setAppUserModelId()`
- `app.on('open-file', $)`
- `app.on('open-url', $)`
Which Does:
- Normalizes the `resourcePath` and `devResourcePath`
- Uses `Config` to locate and read the config file
- `atomPaths.setAtomHome()`
- `atomPaths.setUserData()`
- May defer to `./src/main-process/squirrel-update.js` to startup if on Windows
- May defer to `./spec/main-process/mocha-test-runner.js` to startup main process tests
- May call `.open()` on `./src/main-process/atom-application.js`
## Application Startup
The proper startup of the Pulsar Application occurs within `./src/main-process/atom-application.js`.
Which Sets:
- `APPLICATION_STATE_VERSION`
- Global `atomApplication`
Which Does:
- Does setup of the application socket
- Handles deprecated benchmark startup
- Ensures to return a new instance of `AtomApplication`
- Registers basic application commands
- Initializes:
* `ApplicationMenu`
* `AtomProtocolHandler`
* `WindowStack`
* `FileRecoveryService`
* `Config`
* `StorageFolder`
* `AutoUpdateManager`
- May startup the package test runner
- May quit if asked to startup in benchmark mode
- May open previously opened files/folders
- May open new instance of Pulsar
## Initialize Application Window
Begins initialization of an individual Pulsar window, occurs within `./src/initialize-application-window.js`.
Which Determines:
Which Sets:
- Sets the `global.atom` to a new instance of `AtomEnvironment`
Which Does:
- triggers `.preloadPackages()`
- Initializes:
* Clipboard
* AtomEnvironment
* ApplicationDelegate
## 'The World'
'The World' refers to being within the Pulsar application, most of the application occurs within here.
This code lives within `./src/atom-environment.js`.
An important note about being initialized within the world, there is no access to the `atom`
global, until the initial constructor completes processing. Meaning great care must be taken
to ensure if `atom` is available within the initialized modules.
Which Sets:
- `AtomEnvironment.version`: `1` | Possibly a reference to `APPLICATION_STATE_VERSION`?
- `AtomEnvironment.saveStateDebounceInterval`: `1000`
Which Does:
- Initializes:
* Clipboard | Inherited from 'Initialize Application Window'
* ApplicationDelegate | Inherited from 'Initialize Application Window'
* DeserializerManager
* ViewRegistry
* NotificationManager
* StateStore
* Config
* KeymapManager
* TooltipManager
* CommandRegistry
* URIHandlerRegistry
* GrammarRegistry
* StyleManager
* PackageManager
* ThemeManager
* MenuManager
* ContextMenuManager
* Project
* CommandInstaller
* ProtocolHandlerInstaller
* TextEditorRegistry
* Workspace
* AutoUpdateManager
* WindowEventHandler
* HistoryManager

View File

@ -0,0 +1,66 @@
# Package Preload
Pulsar's packages are preloaded, very early on within the startup cycle of Pulsar.
As it's called immediatly after the `atom` global is initialized, it's important to understand what steps occur during preloading, and what package's are affected.
![Package Preload Overview](./assets/package-preload.svg "Package Preload Overview")
---
<details>
<summary>
MermaidJS to create image above
</summary>
```
flowchart TD
iaw["`
initialize-application-window.js
Called right after global 'atom' is set
`"] -->
ae["`
AtomEnvironment
.preloadPackages()
`"] -->
pl1["preloadPackages()"] -->|if in packageCache| sg1
subgraph sg1
direction LR
pl2["preloadPackage()"]
pl2 -->|"call .preload()"| tp1["new ThemePackage"]
pl2 -->|"call .preload()"| p1["new Package"]
p1 --> p1Pre["`
this.preload() call:
Does more than advertised here
`"]
p1Pre --> lk1[".loadKeymaps()"]
lk1 --> pcRead1["`Read from packagesCache:
If bundled package && in packagesCache`"]
lk1 --> fileRead1["`Read from file:
If !bundled || !in packagesCache`"]
p1Pre --> lm1[".loadMenus()"]
lm1 --> pcRead1
lm1 --> fileRead1
p1Pre --> acss[".activateCoreStartupServices()"]
p1Pre --> rmm[".requireMainModule()"]
acss --> rmm
p1Pre --> ls1[".loadSettings()"]
ls1 --> pcRead1
ls1 --> fileRead1
p1Pre --> ak1[".activateKeymaps()"]
p1Pre --> am1[".activateMenus()"]
tp1 --> tp1Pre[".preload()"]
tp1Pre --> csrol1[".reigsterConfigSchemaFromMetadata()"]
end
```
</details>

View File

@ -0,0 +1,101 @@
# 'The World'
While it's difficult to convey the full scope of how Pulsar works internally, just like the previous page [`overview.md`](./overview.md) detailed the general gist of how Pulsar starts up, this document provides a quick reference to how all the interal parts of Pulsar are connected.
This document is not at all comprehensive, and must ensure to be continually updated. Additionally, this image does not track outside dependency usage, nor dependence on every single internal module. Focusing mostly on modules that are either required during initialization, or are referenced during the constructor of their respective class.
<details>
<summary>
Details on the creation of this image
</summary>
This image has been created with Plant UML. A Live editor is available [here](https://www.plantuml.com/plantuml/uml).
The code used to create this image:
```uml
@startwbs
* Initialization
** Startup
***< Main Process Tests
*** Startup w/ Squirrel
***< Application Startup
****< Test Runner
**** Initialize Application Window
***** 'The World'
******< Config
******* ScopeDescriptor
****** KemapManager
****** TolltipManager
******* Tooltip
******< CommandRegistry
******< URIHandlerRegistry
****** StyleManager
******* createStylesElement
******* DEPRECATED_SYNTAX_SELECTORS
******< MenuManager
******* MenuHelpers
****** ContextMenuManager
******* MenuHelpers
******* sortMenuItems
******< TextEditorRegistry
******* TextEditor
******* ScopeDescriptor
****** HistoryManager
******< DeserializerManager
******< ViewRegistry
****** NotificationManager
******* Notification
****** StateStore
******< PackageManager
******* Package
******* ThemePackage
******* ModuleCache
****** ThemeManager
******* LessCompileCache
****** Project
******* watchPath
******* DefaultDirectoryProvider
******* Model
******* GitRepositoryProvider
******< CommandInstaller
******< ProtocolHandlerInstaller
******< AutoUpdateManager
******< WindowEventHandler
******* listen
******< GrammarRegistry
*******< NodeTreeSitterLanguageMode
******** TreeIndenter
********< TextMateLanguageMode
******** TokenizedLine
********< ScopeDescriptor
******** matcherForSelector
********< Token
******* WASMTreeSitterLanguageNode
******** Parser
******* TextMateLanguageMode
******** TokenizedLine
********< TokenIterator
******** ScopeDescriptor
********< NullGrammar
*******< ScopeDescriptor
******* Token
****** Workspace
******* DefaultDirectorySearcher
*******< RipgrepDirectorySearcher
******* WorkspaceCenter
*******< createWorkspaceElement
******* PanelContainer
*******< StateStore
******* TextEditor
*******< Panel
******* Task
*******< Dock
@endwbs
```
</details>
---
![Pulsar 'How Everything Connects' UML Image](./assets/how-everything-connects.svg "Pulsar 'How Everything Connects'")

View File

@ -0,0 +1,79 @@
---
# These are optional elements. Feel free to remove any of them.
status: {proposed | rejected | accepted | deprecated | … | superseded by ADR-0005 <0005-example.md>}
date: {YYYY-MM-DD when the decision was last updated}
deciders: {list everyone involved in the decision}
consulted: {list everyone whose opinions are sought (typically subject-matter experts); and with whom there is a two-way communication}
informed: {list everyone who is kept up-to-date on progress; and with whom there is a one-way communication}
---
# {short title of solved problem and solution}
## Context and Problem Statement
{Describe the context and problem statement, e.g., in free form using two to three sentences or in the form of an illustrative story.
You may want to articulate the problem in form of a question and add links to collaboration boards or issue management systems.}
<!-- This is an optional element. Feel free to remove. -->
## Decision Drivers
* {decision driver 1, e.g., a force, facing concern, …}
* {decision driver 2, e.g., a force, facing concern, …}
* … <!-- numbers of drivers can vary -->
## Considered Options
* {title of option 1}
* {title of option 2}
* {title of option 3}
* … <!-- numbers of options can vary -->
## Decision Outcome
Chosen option: "{title of option 1}", because
{justification. e.g., only option, which meets k.o. criterion decision driver | which resolves force {force} | … | comes out best (see below)}.
<!-- This is an optional element. Feel free to remove. -->
### Consequences
* Good, because {positive consequence, e.g., improvement of one or more desired qualities, …}
* Bad, because {negative consequence, e.g., compromising one or more desired qualities, …}
* … <!-- numbers of consequences can vary -->
<!-- This is an optional element. Feel free to remove. -->
## Validation
{describe how the implementation of/compliance with the ADR is validated. E.g., by a review or an ArchUnit test}
<!-- This is an optional element. Feel free to remove. -->
## Pros and Cons of the Options
### {title of option 1}
<!-- This is an optional element. Feel free to remove. -->
{example | description | pointer to more information | …}
* Good, because {argument a}
* Good, because {argument b}
<!-- use "neutral" if the given argument weights neither for good nor bad -->
* Neutral, because {argument c}
* Bad, because {argument d}
* … <!-- numbers of pros and cons can vary -->
### {title of other option}
{example | description | pointer to more information | …}
* Good, because {argument a}
* Good, because {argument b}
* Neutral, because {argument c}
* Bad, because {argument d}
* …
<!-- This is an optional element. Feel free to remove. -->
## More Information
{You might want to provide additional evidence/confidence for the decision outcome here and/or
document the team agreement on the decision and/or
define when and how this decision should be realized and if/when it should be re-visited and/or
how the decision is validated.
Links to other decisions and resources might appear here as well.}

View File

@ -0,0 +1,48 @@
---
status: accepted
date: 2022-08-01
---
# Removal of the old build scripts, V8 Snapshots, and migration to `yarn`
## Build scripts on Atom
It was _really difficult_ to understand the old build scripts. They basically
transpiled the old code and copied some dependencies on a directory called
`app`; then, some dependencies were installed, some other steps were run, like
creating some manifests and metadata; finally, this transpiled code was passed
through another script that basically generated a `mksnapshot` script command,
and everything was _manually packaged_ into an ASAR file.
The problems: the script was too big, it depended on `npm` < 7.0, it used to
fail constantly (needing multiple "clean" commands), it took too much time, and
basically the "snapshots" depended on the source file _structure_ on the disk -
if we decided to rename some file, or move some `require`s around, the script
would break; also, some of the dependencies of that script were also made
in-house, making for new work to keep in Pulsar
## Decision Drivers
* Easier to install dependencies
* Easier to build binaries, in a way that can work in the first run instead
of multiple retries
* Make it easier to build and start hacking the editor
* Faster to build and hack, specially after bumping dependencies
## Considered Options
* Rewrite the scripts
* Remove the scripts completely and migrate to electron-build
## Decision Outcome
Chosen option: "Remove the scripts". It makes things easier because we don't
need to keep any in-house packaging of Electron, ASAR creation, and
RPM/DEB/AppImage generation for Linux, or binaries for Mac / Windows.
### Consequences
* Faster editor bootstrap, and easier to bump dependencies, specially Electron
* The editor is _way slower_ to load
* We had some bugs because we are not using V8 Snapshots anymore - basically,
with V8 Snapshots, the global object `atom` was present where it wasn't supposed
to

View File

@ -0,0 +1,44 @@
---
status: accepted
---
# Removal of experimental watchers on Pulsar
## Removal of @atom/watcher and others
Atom had four different ways of watching for filesystem changes. Two used an
in-house library called `@atom/watcher` - one for "polling" and other for
"experimental". It also had a different library to watch files for changes that
basically used the Tree-View package's watch mechanism
## Decision Drivers
* We don't know what "experimental watcher" means, and we have no way of knowing
* We don't want to support another "in-house" library to watch files, specially
considering that it's a binary library
* We know that tree-view _can use_ Pulsar's watcher mechanism to watch files,
meaning we enter somekind of weird loop
## Considered Options
* Use `nsfw` only
* Use `chokidar` only
## Decision Outcome
Use only `nsfw` library to watch for changes, and use the community version of
that library. Make the config for "watch" in Pulsar reflect that we only have
`nsfw` as an option (Native operating system APIs).
## Validation
Files are still being watched. Config failed to watch, but that's because of a
different issue - see ADR 001
## More Information
We still have another "watch" library called Pathwatcher. It is used by
TextBuffer, atom-keymap and tree-view. It may be possible to remove this library
to use only Pulsar's watch mechanism (it is available under the public API) so
every code will basically use the public API and they will all respect the
choice of what Pulsar defined, if we decide to introduce new file watcher
libraries in the future

View File

@ -0,0 +1,22 @@
---
status: rejected
---
# Make a compatibility layer on Pulsar's "pathwatcher"
## Remove the binary dependency on "pathwatcher" and a compatibility layer to use Pulsar's public API
The idea was to keep 100% API compatibility with Pathwatcher using Pulsar's public API (using `require('atom').watchPath` code). The experiment lives on [Pulsar
Pathwatcher](https://github.com/pulsar-edit/pulsar-pathwatcher) repository
## Decision Drivers
* Avoid another watch library
* Avoid another binary dependency
## Decision Outcome
Because of the cyclic dependency between TextBuffer and Pulsar, together with
the "exports" library and other issues, this experiment didn't go far. Some
infinite loops happened, sometimes the renderer process crashed, and sometimes
other weird issues appeared depending on the order things got loaded, so
this will probably be revisited in the future, if at all.

View File

@ -0,0 +1,87 @@
---
status: accepted
date: 2023-03-28
deciders: '@mauricioszabo, @confused-Techie, @savetheclocktower'
---
# Use a different Oniguruma implementation for TextMate grammars
## Current version of Oniguruma is in-house and crashes with Electron >= 21
To tokenize TextMate grammars, Pulsar uses a NPM binary library called
Oniguruma. Unfortunately, while we could make the native library work on
Electron versions 14 up to 20, when upgrading to Electron 21 it breaks because
of the new V8 Memory Model (sandboxed pointers).
## Considered Options
* Migrate to a pure-JS version of Oniguruma
* Migrate to [WASM Oniguruma](https://github.com/rebornix/wasm-oniguruma)
* Migrate to [VSCode Oniguruma](https://github.com/microsoft/vscode-oniguruma/)
* Migrate **all tokenization** to [VSCode Textmate](https://github.com/microsoft/vscode-textmate)
## Decision Outcome
Chosen option: "Migrate fo VSCode Oniguruma", because the pure JS version we found was not up-to-date with Oniguruma and could not parse most of the Regexp that our grammars need; second, WASM Oniguruma didn't see any update in the last 6 years, so we end up with vscode-oniguruma.
We basically duplicated the
[first-mate](https://github.com/pulsar-edit/first-mate/) repository and created
[second-mate](https://github.com/pulsar-edit/second-mate/), that uses the new
VSCode oniguruma version
## Pros and Cons of the Options
### Bump to Electron 23
It was confirmed that new vscode-oniguruma works with Electron 23.
### Faster
This is maybe counter-intuitive, but for some reason, the tokenizer got faster
when we migrated to vscode-oniguruma (by about 50% in some cases). We had also
lower deviation - the slowest case and the fastest one are now closer in time
### macOS problems
This broke Silicon macOS builds because if we use WASM, we need to add [Allow
JIT entitlement to the plist
file](https://github.com/pulsar-edit/pulsar/pull/454). We solved this, but with
this entitlement, Intel macOS builds also got slower ([Electron issue](https://github.com/electron/electron/issues/26143))
### Memory leaks
We found out that in some situations, we could have memory leaks - basically,
WASM doesn't have garbage collection like Node, so every time we created new
`OnigScanner` objects (discarding the old ones) that caused a new memory
allocation without any deallocation of the old memory.
To mitigate this, we basically implemented a cache of `OnigScanner`s - when
someone instantiates this class, it'll first see if a previous version was
instantiated, and if so, it'll reuse the same object. This doesn't _actually
solve_ the memory leak, but makes it controllable - TextMate grammars do not use
too many regular expressions, so everytime one opens Pulsar, even if the user
have multiple TextMate grammars only it'll only leak a couple kilobytes of
memory, so it's considered a non-issue for now.
## Usage of VSCode TextMate
VSCode TextMate is a library that basically highlights all code in VSCode. One
idea was to migrate away completely from `first-mate` and use this library as
the tokenizer. Unfortunately, `first-mate` is actually _more correct_ than
VSCode's implementation - for example, Pulsar is able to higlight XML namespaces
on attributes, like for example `<e foo:bar="attr">` (the `foo` gets
highlighted)
## Second-Mate, and VSCode-Oniguruma patches
VSCode-Oniguruma works a little bit differently than Atom's version. On VSCode's
version, when it doesn't find a match (length=0 on the result) VSCode's version
return a meaningless value on `start` and `end`, whereas Atom's return the
latest `end` from the previous match. We had to normalize this on Second-Mate.
Also, Atom's version of Oniguruma had an object called `OnigRegExp` that the new
library doesn't have. The only usage of that object was a method called `test` -
fortunatelly, this translates 100% do using `OnigScanner#findNextMatchSync`, so
we moved all usages to this new API - meaning, `second-mate` is 90% compatible
with `first-mate`, with the exception of the `firstLineRegex` and
`contentsRegex`, that now return an object of `OnigScanner` instead of
`OnigRegExp`.

View File

@ -0,0 +1,81 @@
---
status: accepted
date: 2023-08-10
deciders: '@confused-Techie, @Daeraxa, @savetheclocktower, @mauricioszabo'
---
# Remove original AutoUpdate Functionality
## Context and Problem Statement
To provide users with some form of autoupdate functionality, many thought we could
adopt the original method used by the Atom team, and de-facto method within Electron
applications, Squirrel. While Squirrel is great for AutoUpdating, it has some big
issues, mainly it requires that every platform has signed binaries, which can be
prohibitively expensive. Additionally, it only supports Windows and macOS.
## Decision Drivers
* Users need a way to fulfill AutoUpdate functionality
## Considered Options
* Sign Windows and macOS (Already do) binaries, and setup Squirrel.
* Remove AutoUpdate totally.
* Use core package to assist in update installation.
## Decision Outcome
Chosen option: "Use core package to assist in update installation", to allow similar
behavior, we opted to create a core package that could help alert users of updates
to Pulsar being available, while technically not actually preforming any installation.
This new core package was added to Pulsar in [`pulsar-edit/pulsar#656`](https://github.com/pulsar-edit/pulsar/pull/656).
This package alerts and assists in users installing new updates to Pulsar, while no
longer relying on any form of AutoUpdate functionality from Squirrel.
This means, that we can now remove all Squirrel and AutoUpdate functionality that's
built right into Pulsar, hopefully cutting down on startup time.
<!-- This is an optional element. Feel free to remove. -->
### Consequences
* Good, because this allows a semblence of AutoUpdate functionality without ever having to hit our own backend.
* Good, because it allows users to be more in control, and automatically notified of new versions.
* Good, because it sections off the logic to a package that can be disabled or replaced as needed.
* Bad, because it does not actually preform any autoupdates.
## Pros and Cons of the Options
### Sign Windows and macOS binaries, and setup Squirrel
This would return things to the status quo, of Atom AutoUpdate functionality.
* Good, because users would know what to expect.
* Good, because it would provide AutoUpdates to Windows and macOS users.
* Bad, because it would be prohibitively expensive, and would fail to work if we ever
didn't have the funds for expensive binary signing costs.
* Bad, because this would leave Linux users with still zero support for AutoUpdates.
* Bad, because it would add additional complexity into our CI for signing Windows binaries.
### Remove AutoUpdates totally
This is essentially, what we have been doing since we took over Atom. Provided zero methodology
for users to preform autoupdates within Pulsar.
* Good, because it requires zero effort on our end.
* Good, because there are no signing costs.
* Bad, because it provides users no method to easily update.
* Bad, because users would be misinformed about Atom's ability to autoupdate being lost.
* Bad, because autoupdates of some kind is an expected and standard feature in any modern application.
## More Information
This decision is not one taken lightly, and could still stir some controversy on best implementations.
There was also additional concerns about not deleting the code used for Squirrel's AutoUpdate functionality
in case we ever did want to return to that behavior, since it already works perfectly, if properly setup.
For that reason, instead of keeping the code within the repo, below will be details about where the code that comprises
of the Squirrel AutoUpdate logic will be kept within Git, so that it can always be retrieved if ever needed.
* Last Commit Before Removal: `bf60fbe6fc267b737a70d5d39c03cad1629ea128`
* PR Where it was Removed: [`pulsar-edit/pulsar#668`](https://github.com/pulsar-edit/pulsar/pull/668)

View File

@ -1,169 +0,0 @@
{
"menu": {
"pulsar": {
"about": "About Pulsar",
"view-license": "View License",
"version": "Version VERSION",
"restart-and-install-update": "Restart and Install Update",
"check-for-update": "Check for Update",
"checking-for-update": "Checking for Update",
"downloading-update": "Downloading Update",
"preferences": "Preferences",
"config": "Config",
"init-script": "Init Script",
"keymap": "Keymap",
"snippets": "Snippets",
"stylesheet": "Stylesheet",
"install-shell-commands": "Install Shell Commands",
"quit": "Quit Pulsar"
},
"macos": {
"services": "Services",
"hide-self": "Hide Pulsar",
"hide-others": "Hide Others",
"show-all": "Show All"
},
"file": {
"self": "File",
"new-window": "New Window",
"new-file": "New File",
"open": "Open...",
"open-file": "Open File...",
"open-folder": "Open Folder...",
"add-project-folder": "Add Project Folder",
"project-history": {
"reopen-project": "Reopen Project",
"clear": "Clear Project History"
},
"reopen-last-item": "Reopen Last Item",
"save": "Save",
"save-as": "Save As...",
"save-all": "Save All",
"close-tab": "Close Tab",
"close-pane": "Close Pane",
"close-window": "Close Window"
},
"edit": {
"self": "Edit",
"undo": "Undo",
"redo": "Redo",
"cut": "Cut",
"copy": "Copy",
"copy-path": "Copy Path",
"paste": "Paste",
"paste-without-reformatting": "Paste Without Reformatting",
"select-all": "Select All",
"toggle-comments": "Toggle Comments",
"lines": {
"self": "Lines",
"indent": "Indent",
"outdent": "Outdent",
"auto-indent": "Auto Indent",
"move-up": "Move Line Up",
"move-down": "Move Line Down",
"duplicate": "Duplicate Lines",
"delete": "Delete Line",
"join": "Join Lines"
},
"columns": {
"self": "Columns",
"move-selection-left": "Move Selection Left",
"move-selection-right": "Move Selection Right"
},
"text": {
"self": "Text",
"upper-case": "Upper Case",
"lower-case": "Lower Case",
"delete-to-end-of-word": "Delete to End of Word",
"delete-to-previous-word-boundary": "Delete to Previous Word Boundary",
"delete-to-next-word-boundary": "Delete to Next Word Boundary",
"delete-line": "Delete Line",
"transpose": "Transpose"
},
"folding": {
"self": "Folding",
"fold": "Fold",
"unfold": "Unfold",
"fold-all": "Fold All",
"unfold-all": "Unfold All",
"fold-level-1": "Fold Level 1",
"fold-level-2": "Fold Level 2",
"fold-level-3": "Fold Level 3",
"fold-level-4": "Fold Level 4",
"fold-level-5": "Fold Level 5",
"fold-level-6": "Fold Level 6",
"fold-level-7": "Fold Level 7",
"fold-level-8": "Fold Level 8",
"fold-level-9": "Fold Level 9"
}
},
"view": {
"self": "View",
"toggle-full-screen": "Toggle Full Screen",
"toggle-menu-bar": "Toggle Menu Bar",
"panes": {
"self": "Panes",
"split-up": "Split Up",
"split-down": "Split Down",
"split-left": "Split Left",
"split-right": "Split Right",
"focus-next": "Focus Next Pane",
"focus-previous": "Focus Previous Pane",
"focus-above": "Focus Pane Above",
"focus-below": "Focus Pane Below",
"focus-on-left": "Focus Pane On Left",
"focus-on-right": "Focus Pane On Right",
"close": "Close Pane"
},
"developer": {
"self": "Developer",
"open-in-dev-mode": "Open In Dev Mode",
"reload-window": "Reload Window",
"run-package-specs": "Run Package Specs",
"toggle-dev-tools": "Toggle Developer Tools"
},
"increase-font-size": "Increase Font Size",
"decrease-font-size": "Decrease Font Size",
"reset-font-size": "Reset Font Size",
"toggle-soft-wrap": "Toggle Soft Wrap"
},
"selection": {
"self": "Selection",
"add-above": "Add Selection Above",
"add-below": "Add Selection Below",
"single": "Single Selection",
"split-into-lines": "Split into Lines",
"to-top": "Select to Top",
"to-bottom": "Select to Bottom",
"line": "Select Line",
"word": "Select Word",
"to-beginning-of-word": "Select to Beginning of Word",
"to-beginning-of-line": "Select to Beginning of Line",
"to-first-char-of-line": "Select to First Character of Line",
"to-end-of-word": "Select to End of Word",
"to-end-of-line": "Select to End of Line"
},
"find": {
"self": "Find"
},
"packages": {
"self": "Packages",
"open-package-manager": "Open Package Manager"
},
"window": {
"self": "Window",
"minimise": "Minimise",
"zoom": "Zoom",
"bring-all-to-front": "Bring All to Front"
},
"help": {
"self": "Help",
"terms-of-use": "Terms of Use",
"docs": "Documentation",
"faq": "Frequently Asked Questions",
"community-discussions": "Community Discussions",
"report-issue": "Report Issue",
"search-issues": "Search Issues"
}
}
}

View File

@ -14,6 +14,11 @@ async function openAtom(profilePath, videoName) {
env: env,
timeout: 50000
}
if(env.BINARY_NAME) {
config.executablePath = env.BINARY_NAME
config.args = ["--no-sandbox"]
}
if(process.env.CI) {
config.recordVideo = {
dir: path.join('tests', 'videos', videoName)

View File

@ -2,227 +2,227 @@
{
label: 'Pulsar'
submenu: [
{ localisedLabel: 'core.menu.pulsar.about', command: 'application:about' }
{ localisedLabel: 'core.menu.pulsar.view-license', command: 'application:open-license' }
{ localisedLabel: 'core.menu.pulsar.version', enabled: false }
{ localisedLabel: 'core.menu.pulsar.restart-and-install-update', command: 'application:install-update', visible: false }
{ localisedLabel: 'core.menu.pulsar.check-for-update', command: 'application:check-for-update', visible: false }
{ localisedLabel: 'core.menu.pulsar.checking-for-update', enabled: false, visible: false }
{ localisedLabel: 'core.menu.pulsar.downloading-update', enabled: false, visible: false }
{ label: 'About Pulsar', command: 'application:about' }
{ label: 'View License', command: 'application:open-license' }
{ label: 'VERSION', enabled: false }
{ label: 'Restart and Install Update', command: 'application:install-update', visible: false}
{ label: 'Check for Update', command: 'application:check-for-update', visible: false}
{ label: 'Checking for Update', enabled: false, visible: false}
{ label: 'Downloading Update', enabled: false, visible: false}
{ type: 'separator' }
{ localisedLabel: 'core.menu.pulsar.preferences', command: 'application:show-settings' }
{ label: 'Preferences…', command: 'application:show-settings' }
{ type: 'separator' }
{ localisedLabel: 'core.menu.pulsar.config', command: 'application:open-your-config' }
{ localisedLabel: 'core.menu.pulsar.init-script', command: 'application:open-your-init-script' }
{ localisedLabel: 'core.menu.pulsar.keymap', command: 'application:open-your-keymap' }
{ localisedLabel: 'core.menu.pulsar.snippets', command: 'application:open-your-snippets' }
{ localisedLabel: 'core.menu.pulsar.stylesheet', command: 'application:open-your-stylesheet' }
{ label: 'Config…', command: 'application:open-your-config' }
{ label: 'Init Script…', command: 'application:open-your-init-script' }
{ label: 'Keymap…', command: 'application:open-your-keymap' }
{ label: 'Snippets…', command: 'application:open-your-snippets' }
{ label: 'Stylesheet…', command: 'application:open-your-stylesheet' }
{ type: 'separator' }
{ localisedLabel: 'core.menu.pulsar.install-shell-commands', command: 'window:install-shell-commands' }
{ label: 'Install Shell Commands', command: 'window:install-shell-commands' }
{ type: 'separator' }
{ localisedLabel: 'core.menu.macos.services', role: 'services', submenu: [] }
{ label: 'Services', role: 'services', submenu: [] }
{ type: 'separator' }
{ localisedLabel: 'core.menu.macos.hide-self', command: 'application:hide' }
{ localisedLabel: 'core.menu.macos.hide-others', command: 'application:hide-other-applications' }
{ localisedLabel: 'core.menu.macos.show-all', command: 'application:unhide-all-applications' }
{ label: 'Hide Pulsar', command: 'application:hide' }
{ label: 'Hide Others', command: 'application:hide-other-applications' }
{ label: 'Show All', command: 'application:unhide-all-applications' }
{ type: 'separator' }
{ localisedLabel: 'core.menu.pulsar.quit', command: 'application:quit' }
{ label: 'Quit Pulsar', command: 'application:quit' }
]
}
{
localisedLabel: 'core.menu.file.self'
label: 'File'
submenu: [
{ localisedLabel: 'core.menu.file.new-window', command: 'application:new-window' }
{ localisedLabel: 'core.menu.file.new-file', command: 'application:new-file' }
{ localisedLabel: 'core.menu.file.open', command: 'application:open' }
{ localisedLabel: 'core.menu.file.add-project-folder', command: 'application:add-project-folder' }
{ label: 'New Window', command: 'application:new-window' }
{ label: 'New File', command: 'application:new-file' }
{ label: 'Open…', command: 'application:open' }
{ label: 'Add Project Folder…', command: 'application:add-project-folder' }
{
localisedLabel: 'core.menu.file.project-history.reopen-project'
label: 'Reopen Project',
submenu: [
{ localisedLabel: 'core.menu.file.project-history.clear', command: 'application:clear-project-history' }
{ label: 'Clear Project History', command: 'application:clear-project-history' }
{ type: 'separator' }
]
}
{ localisedLabel: 'core.menu.file.reopen-last-item', command: 'pane:reopen-closed-item' }
{ label: 'Reopen Last Item', command: 'pane:reopen-closed-item' }
{ type: 'separator' }
{ localisedLabel: 'core.menu.file.save', command: 'core:save' }
{ localisedLabel: 'core.menu.file.save-as', command: 'core:save-as' }
{ localisedLabel: 'core.menu.file.save-all', command: 'window:save-all' }
{ label: 'Save', command: 'core:save' }
{ label: 'Save As…', command: 'core:save-as' }
{ label: 'Save All', command: 'window:save-all' }
{ type: 'separator' }
{ localisedLabel: 'core.menu.file.close-tab', command: 'core:close' }
{ localisedLabel: 'core.menu.file.close-pane', command: 'pane:close' }
{ localisedLabel: 'core.menu.file.close-window', command: 'window:close' }
{ label: 'Close Tab', command: 'core:close' }
{ label: 'Close Pane', command: 'pane:close' }
{ label: 'Close Window', command: 'window:close' }
]
}
{
localisedLabel: 'core.menu.edit.self'
label: 'Edit'
submenu: [
{ localisedLabel: 'core.menu.edit.undo', command: 'core:undo' }
{ localisedLabel: 'core.menu.edit.redo', command: 'core:redo' }
{ label: 'Undo', command: 'core:undo' }
{ label: 'Redo', command: 'core:redo' }
{ type: 'separator' }
{ localisedLabel: 'core.menu.edit.cut', command: 'core:cut' }
{ localisedLabel: 'core.menu.edit.copy', command: 'core:copy' }
{ localisedLabel: 'core.menu.edit.copy-path', command: 'editor:copy-path' }
{ localisedLabel: 'core.menu.edit.paste', command: 'core:paste' }
{ localisedLabel: 'core.menu.edit.paste-without-reformatting', command: 'editor:paste-without-reformatting' }
{ localisedLabel: 'core.menu.edit.select-all', command: 'core:select-all' }
{ label: 'Cut', command: 'core:cut' }
{ label: 'Copy', command: 'core:copy' }
{ label: 'Copy Path', command: 'editor:copy-path' }
{ label: 'Paste', command: 'core:paste' }
{ label: 'Paste Without Reformatting', command: 'editor:paste-without-reformatting' }
{ label: 'Select All', command: 'core:select-all' }
{ type: 'separator' }
{ localisedLabel: 'core.menu.edit.toggle-comments', command: 'editor:toggle-line-comments' }
{ label: 'Toggle Comments', command: 'editor:toggle-line-comments' }
{
localisedLabel: 'core.menu.edit.lines.self',
label: 'Lines',
submenu: [
{ localisedLabel: 'core.menu.edit.lines.indent', command: 'editor:indent-selected-rows' }
{ localisedLabel: 'core.menu.edit.lines.outdent', command: 'editor:outdent-selected-rows' }
{ localisedLabel: 'core.menu.edit.lines.auto-indent', command: 'editor:auto-indent' }
{ label: 'Indent', command: 'editor:indent-selected-rows' }
{ label: 'Outdent', command: 'editor:outdent-selected-rows' }
{ label: 'Auto Indent', command: 'editor:auto-indent' }
{ type: 'separator' }
{ localisedLabel: 'core.menu.edit.lines.move-up', command: 'editor:move-line-up' }
{ localisedLabel: 'core.menu.edit.lines.move-down', command: 'editor:move-line-down' }
{ localisedLabel: 'core.menu.edit.lines.duplicate', command: 'editor:duplicate-lines' }
{ localisedLabel: 'core.menu.edit.lines.delete', command: 'editor:delete-line' }
{ localisedLabel: 'core.menu.edit.lines.join', command: 'editor:join-lines' }
{ label: 'Move Line Up', command: 'editor:move-line-up' }
{ label: 'Move Line Down', command: 'editor:move-line-down' }
{ label: 'Duplicate Lines', command: 'editor:duplicate-lines' }
{ label: 'Delete Line', command: 'editor:delete-line' }
{ label: 'Join Lines', command: 'editor:join-lines' }
]
}
{
localisedLabel: 'core.menu.edit.columns.self'
label: 'Columns',
submenu: [
{ localisedLabel: 'core.menu.edit.columns.move-selection-left', command: 'editor:move-selection-left' }
{ localisedLabel: 'core.menu.edit.columns.move-selection-right', command: 'editor:move-selection-right' }
{ label: 'Move Selection Left', command: 'editor:move-selection-left' }
{ label: 'Move Selection Right', command: 'editor:move-selection-right' }
]
}
{
localisedLabel: 'core.menu.edit.text.self'
label: 'Text',
submenu: [
{ localisedLabel: 'core.menu.edit.text.upper-case', command: 'editor:upper-case' }
{ localisedLabel: 'core.menu.edit.text.lower-case', command: 'editor:lower-case' }
{ label: 'Upper Case', command: 'editor:upper-case' }
{ label: 'Lower Case', command: 'editor:lower-case' }
{ type: 'separator' }
{ localisedLabel: 'core.menu.edit.text.delete-to-end-of-word', command: 'editor:delete-to-end-of-word' }
{ localisedLabel: 'core.menu.edit.text.delete-to-previous-word-boundary', command: 'editor:delete-to-previous-word-boundary' }
{ localisedLabel: 'core.menu.edit.text.delete-to-next-word-boundary', command: 'editor:delete-to-next-word-boundary' }
{ localisedLabel: 'core.menu.edit.text.delete-line', command: 'editor:delete-line' }
{ label: 'Delete to End of Word', command: 'editor:delete-to-end-of-word' }
{ label: 'Delete to Previous Word Boundary', command: 'editor:delete-to-previous-word-boundary' }
{ label: 'Delete to Next Word Boundary', command: 'editor:delete-to-next-word-boundary' }
{ label: 'Delete Line', command: 'editor:delete-line' }
{ type: 'separator' }
{ localisedLabel: 'core.menu.edit.text.transpose', command: 'editor:transpose' }
{ label: 'Transpose', command: 'editor:transpose' }
]
}
{
localisedLabel: 'core.menu.edit.folding.self'
label: 'Folding',
submenu: [
{ localisedLabel: 'core.menu.edit.folding.fold', command: 'editor:fold-current-row' }
{ localisedLabel: 'core.menu.edit.folding.unfold', command: 'editor:unfold-current-row' }
{ localisedLabel: 'core.menu.edit.folding.fold-all', command: 'editor:fold-all' }
{ localisedLabel: 'core.menu.edit.folding.unfold-all', command: 'editor:unfold-all' }
{ label: 'Fold', command: 'editor:fold-current-row' }
{ label: 'Unfold', command: 'editor:unfold-current-row' }
{ label: 'Fold All', command: 'editor:fold-all' }
{ label: 'Unfold All', command: 'editor:unfold-all' }
{ type: 'separator' }
{ localisedLabel: 'core.menu.edit.folding.fold-level-1', command: 'editor:fold-at-indent-level-1' }
{ localisedLabel: 'core.menu.edit.folding.fold-level-2', command: 'editor:fold-at-indent-level-2' }
{ localisedLabel: 'core.menu.edit.folding.fold-level-3', command: 'editor:fold-at-indent-level-3' }
{ localisedLabel: 'core.menu.edit.folding.fold-level-4', command: 'editor:fold-at-indent-level-4' }
{ localisedLabel: 'core.menu.edit.folding.fold-level-5', command: 'editor:fold-at-indent-level-5' }
{ localisedLabel: 'core.menu.edit.folding.fold-level-6', command: 'editor:fold-at-indent-level-6' }
{ localisedLabel: 'core.menu.edit.folding.fold-level-7', command: 'editor:fold-at-indent-level-7' }
{ localisedLabel: 'core.menu.edit.folding.fold-level-8', command: 'editor:fold-at-indent-level-8' }
{ localisedLabel: 'core.menu.edit.folding.fold-level-9', command: 'editor:fold-at-indent-level-9' }
{ label: 'Fold Level 1', command: 'editor:fold-at-indent-level-1' }
{ label: 'Fold Level 2', command: 'editor:fold-at-indent-level-2' }
{ label: 'Fold Level 3', command: 'editor:fold-at-indent-level-3' }
{ label: 'Fold Level 4', command: 'editor:fold-at-indent-level-4' }
{ label: 'Fold Level 5', command: 'editor:fold-at-indent-level-5' }
{ label: 'Fold Level 6', command: 'editor:fold-at-indent-level-6' }
{ label: 'Fold Level 7', command: 'editor:fold-at-indent-level-7' }
{ label: 'Fold Level 8', command: 'editor:fold-at-indent-level-8' }
{ label: 'Fold Level 9', command: 'editor:fold-at-indent-level-9' }
]
}
]
}
{
localisedLabel: 'core.menu.view.self'
label: 'View'
submenu: [
{ localisedLabel: 'core.menu.view.toggle-full-screen', command: 'window:toggle-full-screen' }
{ label: 'Toggle Full Screen', command: 'window:toggle-full-screen' }
{
localisedLabel: 'core.menu.view.panes.self'
label: 'Panes'
submenu: [
{ localisedLabel: 'core.menu.view.panes.split-up', command: 'pane:split-up-and-copy-active-item' }
{ localisedLabel: 'core.menu.view.panes.split-down', command: 'pane:split-down-and-copy-active-item' }
{ localisedLabel: 'core.menu.view.panes.split-left', command: 'pane:split-left-and-copy-active-item' }
{ localisedLabel: 'core.menu.view.panes.split-right', command: 'pane:split-right-and-copy-active-item' }
{ label: 'Split Up', command: 'pane:split-up-and-copy-active-item' }
{ label: 'Split Down', command: 'pane:split-down-and-copy-active-item' }
{ label: 'Split Left', command: 'pane:split-left-and-copy-active-item' }
{ label: 'Split Right', command: 'pane:split-right-and-copy-active-item' }
{ type: 'separator' }
{ localisedLabel: 'core.menu.view.panes.focus-next', command: 'window:focus-next-pane' }
{ localisedLabel: 'core.menu.view.panes.focus-previous', command: 'window:focus-previous-pane' }
{ label: 'Focus Next Pane', command: 'window:focus-next-pane' }
{ label: 'Focus Previous Pane', command: 'window:focus-previous-pane' }
{ type: 'separator' }
{ localisedLabel: 'core.menu.view.panes.focus-above', command: 'window:focus-pane-above' }
{ localisedLabel: 'core.menu.view.panes.focus-below', command: 'window:focus-pane-below' }
{ localisedLabel: 'core.menu.view.panes.focus-on-left', command: 'window:focus-pane-on-left' }
{ localisedLabel: 'core.menu.view.panes.focus-on-right', command: 'window:focus-pane-on-right' }
{ label: 'Focus Pane Above', command: 'window:focus-pane-above' }
{ label: 'Focus Pane Below', command: 'window:focus-pane-below' }
{ label: 'Focus Pane On Left', command: 'window:focus-pane-on-left' }
{ label: 'Focus Pane On Right', command: 'window:focus-pane-on-right' }
{ type: 'separator' }
{ localisedLabel: 'core.menu.view.panes.close', command: 'pane:close' }
{ label: 'Close Pane', command: 'pane:close' }
]
}
{
localisedLabel: 'core.menu.view.developer.self'
label: 'Developer'
submenu: [
{ localisedLabel: 'core.menu.view.developer.open-in-dev-mode', command: 'application:open-dev' }
{ localisedLabel: 'core.menu.view.developer.reload-window', command: 'window:reload' }
{ localisedLabel: 'core.menu.view.developer.run-package-specs', command: 'window:run-package-specs' }
{ localisedLabel: 'core.menu.view.developer.toggle-dev-tools', command: 'window:toggle-dev-tools' }
{ label: 'Open In Dev Mode…', command: 'application:open-dev' }
{ label: 'Reload Window', command: 'window:reload' }
{ label: 'Run Package Specs', command: 'window:run-package-specs' }
{ label: 'Toggle Developer Tools', command: 'window:toggle-dev-tools' }
]
}
{ type: 'separator' }
{ localisedLabel: 'core.menu.view.increase-font-size', command: 'window:increase-font-size' }
{ localisedLabel: 'core.menu.view.decrease-font-size', command: 'window:decrease-font-size' }
{ localisedLabel: 'core.menu.view.reset-font-size', command: 'window:reset-font-size' }
{ label: 'Increase Font Size', command: 'window:increase-font-size' }
{ label: 'Decrease Font Size', command: 'window:decrease-font-size' }
{ label: 'Reset Font Size', command: 'window:reset-font-size' }
{ type: 'separator' }
{ localisedLabel: 'core.menu.view.toggle-soft-wrap', command: 'editor:toggle-soft-wrap' }
{ label: 'Toggle Soft Wrap', command: 'editor:toggle-soft-wrap' }
]
}
{
localisedLabel: 'core.menu.selection.self'
label: 'Selection'
submenu: [
{ localisedLabel: 'core.menu.selection.add-above', command: 'editor:add-selection-above' }
{ localisedLabel: 'core.menu.selection.add-below', command: 'editor:add-selection-below' }
{ localisedLabel: 'core.menu.selection.single', command: 'editor:consolidate-selections' }
{ localisedLabel: 'core.menu.selection.split-into-lines', command: 'editor:split-selections-into-lines' }
{ label: 'Add Selection Above', command: 'editor:add-selection-above' }
{ label: 'Add Selection Below', command: 'editor:add-selection-below' }
{ label: 'Single Selection', command: 'editor:consolidate-selections'}
{ label: 'Split into Lines', command: 'editor:split-selections-into-lines'}
{ type: 'separator' }
{ localisedLabel: 'core.menu.selection.to-top', command: 'core:select-to-top' }
{ localisedLabel: 'core.menu.selection.to-bottom', command: 'core:select-to-bottom' }
{ label: 'Select to Top', command: 'core:select-to-top' }
{ label: 'Select to Bottom', command: 'core:select-to-bottom' }
{ type: 'separator' }
{ localisedLabel: 'core.menu.selection.line', command: 'editor:select-line' }
{ localisedLabel: 'core.menu.selection.word', command: 'editor:select-word' }
{ localisedLabel: 'core.menu.selection.to-beginning-of-word', command: 'editor:select-to-beginning-of-word' }
{ localisedLabel: 'core.menu.selection.to-beginning-of-line', command: 'editor:select-to-beginning-of-line' }
{ localisedLabel: 'core.menu.selection.to-first-char-of-line', command: 'editor:select-to-first-character-of-line' }
{ localisedLabel: 'core.menu.selection.to-end-of-word', command: 'editor:select-to-end-of-word' }
{ localisedLabel: 'core.menu.selection.to-end-of-line', command: 'editor:select-to-end-of-line' }
{ label: 'Select Line', command: 'editor:select-line' }
{ label: 'Select Word', command: 'editor:select-word' }
{ label: 'Select to Beginning of Word', command: 'editor:select-to-beginning-of-word' }
{ label: 'Select to Beginning of Line', command: 'editor:select-to-beginning-of-line' }
{ label: 'Select to First Character of Line', command: 'editor:select-to-first-character-of-line' }
{ label: 'Select to End of Word', command: 'editor:select-to-end-of-word' }
{ label: 'Select to End of Line', command: 'editor:select-to-end-of-line' }
]
}
{
localisedLabel: 'core.menu.find.self'
label: 'Find'
submenu: []
}
{
localisedLabel: 'core.menu.packages.self'
label: 'Packages'
submenu: [
{ localisedLabel: 'core.menu.packages.open-package-manager', command: 'settings-view:view-installed-packages' }
{ label: 'Open Package Manager', command: 'settings-view:view-installed-packages' }
{ type: 'separator' }
]
}
{
localisedLabel: 'core.menu.window.self'
label: 'Window'
role: 'window'
submenu: [
{ localisedLabel: 'core.menu.window.minimise', command: 'application:minimize' }
{ localisedLabel: 'core.menu.window.zoom', command: 'application:zoom' }
{ label: 'Minimize', command: 'application:minimize' }
{ label: 'Zoom', command: 'application:zoom' }
{ type: 'separator' }
{ localisedLabel: 'core.menu.window.bring-all-to-front', command: 'application:bring-all-windows-to-front' }
{ label: 'Bring All to Front', command: 'application:bring-all-windows-to-front' }
]
}
{
localisedLabel: 'core.menu.help.self'
label: 'Help'
role: 'help'
submenu: [
{ localisedLabel: 'core.menu.help.terms-of-use', command: 'application:open-terms-of-use' }
{ localisedLabel: 'core.menu.help.docs', command: 'application:open-documentation' }
{ localisedLabel: 'core.menu.help.faq', command: 'application:open-faq' }
{ label: 'Terms of Use', command: 'application:open-terms-of-use' }
{ label: 'Documentation', command: 'application:open-documentation' }
{ label: 'Frequently Asked Questions', command: 'application:open-faq' }
{ type: 'separator' }
{ localisedLabel: 'core.menu.help.community-discussions', command: 'application:open-discussions' }
{ localisedLabel: 'core.menu.help.report-issue', command: 'application:report-issue' }
{ localisedLabel: 'core.menu.help.search-issues', command: 'application:search-issues' }
{ label: 'Community Discussions', command: 'application:open-discussions' }
{ label: 'Report Issue', command: 'application:report-issue' }
{ label: 'Search Issues', command: 'application:search-issues' }
{ type: 'separator' }
]
}

View File

@ -1,204 +1,204 @@
'menu': [
{
localisedLabel: 'core.menu.file.self' # accelerator F
label: '&File'
submenu: [
{ localisedLabel: 'core.menu.file.new-window', command: 'application:new-window' } # accelerator W
{ localisedLabel: 'core.menu.file.new-file', command: 'application:new-file' } # accelerator N
{ localisedLabel: 'core.menu.file.open-file', command: 'application:open-file' } # accelerator O
{ localisedLabel: 'core.menu.file.open-folder', command: 'application:open-folder' }
{ localisedLabel: 'core.menu.file.add-project-folder', command: 'application:add-project-folder' }
{ label: 'New &Window', command: 'application:new-window' }
{ label: '&New File', command: 'application:new-file' }
{ label: '&Open File…', command: 'application:open-file' }
{ label: 'Open Folder…', command: 'application:open-folder' }
{ label: 'Add Project Folder…', command: 'application:add-project-folder' }
{
localisedLabel: 'core.menu.file.project-history.reopen-project'
label: 'Reopen Project',
submenu: [
{ localisedLabel: 'core.menu.file.project-history.clear', command: 'application:clear-project-history' }
{ label: 'Clear Project History', command: 'application:clear-project-history' }
{ type: 'separator' }
]
}
{ localisedLabel: 'core.menu.file.reopen-last-item', command: 'pane:reopen-closed-item' } # accelerator I
{ label: 'Reopen Last &Item', command: 'pane:reopen-closed-item' }
{ type: 'separator' }
{ localisedLabel: 'core.menu.file.save', command: 'core:save' } # accelerator S
{ localisedLabel: 'core.menu.file.save-as', command: 'core:save-as' } # accelerator A
{ localisedLabel: 'core.menu.file.save-all', command: 'window:save-all' } # accelerator L
{ label: '&Save', command: 'core:save' }
{ label: 'Save &As…', command: 'core:save-as' }
{ label: 'Save A&ll', command: 'window:save-all' }
{ type: 'separator' }
{ localisedLabel: 'core.menu.file.close-tab', command: 'core:close' } # accelerator C
{ localisedLabel: 'core.menu.file.close-pane', command: 'pane:close' } # accelerator P
{ localisedLabel: 'core.menu.file.close-window', command: 'window:close' } # accelerator E
{ label: '&Close Tab', command: 'core:close' }
{ label: 'Close &Pane', command: 'pane:close' }
{ label: 'Clos&e Window', command: 'window:close' }
{ type: 'separator' }
{ localisedLabel: 'core.menu.pulsar.quit', command: 'application:quit' }
{ label: 'Quit', command: 'application:quit' }
]
}
{
localisedLabel: 'core.menu.edit.self' # accelerator E
label: '&Edit'
submenu: [
{ localisedLabel: 'core.menu.edit.undo', command: 'core:undo' } # accelerator U
{ localisedLabel: 'core.menu.edit.redo', command: 'core:redo' } # accelerator R
{ label: '&Undo', command: 'core:undo' }
{ label: '&Redo', command: 'core:redo' }
{ type: 'separator' }
{ localisedLabel: 'core.menu.edit.cut', command: 'core:cut' } # accelerator C
{ localisedLabel: 'core.menu.edit.copy', command: 'core:copy' } # accelerator O
{ localisedLabel: 'core.menu.edit.copy-path', command: 'editor:copy-path' } # accelerator H
{ localisedLabel: 'core.menu.edit.paste', command: 'core:paste' } # accelerator P
{ localisedLabel: 'core.menu.edit.paste-without-reformatting', command: 'editor:paste-without-reformatting' }
{ localisedLabel: 'core.menu.edit.select-all', command: 'core:select-all' } # accelerator A
{ label: '&Cut', command: 'core:cut' }
{ label: 'C&opy', command: 'core:copy' }
{ label: 'Copy Pat&h', command: 'editor:copy-path' }
{ label: '&Paste', command: 'core:paste' }
{ label: 'Paste Without Reformatting', command: 'editor:paste-without-reformatting' }
{ label: 'Select &All', command: 'core:select-all' }
{ type: 'separator' }
{ localisedLabel: 'core.menu.edit.toggle-comments', command: 'editor:toggle-line-comments' } # accelerator T
{ label: '&Toggle Comments', command: 'editor:toggle-line-comments' }
{
localisedLabel: 'core.menu.edit.lines.self'
label: 'Lines',
submenu: [
{ localisedLabel: 'core.menu.edit.lines.indent', command: 'editor:indent-selected-rows' } # accelerator I
{ localisedLabel: 'core.menu.edit.lines.outdent', command: 'editor:outdent-selected-rows' } # accelerator O
{ localisedLabel: 'core.menu.edit.lines.auto-indent', command: 'editor:auto-indent' } # accelerator A
{ label: '&Indent', command: 'editor:indent-selected-rows' }
{ label: '&Outdent', command: 'editor:outdent-selected-rows' }
{ label: '&Auto Indent', command: 'editor:auto-indent' }
{ type: 'separator' }
{ localisedLabel: 'core.menu.edit.lines.move-up', command: 'editor:move-line-up' } # accelerator U
{ localisedLabel: 'core.menu.edit.lines.move-down', command: 'editor:move-line-down' } # accelerator D
{ localisedLabel: 'core.menu.edit.lines.duplicate', command: 'editor:duplicate-lines' } # accelerator P
{ localisedLabel: 'core.menu.edit.lines.delete', command: 'editor:delete-line' } # accelerator E
{ localisedLabel: 'core.menu.edit.lines.join', command: 'editor:join-lines' } # accelerator J
{ label: 'Move Line &Up', command: 'editor:move-line-up' }
{ label: 'Move Line &Down', command: 'editor:move-line-down' }
{ label: 'Du&plicate Lines', command: 'editor:duplicate-lines' }
{ label: 'D&elete Line', command: 'editor:delete-line' }
{ label: '&Join Lines', command: 'editor:join-lines' }
]
}
{
localisedLabel: 'core.menu.edit.columns.self'
label: 'Columns',
submenu: [
{ localisedLabel: 'core.menu.edit.columns.move-selection-left', command: 'editor:move-selection-left' } # accelerator L
{ localisedLabel: 'core.menu.edit.columns.move-selection-right', command: 'editor:move-selection-right' } # accelerator R
{ label: 'Move Selection &Left', command: 'editor:move-selection-left' }
{ label: 'Move Selection &Right', command: 'editor:move-selection-right' }
]
}
{
localisedLabel: 'core.menu.edit.text.self'
label: 'Text',
submenu: [
{ localisedLabel: 'core.menu.edit.text.upper-case', command: 'editor:upper-case' } # accelerator U
{ localisedLabel: 'core.menu.edit.text.lower-case', command: 'editor:lower-case' } # accelerator L
{ label: '&Upper Case', command: 'editor:upper-case' }
{ label: '&Lower Case', command: 'editor:lower-case' }
{ type: 'separator' }
{ localisedLabel: 'core.menu.edit.text.delete-to-end-of-word', command: 'editor:delete-to-end-of-word' } # accelerator W
{ localisedLabel: 'core.menu.edit.text.delete-to-previous-word-boundary', command: 'editor:delete-to-previous-word-boundary' }
{ localisedLabel: 'core.menu.edit.text.delete-to-next-word-boundary', command: 'editor:delete-to-next-word-boundary' }
{ localisedLabel: 'core.menu.edit.text.delete-line', command: 'editor:delete-line' } # accelerator D
{ label: 'Delete to End of &Word', command: 'editor:delete-to-end-of-word' }
{ label: 'Delete to Previous Word Boundary', command: 'editor:delete-to-previous-word-boundary' }
{ label: 'Delete to Next Word Boundary', command: 'editor:delete-to-next-word-boundary' }
{ label: '&Delete Line', command: 'editor:delete-line' }
{ type: 'separator' }
{ localisedLabel: 'core.menu.edit.text.transpose', command: 'editor:transpose' } # accelerator T
{ label: '&Transpose', command: 'editor:transpose' }
]
}
{
localisedLabel: 'core.menu.edit.folding.self'
label: 'Folding',
submenu: [
{ localisedLabel: 'core.menu.edit.folding.fold', command: 'editor:fold-current-row' } # accelerator F
{ localisedLabel: 'core.menu.edit.folding.unfold', command: 'editor:unfold-current-row' } # accelerator U
{ localisedLabel: 'core.menu.edit.folding.fold-all', command: 'editor:fold-all' } # accelerator D
{ localisedLabel: 'core.menu.edit.folding.unfold-all', command: 'editor:unfold-all' } # accelerator A
{ label: '&Fold', command: 'editor:fold-current-row' }
{ label: '&Unfold', command: 'editor:unfold-current-row' }
{ label: 'Fol&d All', command: 'editor:fold-all' }
{ label: 'Unfold &All', command: 'editor:unfold-all' }
{ type: 'separator' }
{ localisedLabel: 'core.menu.edit.folding.fold-level-1', command: 'editor:fold-at-indent-level-1' }
{ localisedLabel: 'core.menu.edit.folding.fold-level-2', command: 'editor:fold-at-indent-level-2' }
{ localisedLabel: 'core.menu.edit.folding.fold-level-3', command: 'editor:fold-at-indent-level-3' }
{ localisedLabel: 'core.menu.edit.folding.fold-level-4', command: 'editor:fold-at-indent-level-4' }
{ localisedLabel: 'core.menu.edit.folding.fold-level-5', command: 'editor:fold-at-indent-level-5' }
{ localisedLabel: 'core.menu.edit.folding.fold-level-6', command: 'editor:fold-at-indent-level-6' }
{ localisedLabel: 'core.menu.edit.folding.fold-level-7', command: 'editor:fold-at-indent-level-7' }
{ localisedLabel: 'core.menu.edit.folding.fold-level-8', command: 'editor:fold-at-indent-level-8' }
{ localisedLabel: 'core.menu.edit.folding.fold-level-9', command: 'editor:fold-at-indent-level-9' }
{ label: 'Fold Level 1', command: 'editor:fold-at-indent-level-1' }
{ label: 'Fold Level 2', command: 'editor:fold-at-indent-level-2' }
{ label: 'Fold Level 3', command: 'editor:fold-at-indent-level-3' }
{ label: 'Fold Level 4', command: 'editor:fold-at-indent-level-4' }
{ label: 'Fold Level 5', command: 'editor:fold-at-indent-level-5' }
{ label: 'Fold Level 6', command: 'editor:fold-at-indent-level-6' }
{ label: 'Fold Level 7', command: 'editor:fold-at-indent-level-7' }
{ label: 'Fold Level 8', command: 'editor:fold-at-indent-level-8' }
{ label: 'Fold Level 9', command: 'editor:fold-at-indent-level-9' }
]
}
{ type: 'separator' }
{ localisedLabel: 'core.menu.pulsar.preferences', command: 'application:show-settings' } # accelerator P
{ label: '&Preferences', command: 'application:show-settings' }
{ type: 'separator' }
{ localisedLabel: 'core.menu.pulsar.config', command: 'application:open-your-config' }
{ localisedLabel: 'core.menu.pulsar.init-script', command: 'application:open-your-init-script' }
{ localisedLabel: 'core.menu.pulsar.keymap', command: 'application:open-your-keymap' }
{ localisedLabel: 'core.menu.pulsar.snippets', command: 'application:open-your-snippets' }
{ localisedLabel: 'core.menu.pulsar.stylesheet', command: 'application:open-your-stylesheet' }
{ label: 'Config…', command: 'application:open-your-config' }
{ label: 'Init Script…', command: 'application:open-your-init-script' }
{ label: 'Keymap…', command: 'application:open-your-keymap' }
{ label: 'Snippets…', command: 'application:open-your-snippets' }
{ label: 'Stylesheet…', command: 'application:open-your-stylesheet' }
{ type: 'separator' }
]
}
{
localisedLabel: 'core.menu.view.self' # accelerator V
label: '&View'
submenu: [
{ localisedLabel: 'core.menu.view.toggle-full-screen', command: 'window:toggle-full-screen' } # accelerator F
{ localisedLabel: 'core.menu.view.toggle-menu-bar', command: 'window:toggle-menu-bar' }
{ label: 'Toggle &Full Screen', command: 'window:toggle-full-screen' }
{ label: 'Toggle Menu Bar', command: 'window:toggle-menu-bar' }
{
localisedLabel: 'core.menu.view.panes.self'
label: 'Panes'
submenu: [
{ localisedLabel: 'core.menu.view.panes.split-up', command: 'pane:split-up-and-copy-active-item' }
{ localisedLabel: 'core.menu.view.panes.split-down', command: 'pane:split-down-and-copy-active-item' }
{ localisedLabel: 'core.menu.view.panes.split-left', command: 'pane:split-left-and-copy-active-item' }
{ localisedLabel: 'core.menu.view.panes.split-right', command: 'pane:split-right-and-copy-active-item' }
{ label: 'Split Up', command: 'pane:split-up-and-copy-active-item' }
{ label: 'Split Down', command: 'pane:split-down-and-copy-active-item' }
{ label: 'Split Left', command: 'pane:split-left-and-copy-active-item' }
{ label: 'Split Right', command: 'pane:split-right-and-copy-active-item' }
{ type: 'separator' }
{ localisedLabel: 'core.menu.view.panes.focus-next', command: 'window:focus-next-pane' }
{ localisedLabel: 'core.menu.view.panes.focus-previous', command: 'window:focus-previous-pane' }
{ label: 'Focus Next Pane', command: 'window:focus-next-pane' }
{ label: 'Focus Previous Pane', command: 'window:focus-previous-pane' }
{ type: 'separator' }
{ localisedLabel: 'core.menu.view.panes.focus-above', command: 'window:focus-pane-above' }
{ localisedLabel: 'core.menu.view.panes.focus-below', command: 'window:focus-pane-below' }
{ localisedLabel: 'core.menu.view.panes.focus-on-left', command: 'window:focus-pane-on-left' }
{ localisedLabel: 'core.menu.view.panes.focus-on-right', command: 'window:focus-pane-on-right' }
{ label: 'Focus Pane Above', command: 'window:focus-pane-above' }
{ label: 'Focus Pane Below', command: 'window:focus-pane-below' }
{ label: 'Focus Pane On Left', command: 'window:focus-pane-on-left' }
{ label: 'Focus Pane On Right', command: 'window:focus-pane-on-right' }
{ type: 'separator' }
{ localisedLabel: 'core.menu.view.panes.close', command: 'pane:close' }
{ label: 'Close Pane', command: 'pane:close' }
]
}
{
localisedLabel: 'core.menu.view.developer.self'
label: 'Developer'
submenu: [
{ localisedLabel: 'core.menu.view.developer.open-in-dev-mode', command: 'application:open-dev' } # accelerator D
{ localisedLabel: 'core.menu.view.developer.reload-window', command: 'window:reload' } # accelerator R
{ localisedLabel: 'core.menu.view.developer.run-package-specs', command: 'window:run-package-specs' } # accelerator S
{ localisedLabel: 'core.menu.view.developer.toggle-dev-tools', command: 'window:toggle-dev-tools' } # accelerator T
{ label: 'Open In &Dev Mode…', command: 'application:open-dev' }
{ label: '&Reload Window', command: 'window:reload' }
{ label: 'Run Package &Specs', command: 'window:run-package-specs' }
{ label: 'Toggle Developer &Tools', command: 'window:toggle-dev-tools' }
]
}
{ type: 'separator' }
{ localisedLabel: 'core.menu.view.increase-font-size', command: 'window:increase-font-size' } # accelerator I
{ localisedLabel: 'core.menu.view.decrease-font-size', command: 'window:decrease-font-size' } # accelerator D
{ localisedLabel: 'core.menu.view.reset-font-size', command: 'window:reset-font-size' } # accelerator S
{ label: '&Increase Font Size', command: 'window:increase-font-size' }
{ label: '&Decrease Font Size', command: 'window:decrease-font-size' }
{ label: 'Re&set Font Size', command: 'window:reset-font-size' }
{ type: 'separator' }
{ localisedLabel: 'core.menu.view.toggle-soft-wrap', command: 'editor:toggle-soft-wrap' } # accelerator W
{ label: 'Toggle Soft &Wrap', command: 'editor:toggle-soft-wrap' }
]
}
{
localisedLabel: 'core.menu.selection.self' # accelerator S
label: '&Selection'
submenu: [
{ localisedLabel: 'core.menu.selection.add-above', command: 'editor:add-selection-above' } # accelerator A
{ localisedLabel: 'core.menu.selection.add-below', command: 'editor:add-selection-below' } # accelerator B
{ localisedLabel: 'core.menu.selection.single', command: 'editor:consolidate-selections' }
{ localisedLabel: 'core.menu.selection.split-into-lines', command: 'editor:split-selections-into-lines' } # accelerator P
{ label: 'Add Selection &Above', command: 'editor:add-selection-above' }
{ label: 'Add Selection &Below', command: 'editor:add-selection-below' }
{ label: 'S&plit into Lines', command: 'editor:split-selections-into-lines'}
{ label: 'Single Selection', command: 'editor:consolidate-selections'}
{ type: 'separator' }
{ localisedLabel: 'core.menu.selection.to-top', command: 'core:select-to-top' } # accelerator T
{ localisedLabel: 'core.menu.selection.to-bottom', command: 'core:select-to-bottom' } # accelerator M
{ label: 'Select to &Top', command: 'core:select-to-top' }
{ label: 'Select to Botto&m', command: 'core:select-to-bottom' }
{ type: 'separator' }
{ localisedLabel: 'core.menu.selection.line', command: 'editor:select-line' } # accelerator L
{ localisedLabel: 'core.menu.selection.word', command: 'editor:select-word' } # accelerator W
{ localisedLabel: 'core.menu.selection.to-beginning-of-word', command: 'editor:select-to-beginning-of-word' } # accelerator O
{ localisedLabel: 'core.menu.selection.to-beginning-of-line', command: 'editor:select-to-beginning-of-line' } # accelerator I
{ localisedLabel: 'core.menu.selection.to-first-char-of-line', command: 'editor:select-to-first-character-of-line' } # accelerator C
{ localisedLabel: 'core.menu.selection.to-end-of-word', command: 'editor:select-to-end-of-word' } # accelerator D
{ localisedLabel: 'core.menu.selection.to-end-of-line', command: 'editor:select-to-end-of-line' } # accelerator E
{ label: 'Select &Line', command: 'editor:select-line' }
{ label: 'Select &Word', command: 'editor:select-word' }
{ label: 'Select to Beginning of W&ord', command: 'editor:select-to-beginning-of-word' }
{ label: 'Select to Beginning of L&ine', command: 'editor:select-to-beginning-of-line' }
{ label: 'Select to First &Character of Line', command: 'editor:select-to-first-character-of-line' }
{ label: 'Select to End of Wor&d', command: 'editor:select-to-end-of-word' }
{ label: 'Select to End of Lin&e', command: 'editor:select-to-end-of-line' }
]
}
{
localisedLabel: 'core.menu.find.self' # accelerator I
label: 'F&ind'
submenu: []
}
{
localisedLabel: 'core.menu.packages.self' # accelerator P
label: '&Packages'
submenu: [
{ localisedLabel: 'core.menu.packages.open-package-manager', command: 'settings-view:view-installed-packages' }
{ label: 'Open Package Manager', command: 'settings-view:view-installed-packages' }
{ type: 'separator' }
]
}
{
localisedLabel: 'core.menu.help.self' # accelerator H
label: '&Help'
submenu: [
{ localisedLabel: 'core.menu.help.terms-of-use', command: 'application:open-terms-of-use' } # accelerator T
{ localisedLabel: 'core.menu.pulsar.view-license', command: 'application:open-license' } # accelerator L
{ localisedLabel: 'core.menu.pulsar.version', enabled: false }
{ label: 'View &Terms of Use', command: 'application:open-terms-of-use' }
{ label: 'View &License', command: 'application:open-license' }
{ label: "VERSION", enabled: false }
{ type: 'separator' }
{ localisedLabel: 'core.menu.help.docs', command: 'application:open-documentation' } # accelerator D
{ localisedLabel: 'core.menu.help.faq', command: 'application:open-faq' }
{ label: '&Documentation', command: 'application:open-documentation' }
{ label: 'Frequently Asked Questions', command: 'application:open-faq' }
{ type: 'separator' }
{ localisedLabel: 'core.menu.help.community-discussions', command: 'application:open-discussions' }
{ localisedLabel: 'core.menu.help.report-issue', 'application:report-issue' }
{ localisedLabel: 'core.menu.help.search-issues', command: 'application:search-issues' }
{ label: 'Community Discussions', command: 'application:open-discussions' }
{ label: 'Report Issue', command: 'application:report-issue' }
{ label: 'Search Issues', command: 'application:search-issues' }
{ type: 'separator' }
{ localisedLabel: 'core.menu.pulsar.about', command: 'application:about' }
{ label: 'About Pulsar', command: 'application:about' }
{ type: 'separator' }
]
}

View File

@ -2,7 +2,7 @@
"name": "pulsar",
"author": "Pulsar-Edit <admin@pulsar-edit.dev>",
"productName": "Pulsar",
"version": "1.103.0-dev",
"version": "1.108.0-dev",
"description": "A Community-led Hyper-Hackable Text Editor",
"branding": {
"id": "pulsar",
@ -19,12 +19,17 @@
"bugs": {
"url": "https://github.com/pulsar-edit/pulsar/issues"
},
"engines": {
"node": ">=14"
},
"license": "MIT",
"electronVersion": "12.2.3",
"resolutions": {
"es5-ext": "https://github.com/pulsar-edit/es5-ext#169f6ae9b2675675269a0ba265f83c29c7b56244"
},
"dependencies": {
"@atom/source-map-support": "^0.3.4",
"@babel/core": "7.18.6",
"@formatjs/icu-messageformat-parser": "^2.3.0",
"about": "file:packages/about",
"archive-view": "file:packages/archive-view",
"async": "3.2.4",
@ -34,13 +39,13 @@
"atom-light-syntax": "file:packages/atom-light-syntax",
"atom-light-ui": "file:packages/atom-light-ui",
"atom-select-list": "^0.8.1",
"autocomplete-atom-api": "https://codeload.github.com/atom/autocomplete-atom-api/legacy.tar.gz/refs/tags/v0.10.7",
"autocomplete-atom-api": "file:packages/autocomplete-atom-api",
"autocomplete-css": "file:packages/autocomplete-css",
"autocomplete-html": "file:packages/autocomplete-html",
"autocomplete-plus": "file:./packages/autocomplete-plus",
"autocomplete-snippets": "file:packages/autocomplete-snippets",
"autoflow": "file:packages/autoflow",
"autosave": "https://codeload.github.com/atom/autosave/legacy.tar.gz/refs/tags/v0.24.6",
"autosave": "file:./packages/autosave",
"babel-preset-atomic": "^5.0.0",
"background-tips": "file:packages/background-tips",
"base16-tomorrow-dark-theme": "file:packages/base16-tomorrow-dark-theme",
@ -61,29 +66,28 @@
"etch": "0.14.1",
"event-kit": "^2.5.3",
"exception-reporting": "file:packages/exception-reporting",
"find-and-replace": "https://github.com/atom-community/find-and-replace/archive/refs/tags/v0.220.1.tar.gz",
"find-and-replace": "file:packages/find-and-replace",
"find-parent-dir": "^0.3.0",
"second-mate": "https://github.com/pulsar-edit/second-mate.git#14aa7bd",
"focus-trap": "6.3.0",
"fs-admin": "0.19.0",
"fs-plus": "^3.1.1",
"fstream": "1.0.12",
"fuzzy-finder": "https://codeload.github.com/atom/fuzzy-finder/legacy.tar.gz/refs/tags/v1.14.3",
"functional-red-black-tree": "^1.0.1",
"fuzzy-finder": "file:packages/fuzzy-finder",
"git-diff": "file:packages/git-diff",
"git-utils": "5.7.1",
"github": "https://codeload.github.com/pulsar-edit/github/tar.gz/refs/tags/v0.36.15-pretranspiled",
"github": "https://codeload.github.com/pulsar-edit/github/tar.gz/refs/tags/v0.36.17-pretranspiled",
"glob": "^7.1.1",
"go-to-line": "file:packages/go-to-line",
"grammar-selector": "file:packages/grammar-selector",
"grim": "2.0.3",
"image-view": "file:packages/image-view",
"incompatible-packages": "file:packages/incompatible-packages",
"intl-messageformat": "^10.3.3",
"jasmine-json": "~0.0",
"jasmine-reporters": "1.1.0",
"jasmine-tagged": "^1.1.4",
"key-path-helpers": "^0.4.0",
"keybinding-resolver": "https://codeload.github.com/atom/keybinding-resolver/legacy.tar.gz/refs/tags/v0.39.1",
"keybinding-resolver": "file:./packages/keybinding-resolver",
"language-c": "file:packages/language-c",
"language-clojure": "file:packages/language-clojure",
"language-coffee-script": "file:packages/language-coffee-script",
@ -118,7 +122,7 @@
"language-typescript": "file:packages/language-typescript",
"language-xml": "file:packages/language-xml",
"language-yaml": "file:packages/language-yaml",
"less-cache": "1.1.0",
"less-cache": "pulsar-edit/less-cache#v2.0.1",
"line-ending-selector": "file:packages/line-ending-selector",
"line-top-index": "0.3.1",
"link": "file:packages/link",
@ -129,9 +133,8 @@
"mocha-multi-reporters": "^1.1.4",
"mock-spawn": "^0.2.6",
"normalize-package-data": "3.0.2",
"notifications": "https://codeload.github.com/atom/notifications/legacy.tar.gz/refs/tags/v0.72.1",
"notifications": "file:./packages/notifications",
"nsfw": "2.2.2",
"nslog": "^3.0.0",
"one-dark-syntax": "file:packages/one-dark-syntax",
"one-dark-ui": "file:packages/one-dark-ui",
"one-light-syntax": "file:packages/one-light-syntax",
@ -139,22 +142,24 @@
"open-on-github": "file:packages/open-on-github",
"package-generator": "file:packages/package-generator",
"pathwatcher": "^8.1.2",
"postcss": "8.2.10",
"postcss": "8.2.13",
"postcss-selector-parser": "6.0.4",
"property-accessors": "^1.1.3",
"pulsar-updater": "file:packages/pulsar-updater",
"resolve": "1.18.1",
"scandal": "^3.2.0",
"scoped-property-store": "^0.17.0",
"scrollbar-style": "^4.0.1",
"season": "^6.0.2",
"semver": "7.3.8",
"second-mate": "https://github.com/pulsar-edit/second-mate.git#9686771",
"semver": "7.5.2",
"service-hub": "^0.7.4",
"settings-view": "file:packages/settings-view",
"sinon": "9.2.1",
"snippets": "github:pulsar-edit/snippets#bb00f909c6c645b173f27346875d8fa0c7af09f7",
"snippets": "github:pulsar-edit/snippets#ba70705",
"solarized-dark-syntax": "file:packages/solarized-dark-syntax",
"solarized-light-syntax": "file:packages/solarized-light-syntax",
"spell-check": "https://codeload.github.com/atom/spell-check/legacy.tar.gz/refs/tags/v0.77.1",
"spell-check": "file:packages/spell-check",
"status-bar": "file:packages/status-bar",
"styleguide": "file:./packages/styleguide",
"superstring": "^2.4.4",
@ -162,15 +167,16 @@
"tabs": "file:packages/tabs",
"temp": "0.9.4",
"text-buffer": "^13.18.6",
"timecop": "https://codeload.github.com/atom/timecop/legacy.tar.gz/refs/tags/v0.36.2",
"timecop": "file:./packages/timecop",
"tree-sitter": "0.20.0",
"tree-view": "https://codeload.github.com/atom/tree-view/legacy.tar.gz/refs/tags/v0.229.1",
"typescript-simple": "8.0.6",
"tree-view": "file:packages/tree-view",
"typescript-simple": "github:pulsar-edit/typescript-simple#ccb03e558217030e8f261339281f1d69147934f7",
"underscore-plus": "^1.7.0",
"update-package-dependencies": "file:./packages/update-package-dependencies",
"vscode-ripgrep": "1.9.0",
"web-tree-sitter": "^0.20.7",
"welcome": "file:packages/welcome",
"whitespace": "https://codeload.github.com/atom/whitespace/legacy.tar.gz/refs/tags/v0.37.8",
"whitespace": "file:./packages/whitespace",
"winreg": "^1.2.1",
"wrap-guide": "file:./packages/wrap-guide",
"yargs": "17.6.2"
@ -190,49 +196,50 @@
"solarized-light-syntax": "file:./packages/solarized-light-syntax",
"about": "file:./packages/about",
"archive-view": "file:./packages/archive-view",
"autocomplete-atom-api": "0.10.7",
"autocomplete-atom-api": "file:packages/autocomplete-atom-api",
"autocomplete-css": "file:./packages/autocomplete-css",
"autocomplete-html": "file:./packages/autocomplete-html",
"autocomplete-plus": "file:./packages/autocomplete-plus",
"autocomplete-snippets": "file:./packages/autocomplete-snippets",
"autoflow": "file:./packages/autoflow",
"autosave": "0.24.6",
"autosave": "file:./packages/autosave",
"background-tips": "file:./packages/background-tips",
"bookmarks": "file:./packages/bookmarks",
"bracket-matcher": "0.92.0",
"bracket-matcher": "file:./packages/bracket-matcher",
"command-palette": "file:./packages/command-palette",
"dalek": "file:./packages/dalek",
"deprecation-cop": "file:./packages/deprecation-cop",
"dev-live-reload": "file:./packages/dev-live-reload",
"encoding-selector": "file:./packages/encoding-selector",
"exception-reporting": "file:./packages/exception-reporting",
"find-and-replace": "0.220.1",
"fuzzy-finder": "1.14.3",
"github": "0.36.14",
"find-and-replace": "file:./packages/find-and-replace",
"fuzzy-finder": "file:packages/fuzzy-finder",
"github": "0.36.17",
"git-diff": "file:./packages/git-diff",
"go-to-line": "file:./packages/go-to-line",
"grammar-selector": "file:./packages/grammar-selector",
"image-view": "file:./packages/image-view",
"incompatible-packages": "file:./packages/incompatible-packages",
"keybinding-resolver": "0.39.1",
"keybinding-resolver": "file:./packages/keybinding-resolver",
"line-ending-selector": "file:./packages/line-ending-selector",
"link": "file:./packages/link",
"markdown-preview": "file:./packages/markdown-preview",
"notifications": "0.72.1",
"notifications": "file:./packages/notifications",
"open-on-github": "file:./packages/open-on-github",
"package-generator": "file:./packages/package-generator",
"pulsar-updater": "file:./packages/pulsar-updater",
"settings-view": "file:./packages/settings-view",
"snippets": "1.6.1",
"spell-check": "0.77.1",
"spell-check": "file:./packages/spell-check",
"status-bar": "file:./packages/status-bar",
"styleguide": "file:./packages/styleguide",
"symbols-view": "0.118.4",
"tabs": "file:./packages/tabs",
"timecop": "0.36.2",
"tree-view": "0.229.1",
"timecop": "file:./packages/timecop",
"tree-view": "file:./packages/tree-view",
"update-package-dependencies": "file:./packages/update-package-dependencies",
"welcome": "file:./packages/welcome",
"whitespace": "0.37.8",
"whitespace": "file:./packages/whitespace",
"wrap-guide": "file:./packages/wrap-guide",
"language-c": "file:./packages/language-c",
"language-clojure": "file:./packages/language-clojure",

View File

@ -13,33 +13,33 @@ See [RFC 003](https://github.com/atom/atom/blob/master/docs/rfcs/003-consolidate
| **atom-dark-ui** | [`./atom-dark-ui`](./atom-dark-ui) | |
| **atom-light-syntax** | [`./atom-light-syntax`](./atom-light-syntax) | |
| **atom-light-ui** | [`./atom-light-ui`](./atom-light-ui) | |
| **autocomplete-atom-api** | [`atom/autocomplete-atom-api`][autocomplete-atom-api] | |
| **autocomplete-atom-api** | [`./autocomplete-atom-api`](./autocomplete-atom-api) | |
| **autocomplete-css** | [`./autocomplete-css`](./autocomplete-css) | |
| **autocomplete-html** | [`./autocomplete-html`](./autocomplete-html) | |
| **autocomplete-plus** | [`./autocomplete-plus`](./autocomplete-plus) | |
| **autocomplete-snippets** | [`./autocomplete-snippets`](./autocomplete-snippets) | |
| **autoflow** | [`./autoflow`](./autoflow) | |
| **autosave** | [`pulsar-edit/autosave`][autosave] | [#17834](https://github.com/atom/atom/issues/17834) |
| **autosave** | [`./autosave`](./autosave) | |
| **background-tips** | [`./background-tips`](./background-tips) | |
| **base16-tomorrow-dark-theme** | [`./base16-tomorrow-dark-theme`](./base16-tomorrow-dark-theme) | |
| **base16-tomorrow-light-theme** | [`./base16-tomorrow-light-theme`](./base16-tomorrow-light-theme) | |
| **bookmarks** | [`./bookmarks`](./bookmarks) | |
| **bracket-matcher** | [`atom/bracket-matcher`][bracket-matcher] | |
| **bracket-matcher** | [`./bracket-matcher`](./bracket-matcher) | |
| **command-palette** | [`./command-palette`](./command-palette) | |
| **dalek** | [`./dalek`](./dalek) | |
| **deprecation-cop** | [`./deprecation-cop`](./deprecation-cop) | |
| **dev-live-reload** | [`./dev-live-reload`](./dev-live-reload) | |
| **encoding-selector** | [`./encoding-selector`](./encoding-selector) | |
| **exception-reporting** | [`./exception-reporting`](./exception-reporting) | |
| **find-and-replace** | [`pulsar-edit/find-and-replace`][find-and-replace] | |
| **fuzzy-finder** | [`pulsar-edit/fuzzy-finder`][fuzzy-finder] | |
| **find-and-replace** | [`./find-and-replace`](./find-and-replace) | |
| **fuzzy-finder** | [`./fuzzy-finder`](./fuzzy-finder) | |
| **github** | [`pulsar-edit/github`][github] | |
| **git-diff** | [`./git-diff`](./git-diff) | |
| **go-to-line** | [`./go-to-line`](./go-to-line) | |
| **grammar-selector** | [`./grammar-selector`](./grammar-selector) | |
| **image-view** | [`./image-view`](./image-view) | |
| **incompatible-packages** | [`./incompatible-packages`](./incompatible-packages) | |
| **keybinding-resolver** | [`atom/keybinding-resolver`][keybinding-resolver] | [#18275](https://github.com/atom/atom/issues/18275) |
| **keybinding-resolver** | [`./keybinding-resolver`](./keybinding-resolver) | |
| **language-c** | [`./language-c`](./language-c) | |
| **language-clojure** | [`./language-clojure`](./language-clojure) | |
| **language-coffee-script** | [`./language-coffee-script`](./language-coffee-script) | |
@ -77,7 +77,7 @@ See [RFC 003](https://github.com/atom/atom/blob/master/docs/rfcs/003-consolidate
| **line-ending-selector** | [`./line-ending-selector`](./line-ending-selector) | |
| **link** | [`./link`](./link) | |
| **markdown-preview** | [`./markdown-preview`](./markdown-preview) | |
| **notifications** | [`atom/notifications`][notifications] | [#18277](https://github.com/atom/atom/issues/18277) |
| **notifications** | [`./notifications`](./notifications) | |
| **one-dark-syntax** | [`./one-dark-syntax`](./one-dark-syntax) | |
| **one-dark-ui** | [`./one-dark-ui`](./one-dark-ui) | |
| **one-light-syntax** | [`./one-light-syntax`](./one-light-syntax) | |
@ -85,31 +85,22 @@ See [RFC 003](https://github.com/atom/atom/blob/master/docs/rfcs/003-consolidate
| **open-on-github** | [`./open-on-github`](./open-on-github) | |
| **settings-view** | [`./settings-view`](./settings-view) | |
| **package-generator** | [`./package-generator`](./package-generator) | |
| **pulsar-updater** | [`./pulsar-updater`](./pulsar-updater) | |
| **snippets** | [`pulsar-edit/snippets`][snippets] | |
| **solarized-dark-syntax** | [`./solarized-dark-syntax`](./solarized-dark-syntax) | |
| **solarized-light-syntax** | [`./solarized-light-syntax`](./solarized-light-syntax) | |
| **spell-check** | [`atom/spell-check`][spell-check] | |
| **spell-check** | [`./spell-check`](./spell-check) | |
| **status-bar** | [`./status-bar`](./status-bar) | |
| **styleguide** | [`./styleguide`](./styleguide) | |
| **symbols-view** | [`pulsar-edit/symbols-view`][symbols-view] | |
| **tabs** | [`./tabs`](./tabs) | |
| **timecop** | [`pulsar-edit/timecop`][timecop] | [#18272](https://github.com/atom/atom/issues/18272) |
| **tree-view** | [`pulsar-edit/tree-view`][tree-view] | |
| **timecop** | [`./timecop`](./timecop) | |
| **tree-view** | [`./tree-view`](./tree-view) | |
| **update-package-dependencies** | [`./update-package-dependencies`](./update-package-dependencies) | |
| **welcome** | [`./welcome`](./welcome) | |
| **whitespace** | [`./whitespace`](./whitespace) | |
| **wrap-guide** | [`./wrap-guide`](./wrap-guide) | |
[autocomplete-atom-api]: https://github.com/pulsar-edit/autocomplete-atom-api
[autosave]: https://github.com/pulsar-edit/autosave
[bracket-matcher]: https://github.com/pulsar-edit/bracket-matcher
[find-and-replace]: https://github.com/pulsar-edit/find-and-replace
[fuzzy-finder]: https://github.com/pulsar-edit/fuzzy-finder
[github]: https://github.com/pulsar-edit/github
[keybinding-resolver]: https://github.com/pulsar-edit/keybinding-resolver
[notifications]: https://github.com/pulsar-edit/notifications
[snippets]: https://github.com/pulsar-edit/snippets
[spell-check]: https://github.com/pulsar-edit/spell-check
[symbols-view]: https://github.com/pulsar-edit/symbols-view
[timecop]: https://github.com/pulsar-edit/timecop
[tree-view]: https://github.com/pulsar-edit/tree-view

View File

@ -36,7 +36,6 @@ module.exports = class About {
if (this.views.aboutView) this.views.aboutView.destroy();
this.views.aboutView = null;
if (this.state.updateManager) this.state.updateManager.dispose();
this.setState({ updateManager: null });
this.subscriptions.dispose();
@ -69,8 +68,7 @@ module.exports = class About {
currentAtomVersion: this.state.currentAtomVersion,
currentElectronVersion: this.state.currentElectronVersion,
currentChromeVersion: this.state.currentChromeVersion,
currentNodeVersion: this.state.currentNodeVersion,
availableVersion: this.state.updateManager.getAvailableVersion()
currentNodeVersion: this.state.currentNodeVersion
});
this.handleStateChanges();
}
@ -86,14 +84,9 @@ module.exports = class About {
currentAtomVersion: this.state.currentAtomVersion,
currentElectronVersion: this.state.currentElectronVersion,
currentChromeVersion: this.state.currentChromeVersion,
currentNodeVersion: this.state.currentNodeVersion,
availableVersion: this.state.updateManager.getAvailableVersion()
currentNodeVersion: this.state.currentNodeVersion
});
}
});
this.state.updateManager.onDidChange(() => {
this.didChange();
});
}
};

View File

@ -1,38 +0,0 @@
const { CompositeDisposable } = require('atom');
const etch = require('etch');
const EtchComponent = require('../etch-component');
const $ = etch.dom;
module.exports = class AboutStatusBar extends EtchComponent {
constructor() {
super();
this.subscriptions = new CompositeDisposable();
this.subscriptions.add(
atom.tooltips.add(this.element, {
title:
'An update will be installed the next time Pulsar is relaunched.<br/><br/>Click the squirrel icon for more information.'
})
);
}
handleClick() {
atom.workspace.open('atom://about');
}
render() {
return $.div(
{
className: 'about-release-notes inline-block',
onclick: this.handleClick.bind(this)
},
$.span({ type: 'button', className: 'icon icon-squirrel' })
);
}
destroy() {
super.destroy();
this.subscriptions.dispose();
}
};

View File

@ -3,7 +3,6 @@ const etch = require('etch');
const { shell } = require('electron');
const AtomLogo = require('./atom-logo');
const EtchComponent = require('../etch-component');
const UpdateView = require('./update-view');
const $ = etch.dom;
@ -31,7 +30,7 @@ module.exports = class AboutView extends EtchComponent {
handleReleaseNotesClick(e) {
e.preventDefault();
shell.openExternal(
this.props.updateManager.getReleaseNotesURLForAvailableVersion() //update-manager.js will need updating when we decide how to do the changelog
this.props.updateManager.getReleaseNotesURLForCurrentVersion()
);
}
@ -52,7 +51,15 @@ module.exports = class AboutView extends EtchComponent {
handleHowToUpdateClick(e) {
e.preventDefault();
shell.openExternal(
'https://pulsar-edit.dev/docs/launch-manual/sections/getting-started/#installing-pulsar'
'https://github.com/pulsar-edit/pulsar/tree/master/packages/pulsar-updater#readme'
);
}
executeUpdateAction(e) {
e.preventDefault();
atom.commands.dispatch(
atom.views.getView(atom.workspace),
'pulsar-updater:check-for-update'
);
}
@ -160,12 +167,31 @@ module.exports = class AboutView extends EtchComponent {
)
),
$(UpdateView, {
updateManager: this.props.updateManager,
availableVersion: this.props.availableVersion,
viewUpdateReleaseNotes: this.handleReleaseNotesClick.bind(this),
viewUpdateInstructions: this.handleHowToUpdateClick.bind(this)
}),
$.div(
{ className: 'about-updates group-start' },
$.div(
{ className: 'about-updates-box' },
$.div(
{ className: 'about-updates-status' },
$.div(
{ className: 'about-updates-item app-unsupported' },
$.span(
{ className: 'about-updates-label is-strong' },
'Updates have been moved to the package ', $.code({}, 'pulsar-updater'), '.',
$.br()
),
$.a(
{
className: 'about-updates-instructions',
onclick: this.handleHowToUpdateClick.bind(this)
},
'How to update'
)
)
),
this.renderUpdateChecker()
)
),
$.div(
{ className: 'about-actions group-item' },
@ -201,6 +227,29 @@ module.exports = class AboutView extends EtchComponent {
);
}
renderUpdateChecker() {
if (atom.packages.isPackageDisabled("pulsar-updater")) {
return $.div(
{ className: 'about-updates-item app-unsupported' },
$.span(
{ className: 'about-updates-label is-strong' },
'Enable `pulsar-updater` to check for updates'
)
);
} else {
return $.button(
{
className: 'btn about-update-action-button',
onclick: this.executeUpdateAction.bind(this),
style: {
display: 'block'
}
},
'Check Now'
);
}
}
serialize() {
return {
deserializer: this.constructor.name,

View File

@ -1,181 +0,0 @@
const etch = require('etch');
const EtchComponent = require('../etch-component');
const UpdateManager = require('../update-manager');
const $ = etch.dom;
module.exports = class UpdateView extends EtchComponent {
constructor(props) {
super(props);
if (
this.props.updateManager.getAutoUpdatesEnabled() &&
this.props.updateManager.getState() === UpdateManager.State.Idle
) {
this.props.updateManager.checkForUpdate();
}
}
handleAutoUpdateCheckbox(e) {
atom.config.set('core.automaticallyUpdate', e.target.checked);
}
shouldUpdateActionButtonBeDisabled() {
let { state } = this.props.updateManager;
return (
state === UpdateManager.State.CheckingForUpdate ||
state === UpdateManager.State.DownloadingUpdate
);
}
executeUpdateAction() {
if (
this.props.updateManager.state ===
UpdateManager.State.UpdateAvailableToInstall
) {
this.props.updateManager.restartAndInstallUpdate();
} else {
this.props.updateManager.checkForUpdate();
}
}
renderUpdateStatus() {
let updateStatus = '';
switch (this.props.updateManager.state) {
case UpdateManager.State.Idle:
updateStatus = $.div(
{
className:
'about-updates-item is-shown about-default-update-message'
},
this.props.updateManager.getAutoUpdatesEnabled()
? 'Pulsar will check for updates automatically'
: 'Automatic updates are disabled please check manually'
);
break;
case UpdateManager.State.CheckingForUpdate:
updateStatus = $.div(
{ className: 'about-updates-item app-checking-for-updates' },
$.span(
{ className: 'about-updates-label icon icon-search' },
'Checking for updates...'
)
);
break;
case UpdateManager.State.DownloadingUpdate:
updateStatus = $.div(
{ className: 'about-updates-item app-downloading-update' },
$.span({ className: 'loading loading-spinner-tiny inline-block' }),
$.span({ className: 'about-updates-label' }, 'Downloading update')
);
break;
case UpdateManager.State.UpdateAvailableToInstall:
updateStatus = $.div(
{ className: 'about-updates-item app-update-available-to-install' },
$.span(
{ className: 'about-updates-label icon icon-squirrel' },
'New update'
),
$.span(
{ className: 'about-updates-version' },
this.props.availableVersion
),
$.a(
{
className: 'about-updates-release-notes',
onclick: this.props.viewUpdateReleaseNotes
},
'Release Notes'
)
);
break;
case UpdateManager.State.UpToDate:
updateStatus = $.div(
{ className: 'about-updates-item app-up-to-date' },
$.span({ className: 'icon icon-check' }),
$.span(
{ className: 'about-updates-label is-strong' },
'Pulsar is up to date!'
)
);
break;
case UpdateManager.State.Unsupported:
updateStatus = $.div(
{ className: 'about-updates-item app-unsupported' },
$.span(
{ className: 'about-updates-label is-strong' },
'Your system does not support automatic updates'
),
$.a(
{
className: 'about-updates-instructions',
onclick: this.props.viewUpdateInstructions
},
'How to update'
)
);
break;
case UpdateManager.State.Error:
updateStatus = $.div(
{ className: 'about-updates-item app-update-error' },
$.span({ className: 'icon icon-x' }),
$.span(
{ className: 'about-updates-label app-error-message is-strong' },
this.props.updateManager.getErrorMessage()
)
);
break;
}
return updateStatus;
}
render() {
return $.div(
{ className: 'about-updates group-start' },
$.div(
{ className: 'about-updates-box' },
$.div({ className: 'about-updates-status' }, this.renderUpdateStatus()),
$.button(
{
className: 'btn about-update-action-button',
disabled: this.shouldUpdateActionButtonBeDisabled(),
onclick: this.executeUpdateAction.bind(this),
style: {
display:
this.props.updateManager.state ===
UpdateManager.State.Unsupported
? 'none'
: 'block'
}
},
this.props.updateManager.state === 'update-available'
? 'Restart and install'
: 'Check now'
)
),
$.div(
{
className: 'about-auto-updates',
style: {
display:
this.props.updateManager.state === UpdateManager.State.Unsupported
? 'none'
: 'block'
}
},
$.label(
{},
$.input({
className: 'input-checkbox',
type: 'checkbox',
checked: this.props.updateManager.getAutoUpdatesEnabled(),
onchange: this.handleAutoUpdateCheckbox.bind(this)
}),
$.span({}, 'Automatically download updates')
)
)
);
}
};

View File

@ -1,69 +1,24 @@
const { CompositeDisposable } = require('atom');
const semver = require('semver');
const UpdateManager = require('./update-manager');
const About = require('./about');
const StatusBarView = require('./components/about-status-bar');
let updateManager;
// The local storage key for the available update version.
const AvailableUpdateVersion = 'about:version-available';
const AboutURI = 'atom://about';
module.exports = {
activate() {
this.subscriptions = new CompositeDisposable();
this.createModel();
let availableVersion = window.localStorage.getItem(AvailableUpdateVersion);
if (
atom.getReleaseChannel() === 'dev' ||
(availableVersion && semver.lte(availableVersion, atom.getVersion()))
) {
this.clearUpdateState();
}
this.subscriptions.add(
updateManager.onDidChange(() => {
if (
updateManager.getState() ===
UpdateManager.State.UpdateAvailableToInstall
) {
window.localStorage.setItem(
AvailableUpdateVersion,
updateManager.getAvailableVersion()
);
this.showStatusBarIfNeeded();
}
})
);
this.subscriptions.add(
atom.commands.add('atom-workspace', 'about:clear-update-state', () => {
this.clearUpdateState();
})
);
},
deactivate() {
this.model.destroy();
if (this.statusBarTile) this.statusBarTile.destroy();
if (updateManager) {
updateManager.dispose();
updateManager = undefined;
}
},
clearUpdateState() {
window.localStorage.removeItem(AvailableUpdateVersion);
},
consumeStatusBar(statusBar) {
this.statusBar = statusBar;
this.showStatusBarIfNeeded();
},
deserializeAboutView(state) {
if (!this.model) {
this.createModel();
@ -83,27 +38,6 @@ module.exports = {
currentNodeVersion: process.version,
updateManager: updateManager
});
},
isUpdateAvailable() {
let availableVersion = window.localStorage.getItem(AvailableUpdateVersion);
return availableVersion && semver.gt(availableVersion, atom.getVersion());
},
showStatusBarIfNeeded() {
if (this.isUpdateAvailable() && this.statusBar) {
let statusBarView = new StatusBarView();
if (this.statusBarTile) {
this.statusBarTile.destroy();
}
this.statusBarTile = this.statusBar.addRightTile({
item: statusBarView,
priority: -100
});
return this.statusBarTile;
}
}
};

View File

@ -1,127 +1,13 @@
const { Emitter, CompositeDisposable } = require('atom');
const Unsupported = 'unsupported';
const Idle = 'idle';
const CheckingForUpdate = 'checking';
const DownloadingUpdate = 'downloading';
const UpdateAvailableToInstall = 'update-available';
const UpToDate = 'no-update-available';
const ErrorState = 'error';
let UpdateManager = class UpdateManager {
constructor() {
this.emitter = new Emitter();
this.currentVersion = atom.getVersion();
this.availableVersion = atom.getVersion();
this.resetState();
this.listenForAtomEvents();
}
listenForAtomEvents() {
this.subscriptions = new CompositeDisposable();
this.subscriptions.add(
atom.autoUpdater.onDidBeginCheckingForUpdate(() => {
this.setState(CheckingForUpdate);
}),
atom.autoUpdater.onDidBeginDownloadingUpdate(() => {
this.setState(DownloadingUpdate);
}),
atom.autoUpdater.onDidCompleteDownloadingUpdate(({ releaseVersion }) => {
this.setAvailableVersion(releaseVersion);
}),
atom.autoUpdater.onUpdateNotAvailable(() => {
this.setState(UpToDate);
}),
atom.autoUpdater.onUpdateError(() => {
this.setState(ErrorState);
}),
atom.config.observe('core.automaticallyUpdate', value => {
this.autoUpdatesEnabled = value;
this.emitDidChange();
})
);
// TODO: When https://github.com/atom/electron/issues/4587 is closed we can add this support.
// atom.autoUpdater.onUpdateAvailable =>
// @find('.about-updates-item').removeClass('is-shown')
// @updateAvailable.addClass('is-shown')
}
dispose() {
this.subscriptions.dispose();
}
onDidChange(callback) {
return this.emitter.on('did-change', callback);
}
emitDidChange() {
this.emitter.emit('did-change');
}
getAutoUpdatesEnabled() {
return (
this.autoUpdatesEnabled && this.state !== UpdateManager.State.Unsupported
);
}
setAutoUpdatesEnabled(enabled) {
return atom.config.set('core.automaticallyUpdate', enabled);
}
getErrorMessage() {
return atom.autoUpdater.getErrorMessage();
}
getState() {
return this.state;
}
setState(state) {
this.state = state;
this.emitDidChange();
}
resetState() {
this.state = atom.autoUpdater.platformSupportsUpdates()
? atom.autoUpdater.getState()
: Unsupported;
this.emitDidChange();
}
getAvailableVersion() {
return this.availableVersion;
}
setAvailableVersion(version) {
this.availableVersion = version;
if (this.availableVersion !== this.currentVersion) {
this.state = UpdateAvailableToInstall;
} else {
this.state = UpToDate;
}
this.emitDidChange();
}
checkForUpdate() {
atom.autoUpdater.checkForUpdate();
}
restartAndInstallUpdate() {
atom.autoUpdater.restartAndInstallUpdate();
}
getReleaseNotesURLForCurrentVersion() {
return this.getReleaseNotesURLForVersion(this.currentVersion);
}
getReleaseNotesURLForAvailableVersion() {
return this.getReleaseNotesURLForVersion(this.availableVersion);
}
getReleaseNotesURLForVersion(appVersion) {
// Dev versions will not have a releases page
if (appVersion.indexOf('dev') > -1) {
@ -138,14 +24,4 @@ let UpdateManager = class UpdateManager {
}
};
UpdateManager.State = {
Unsupported: Unsupported,
Idle: Idle,
CheckingForUpdate: CheckingForUpdate,
DownloadingUpdate: DownloadingUpdate,
UpdateAvailableToInstall: UpdateAvailableToInstall,
UpToDate: UpToDate,
Error: ErrorState
};
module.exports = UpdateManager;

View File

@ -9,8 +9,7 @@
"version": "1.9.1",
"license": "MIT",
"dependencies": {
"etch": "^0.14.1",
"semver": "^7.3.8"
"etch": "^0.14.1"
},
"engines": {
"atom": ">=1.7 <2.0.0"
@ -20,36 +19,6 @@
"version": "0.14.1",
"resolved": "https://registry.npmjs.org/etch/-/etch-0.14.1.tgz",
"integrity": "sha512-+IwqSDBhaQFMUHJu4L/ir0dhDoW5IIihg4Z9lzsIxxne8V0PlSg0gnk2STaKWjGJQnDR4cxpA+a/dORX9kycTA=="
},
"node_modules/lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/semver": {
"version": "7.3.8",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
"integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
"dependencies": {
"lru-cache": "^6.0.0"
},
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
}
}
}

View File

@ -10,15 +10,7 @@
"atom": ">=1.7 <2.0.0"
},
"dependencies": {
"etch": "^0.14.1",
"semver": "^7.3.8"
},
"consumedServices": {
"status-bar": {
"versions": {
"^1.0.0": "consumeStatusBar"
}
}
"etch": "^0.14.1"
},
"deserializers": {
"AboutView": "deserializeAboutView"

View File

@ -2,15 +2,6 @@ describe('About', () => {
let workspaceElement;
beforeEach(async () => {
let storage = {};
spyOn(window.localStorage, 'setItem').andCallFake((key, value) => {
storage[key] = value;
});
spyOn(window.localStorage, 'getItem').andCallFake(key => {
return storage[key];
});
workspaceElement = atom.views.getView(atom.workspace);
await atom.packages.activatePackage('about');
});
@ -100,4 +91,16 @@ describe('About', () => {
expect(atom.clipboard.read()).toBe(process.version);
});
});
describe('check for update appears', () => {
it('when "pulsar-updater" is enabled', async () => {
atom.packages.activatePackage('pulsar-updater');
await atom.workspace.open('atom://about');
jasmine.attachToDOM(workspaceElement);
let aboutElement = workspaceElement.querySelector('.about');
let updateContainer = aboutElement.querySelector('.about-update-action-button');
expect(updateContainer.innerText).toBe('Check Now');
});
});
});

View File

@ -1,184 +0,0 @@
const { conditionPromise } = require('./helpers/async-spec-helpers');
const MockUpdater = require('./mocks/updater');
describe('the status bar', () => {
let atomVersion;
let workspaceElement;
beforeEach(async () => {
let storage = {};
spyOn(window.localStorage, 'setItem').andCallFake((key, value) => {
storage[key] = value;
});
spyOn(window.localStorage, 'getItem').andCallFake(key => {
return storage[key];
});
spyOn(atom, 'getVersion').andCallFake(() => {
return atomVersion;
});
workspaceElement = atom.views.getView(atom.workspace);
await atom.packages.activatePackage('status-bar');
await atom.workspace.open('sample.js');
jasmine.attachToDOM(workspaceElement);
});
afterEach(async () => {
await atom.packages.deactivatePackage('about');
await atom.packages.deactivatePackage('status-bar');
});
describe('on a stable version', function() {
beforeEach(async () => {
atomVersion = '1.2.3';
await atom.packages.activatePackage('about');
});
describe('with no update', () => {
it('does not show the view', () => {
expect(workspaceElement).not.toContain('.about-release-notes');
});
});
describe('with an update', () => {
it('shows the view when the update finishes downloading', () => {
MockUpdater.finishDownloadingUpdate('42.0.0');
expect(workspaceElement).toContain('.about-release-notes');
});
describe('clicking on the status', () => {
it('opens the about page', async () => {
MockUpdater.finishDownloadingUpdate('42.0.0');
workspaceElement.querySelector('.about-release-notes').click();
await conditionPromise(() =>
workspaceElement.querySelector('.about')
);
expect(workspaceElement.querySelector('.about')).toExist();
});
});
it('continues to show the squirrel until Pulsar is updated to the new version', async () => {
MockUpdater.finishDownloadingUpdate('42.0.0');
expect(workspaceElement).toContain('.about-release-notes');
await atom.packages.deactivatePackage('about');
expect(workspaceElement).not.toContain('.about-release-notes');
await atom.packages.activatePackage('about');
await Promise.resolve(); // Service consumption hooks are deferred until the next tick
expect(workspaceElement).toContain('.about-release-notes');
await atom.packages.deactivatePackage('about');
expect(workspaceElement).not.toContain('.about-release-notes');
atomVersion = '42.0.0';
await atom.packages.activatePackage('about');
await Promise.resolve(); // Service consumption hooks are deferred until the next tick
expect(workspaceElement).not.toContain('.about-release-notes');
});
it('does not show the view if Pulsar is updated to a newer version than notified', async () => {
MockUpdater.finishDownloadingUpdate('42.0.0');
await atom.packages.deactivatePackage('about');
atomVersion = '43.0.0';
await atom.packages.activatePackage('about');
await Promise.resolve(); // Service consumption hooks are deferred until the next tick
expect(workspaceElement).not.toContain('.about-release-notes');
});
});
});
describe('on a beta version', function() {
beforeEach(async () => {
atomVersion = '1.2.3-beta4';
await atom.packages.activatePackage('about');
});
describe('with no update', () => {
it('does not show the view', () => {
expect(workspaceElement).not.toContain('.about-release-notes');
});
});
describe('with an update', () => {
it('shows the view when the update finishes downloading', () => {
MockUpdater.finishDownloadingUpdate('42.0.0');
expect(workspaceElement).toContain('.about-release-notes');
});
describe('clicking on the status', () => {
it('opens the about page', async () => {
MockUpdater.finishDownloadingUpdate('42.0.0');
workspaceElement.querySelector('.about-release-notes').click();
await conditionPromise(() =>
workspaceElement.querySelector('.about')
);
expect(workspaceElement.querySelector('.about')).toExist();
});
});
it('continues to show the squirrel until Pulsar is updated to the new version', async () => {
MockUpdater.finishDownloadingUpdate('42.0.0');
expect(workspaceElement).toContain('.about-release-notes');
await atom.packages.deactivatePackage('about');
expect(workspaceElement).not.toContain('.about-release-notes');
await atom.packages.activatePackage('about');
await Promise.resolve(); // Service consumption hooks are deferred until the next tick
expect(workspaceElement).toContain('.about-release-notes');
await atom.packages.deactivatePackage('about');
expect(workspaceElement).not.toContain('.about-release-notes');
atomVersion = '42.0.0';
await atom.packages.activatePackage('about');
await Promise.resolve(); // Service consumption hooks are deferred until the next tick
expect(workspaceElement).not.toContain('.about-release-notes');
});
it('does not show the view if Pulsar is updated to a newer version than notified', async () => {
MockUpdater.finishDownloadingUpdate('42.0.0');
await atom.packages.deactivatePackage('about');
atomVersion = '43.0.0';
await atom.packages.activatePackage('about');
await Promise.resolve(); // Service consumption hooks are deferred until the next tick
expect(workspaceElement).not.toContain('.about-release-notes');
});
});
});
describe('on a development version', function() {
beforeEach(async () => {
atomVersion = '1.2.3-dev-0123abcd';
await atom.packages.activatePackage('about');
});
describe('with no update', () => {
it('does not show the view', () => {
expect(workspaceElement).not.toContain('.about-release-notes');
});
});
describe('with a previously downloaded update', () => {
it('does not show the view', () => {
window.localStorage.setItem('about:version-available', '42.0.0');
expect(workspaceElement).not.toContain('.about-release-notes');
});
});
});
});

View File

@ -1,23 +0,0 @@
module.exports = {
updateError() {
atom.autoUpdater.emitter.emit('update-error');
},
checkForUpdate() {
atom.autoUpdater.emitter.emit('did-begin-checking-for-update');
},
updateNotAvailable() {
atom.autoUpdater.emitter.emit('update-not-available');
},
downloadUpdate() {
atom.autoUpdater.emitter.emit('did-begin-downloading-update');
},
finishDownloadingUpdate(releaseVersion) {
atom.autoUpdater.emitter.emit('did-complete-downloading-update', {
releaseVersion
});
}
};

View File

@ -1,387 +0,0 @@
const { shell } = require('electron');
const main = require('../lib/main');
const AboutView = require('../lib/components/about-view');
const UpdateView = require('../lib/components/update-view');
const MockUpdater = require('./mocks/updater');
describe('UpdateView', () => {
let aboutElement;
let updateManager;
let workspaceElement;
let scheduler;
beforeEach(async () => {
let storage = {};
spyOn(window.localStorage, 'setItem').andCallFake((key, value) => {
storage[key] = value;
});
spyOn(window.localStorage, 'getItem').andCallFake(key => {
return storage[key];
});
workspaceElement = atom.views.getView(atom.workspace);
await atom.packages.activatePackage('about');
spyOn(atom.autoUpdater, 'getState').andReturn('idle');
spyOn(atom.autoUpdater, 'checkForUpdate');
spyOn(atom.autoUpdater, 'platformSupportsUpdates').andReturn(true);
});
describe('when the About page is open', () => {
beforeEach(async () => {
jasmine.attachToDOM(workspaceElement);
await atom.workspace.open('atom://about');
aboutElement = workspaceElement.querySelector('.about');
updateManager = main.model.state.updateManager;
scheduler = AboutView.getScheduler();
});
describe('when the updates are not supported by the platform', () => {
beforeEach(async () => {
atom.autoUpdater.platformSupportsUpdates.andReturn(false);
updateManager.resetState();
await scheduler.getNextUpdatePromise();
});
it('hides the auto update UI and shows the update instructions link', async () => {
expect(
aboutElement.querySelector('.about-update-action-button')
).not.toBeVisible();
expect(
aboutElement.querySelector('.about-auto-updates')
).not.toBeVisible();
});
it('opens the update instructions page when the instructions link is clicked', async () => {
spyOn(shell, 'openExternal');
let link = aboutElement.querySelector(
'.app-unsupported .about-updates-instructions'
);
link.click();
let args = shell.openExternal.mostRecentCall.args;
expect(shell.openExternal).toHaveBeenCalled();
expect(args[0]).toContain('installing-pulsar');
});
});
describe('when updates are supported by the platform', () => {
beforeEach(async () => {
atom.autoUpdater.platformSupportsUpdates.andReturn(true);
updateManager.resetState();
await scheduler.getNextUpdatePromise();
});
it('shows the auto update UI', () => {
expect(aboutElement.querySelector('.about-updates')).toBeVisible();
});
it('shows the correct panels when the app checks for updates and there is no update available', async () => {
expect(
aboutElement.querySelector('.about-default-update-message')
).toBeVisible();
MockUpdater.checkForUpdate();
await scheduler.getNextUpdatePromise();
expect(aboutElement.querySelector('.app-up-to-date')).not.toBeVisible();
expect(
aboutElement.querySelector('.app-checking-for-updates')
).toBeVisible();
MockUpdater.updateNotAvailable();
await scheduler.getNextUpdatePromise();
expect(aboutElement.querySelector('.app-up-to-date')).toBeVisible();
expect(
aboutElement.querySelector('.app-checking-for-updates')
).not.toBeVisible();
});
it('shows the correct panels when the app checks for updates and encounters an error', async () => {
expect(
aboutElement.querySelector('.about-default-update-message')
).toBeVisible();
MockUpdater.checkForUpdate();
await scheduler.getNextUpdatePromise();
expect(aboutElement.querySelector('.app-up-to-date')).not.toBeVisible();
expect(
aboutElement.querySelector('.app-checking-for-updates')
).toBeVisible();
spyOn(atom.autoUpdater, 'getErrorMessage').andReturn(
'an error message'
);
MockUpdater.updateError();
await scheduler.getNextUpdatePromise();
expect(aboutElement.querySelector('.app-update-error')).toBeVisible();
expect(
aboutElement.querySelector('.app-error-message').textContent
).toBe('an error message');
expect(
aboutElement.querySelector('.app-checking-for-updates')
).not.toBeVisible();
expect(
aboutElement.querySelector('.about-update-action-button').disabled
).toBe(false);
expect(
aboutElement.querySelector('.about-update-action-button').textContent
).toBe('Check now');
});
it('shows the correct panels and button states when the app checks for updates and an update is downloaded', async () => {
expect(
aboutElement.querySelector('.about-default-update-message')
).toBeVisible();
expect(
aboutElement.querySelector('.about-update-action-button').disabled
).toBe(false);
expect(
aboutElement.querySelector('.about-update-action-button').textContent
).toBe('Check now');
MockUpdater.checkForUpdate();
await scheduler.getNextUpdatePromise();
expect(aboutElement.querySelector('.app-up-to-date')).not.toBeVisible();
expect(
aboutElement.querySelector('.app-checking-for-updates')
).toBeVisible();
expect(
aboutElement.querySelector('.about-update-action-button').disabled
).toBe(true);
expect(
aboutElement.querySelector('.about-update-action-button').textContent
).toBe('Check now');
MockUpdater.downloadUpdate();
await scheduler.getNextUpdatePromise();
expect(
aboutElement.querySelector('.app-checking-for-updates')
).not.toBeVisible();
expect(
aboutElement.querySelector('.app-downloading-update')
).toBeVisible();
// TODO: at some point it would be nice to be able to cancel an update download, and then this would be a cancel button
expect(
aboutElement.querySelector('.about-update-action-button').disabled
).toBe(true);
expect(
aboutElement.querySelector('.about-update-action-button').textContent
).toBe('Check now');
MockUpdater.finishDownloadingUpdate('42.0.0');
await scheduler.getNextUpdatePromise();
expect(
aboutElement.querySelector('.app-downloading-update')
).not.toBeVisible();
expect(
aboutElement.querySelector('.app-update-available-to-install')
).toBeVisible();
expect(
aboutElement.querySelector(
'.app-update-available-to-install .about-updates-version'
).textContent
).toBe('42.0.0');
expect(
aboutElement.querySelector('.about-update-action-button').disabled
).toBe(false);
expect(
aboutElement.querySelector('.about-update-action-button').textContent
).toBe('Restart and install');
});
it('opens the release notes for the downloaded release when the release notes link are clicked', async () => {
MockUpdater.finishDownloadingUpdate('1.2.3');
await scheduler.getNextUpdatePromise();
spyOn(shell, 'openExternal');
let link = aboutElement.querySelector(
'.app-update-available-to-install .about-updates-release-notes'
);
link.click();
let args = shell.openExternal.mostRecentCall.args;
expect(shell.openExternal).toHaveBeenCalled();
expect(args[0]).toContain('/v1.2.3');
});
it('executes checkForUpdate() when the check for update button is clicked', () => {
let button = aboutElement.querySelector('.about-update-action-button');
button.click();
expect(atom.autoUpdater.checkForUpdate).toHaveBeenCalled();
});
it('executes restartAndInstallUpdate() when the restart and install button is clicked', async () => {
spyOn(atom.autoUpdater, 'restartAndInstallUpdate');
MockUpdater.finishDownloadingUpdate('42.0.0');
await scheduler.getNextUpdatePromise();
let button = aboutElement.querySelector('.about-update-action-button');
button.click();
expect(atom.autoUpdater.restartAndInstallUpdate).toHaveBeenCalled();
});
it("starts in the same state as atom's AutoUpdateManager", async () => {
atom.autoUpdater.getState.andReturn('downloading');
updateManager.resetState();
await scheduler.getNextUpdatePromise();
expect(
aboutElement.querySelector('.app-checking-for-updates')
).not.toBeVisible();
expect(
aboutElement.querySelector('.app-downloading-update')
).toBeVisible();
expect(
aboutElement.querySelector('.about-update-action-button').disabled
).toBe(true);
expect(
aboutElement.querySelector('.about-update-action-button').textContent
).toBe('Check now');
});
// describe('when core.automaticallyUpdate is toggled', () => {
// beforeEach(async () => {
// expect(atom.config.get('core.automaticallyUpdate')).toBe(true);
// atom.autoUpdater.checkForUpdate.reset();
// });
//
// it('shows the auto update UI', async () => {
// expect(
// aboutElement.querySelector('.about-auto-updates input').checked
// ).toBe(true);
// expect(
// aboutElement.querySelector('.about-default-update-message')
// ).toBeVisible();
// expect(
// aboutElement.querySelector('.about-default-update-message')
// .textContent
// ).toBe('Pulsar will check for updates automatically');
//
// atom.config.set('core.automaticallyUpdate', false);
// await scheduler.getNextUpdatePromise();
//
// expect(
// aboutElement.querySelector('.about-auto-updates input').checked
// ).toBe(false);
// expect(
// aboutElement.querySelector('.about-default-update-message')
// ).toBeVisible();
// expect(
// aboutElement.querySelector('.about-default-update-message')
// .textContent
// ).toBe('Automatic updates are disabled please check manually');
// });
//
// it('updates config and the UI when the checkbox is used to toggle', async () => {
// expect(
// aboutElement.querySelector('.about-auto-updates input').checked
// ).toBe(true);
//
// aboutElement.querySelector('.about-auto-updates input').click();
// await scheduler.getNextUpdatePromise();
//
// expect(atom.config.get('core.automaticallyUpdate')).toBe(false);
// expect(
// aboutElement.querySelector('.about-auto-updates input').checked
// ).toBe(false);
// expect(
// aboutElement.querySelector('.about-default-update-message')
// ).toBeVisible();
// expect(
// aboutElement.querySelector('.about-default-update-message')
// .textContent
// ).toBe('Automatic updates are disabled please check manually');
//
// aboutElement.querySelector('.about-auto-updates input').click();
// await scheduler.getNextUpdatePromise();
//
// expect(atom.config.get('core.automaticallyUpdate')).toBe(true);
// expect(
// aboutElement.querySelector('.about-auto-updates input').checked
// ).toBe(true);
// expect(
// aboutElement.querySelector('.about-default-update-message')
// ).toBeVisible();
// expect(
// aboutElement.querySelector('.about-default-update-message')
// .textContent
// ).toBe('Pulsar will check for updates automatically');
// });
//
// describe('checking for updates', function() {
// afterEach(() => {
// this.updateView = null;
// });
//
// it('checks for update when the about page is shown', () => {
// expect(atom.autoUpdater.checkForUpdate).not.toHaveBeenCalled();
//
// this.updateView = new UpdateView({
// updateManager: updateManager,
// availableVersion: '9999.0.0',
// viewUpdateReleaseNotes: () => {}
// });
//
// expect(atom.autoUpdater.checkForUpdate).toHaveBeenCalled();
// });
//
// it('does not check for update when the about page is shown and the update manager is not in the idle state', () => {
// atom.autoUpdater.getState.andReturn('downloading');
// updateManager.resetState();
// expect(atom.autoUpdater.checkForUpdate).not.toHaveBeenCalled();
//
// this.updateView = new UpdateView({
// updateManager: updateManager,
// availableVersion: '9999.0.0',
// viewUpdateReleaseNotes: () => {}
// });
//
// expect(atom.autoUpdater.checkForUpdate).not.toHaveBeenCalled();
// });
//
// it('does not check for update when the about page is shown and auto updates are turned off', () => {
// atom.config.set('core.automaticallyUpdate', false);
// expect(atom.autoUpdater.checkForUpdate).not.toHaveBeenCalled();
//
// this.updateView = new UpdateView({
// updateManager: updateManager,
// availableVersion: '9999.0.0',
// viewUpdateReleaseNotes: () => {}
// });
//
// expect(atom.autoUpdater.checkForUpdate).not.toHaveBeenCalled();
// });
// });
// });
});
});
describe('when the About page is not open and an update is downloaded', () => {
it('should display the new version when it is opened', async () => {
MockUpdater.finishDownloadingUpdate('42.0.0');
jasmine.attachToDOM(workspaceElement);
await atom.workspace.open('atom://about');
aboutElement = workspaceElement.querySelector('.about');
updateManager = main.model.state.updateManager;
scheduler = AboutView.getScheduler();
expect(
aboutElement.querySelector('.app-update-available-to-install')
).toBeVisible();
expect(
aboutElement.querySelector(
'.app-update-available-to-install .about-updates-version'
).textContent
).toBe('42.0.0');
expect(
aboutElement.querySelector('.about-update-action-button').disabled
).toBe(false);
expect(
aboutElement.querySelector('.about-update-action-button').textContent
).toBe('Restart and install');
});
});
});

View File

@ -94,7 +94,7 @@
.about-updates {
width: 100%;
max-width: 500px;
//max-width: 510px;
}
.about-updates-box {
@ -102,14 +102,14 @@
align-items: center;
padding: @component-padding;
border: 1px solid @base-border-color;
border-radius: @component-border-radius * 2;
border-radius: (@component-border-radius * 2);
background-color: @background-color-highlight;
}
.about-updates-status {
flex: 1;
margin-left: .5em;
text-align: left;
text-align: center;
}
.about-updates-item,

View File

@ -242,12 +242,17 @@
color: #555;
}
.syntax--punctuation.syntax--definition.syntax--list-item {
color: #C6C5FE;
}
.syntax--variable.syntax--list,
.syntax--support.syntax--quote {
color: #555;
}
.syntax--link .syntax--entity {
.syntax--link .syntax--entity,
.syntax--meta.syntax--link.syntax--text {
color: #ddd;
}

View File

@ -202,11 +202,6 @@
color: #eee;
}
// - item
&.syntax--list {
color: #555;
}
// > quote
&.syntax--quote {
color: #555;
@ -226,6 +221,14 @@
&.syntax--alt {
color: #ddd;
}
&.syntax--bold {
font-style: bold;
}
&.syntax--italic {
font-style: italic;
}
}
// /* comment */

View File

@ -4,7 +4,7 @@ atom-text-editor[mini] {
border: 1px solid @input-border-color;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
border-radius: @component-border-radius;
padding-left: @component-padding/2;
padding-left: (@component-padding/2);
.cursor { border-color: #fff; }
.selection .region { background-color: lighten(@input-background-color, 10%); }

View File

@ -91,16 +91,16 @@
.select-list.popover-list {
background-color: @overlay-background-color;
box-shadow: 0 0 10px @base-border-color;
padding: @component-padding/2;
padding: (@component-padding/2);
border-radius: @component-border-radius;
border: 1px solid @overlay-border-color;
atom-text-editor {
margin-bottom: @component-padding/2;
margin-bottom: (@component-padding/2);
}
.list-group li {
padding-left: @component-padding/2;
padding-left: (@component-padding/2);
}
}

View File

@ -19,7 +19,7 @@ atom-panel.modal, .overlay {
padding: @component-padding;
border-bottom: 1px solid @overlay-background-color;
&.two-lines { padding: @component-padding/2 @component-padding; }
&.two-lines { padding: (@component-padding/2) @component-padding; }
.status.icon {
float: right;

View File

@ -8,7 +8,7 @@
@tab-max-width: 160px;
.tab-bar {
height: @tab-height + @tab-top-padding + @tab-bottom-border-height;
height: (@tab-height + @tab-top-padding + @tab-bottom-border-height);
background: @tab-bar-background-color;
box-shadow: inset 0 -8px 8px -4px rgba(0,0,0, .15);
padding: 0 10px 0 25px;
@ -75,7 +75,7 @@
&.modified:not(:hover) .close-icon {
right: 0;
top: @tab-height/2 - @modified-icon-width/2 + 1px;
top: (@tab-height/2 - @modified-icon-width/2 + 1px);
width: @modified-icon-width;
height: @modified-icon-width;
}
@ -99,13 +99,13 @@
box-shadow: inset -1px 1px 0 @tab-border-color, 4px -4px 4px rgba(0,0,0,.1);
.close-icon {
line-height: @tab-height - 1px;
line-height: (@tab-height - 1px);
color: @text-color;
}
&, &:before, &:after {
background-image: -webkit-linear-gradient(top, lighten(@tab-background-color-active, 7%), @tab-background-color-active);
height: @tab-height + 1px;
height: (@tab-height + 1px);
}
&:before {
@ -130,13 +130,13 @@
}
.placeholder {
height: @tab-height + @tab-top-padding + @tab-bottom-border-height;
height: (@tab-height + @tab-top-padding + @tab-bottom-border-height);
pointer-events: none;
&:before {
margin-left: -9px; // center between tabs
}
&:after {
top: @tab-height + @tab-top-padding + @tab-bottom-border-height - 2px;
top: (@tab-height + @tab-top-padding + @tab-bottom-border-height - 2px);
margin-left: -10px; // center between tabs
}
}

View File

@ -8,8 +8,8 @@
border-radius: @component-border-radius;
margin-left: @component-icon-padding;
font-family: Helvetica, Arial, sans-serif;
font-size: @font-size - 1px;
padding: @component-padding / 2;
font-size: (@font-size - 1px);
padding: (@component-padding / 2);
}
.badge {

View File

@ -170,9 +170,14 @@
color: #888;
}
.syntax--variable.syntax--list {
.syntax--variable.syntax--list,
.syntax--punctuation.syntax--definition.syntax--list-item {
color: #888;
}
.syntax--meta.syntax--link.syntax--text {
color: #555;
}
}
.syntax--markdown {

View File

@ -190,6 +190,14 @@
&.syntax--link {
color: #888;
}
&.syntax--bold {
font-style: bold;
}
&.syntax--italic {
font-style: italic;
}
}
// /* comment */

View File

@ -5,7 +5,7 @@ atom-text-editor[mini] {
box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
border-radius: @component-border-radius;
padding-left: @component-padding/2;
padding-left: (@component-padding/2);
.cursor { border-color: #000; }
.selection .region { background-color: rgba(0, 0, 0, .2); }

View File

@ -90,16 +90,16 @@
.select-list.popover-list {
background-color: @overlay-background-color;
box-shadow: 0 0 10px @base-border-color;
padding: @component-padding/2;
padding: (@component-padding/2);
border-radius: @component-border-radius;
border: 1px solid @overlay-border-color;
atom-text-editor {
margin-bottom: @component-padding/2;
margin-bottom: (@component-padding/2);
}
.list-group li {
padding-left: @component-padding/2;
padding-left: (@component-padding/2);
}
}

View File

@ -22,7 +22,7 @@ atom-panel.modal, .overlay {
border-right: 1px solid @inset-panel-border-color;
&:last-child { border-bottom: 1px solid @inset-panel-border-color; }
&.two-lines { padding: @component-padding/2 @component-padding; }
&.two-lines { padding: (@component-padding/2) @component-padding; }
&.selected {
color: @text-color;
background-color: @background-color-highlight;

View File

@ -40,7 +40,7 @@ atom-panel, .tool-panel {
.panel-heading {
border-bottom: none;
padding: @component-padding - 2px @component-padding;
padding: (@component-padding - 2px) @component-padding;
background-color: transparent;
background-image: -webkit-linear-gradient(@panel-heading-background-color, darken(@panel-heading-background-color, 10%));

View File

@ -8,7 +8,7 @@
@tab-max-width: 160px;
.tab-bar {
height: @tab-height + @tab-top-padding + @tab-bottom-border-height;
height: (@tab-height + @tab-top-padding + @tab-bottom-border-height);
background-image: -webkit-linear-gradient(top, @tab-bar-background-color, lighten(@tab-bar-background-color, 9%));
box-shadow: inset 0 -8px 8px -4px rgba(0,0,0, .15);
padding: 0 10px 0 25px;
@ -77,7 +77,7 @@
&.modified:not(:hover) .close-icon {
right: 0;
top: @tab-height/2 - @modified-icon-width/2 + 1px;
top: (@tab-height/2 - @modified-icon-width/2 + 1px);
width: @modified-icon-width;
height: @modified-icon-width;
}
@ -89,8 +89,8 @@
.title {
position: relative;
z-index: 1;
margin-top: -@tab-top-padding - 1px;
padding-top: @tab-top-padding + 1px;
margin-top: (-@tab-top-padding - 1px);
padding-top: (@tab-top-padding + 1px);
padding-right: 10px;
}
}
@ -100,13 +100,13 @@
color: @text-color-highlight;
.close-icon {
line-height: @tab-height - 1px;
line-height: (@tab-height - 1px);
color: @text-color;
}
&, &:before, &:after {
background: @tab-background-color-active;
height: @tab-height + 1px;
height: (@tab-height + 1px);
box-shadow: none;
}
}
@ -124,13 +124,13 @@
}
.placeholder {
height: @tab-height + @tab-top-padding + @tab-bottom-border-height;
height: (@tab-height + @tab-top-padding + @tab-bottom-border-height);
pointer-events: none;
&:before {
margin-left: -9px; // center between tabs
}
&:after {
top: @tab-height + @tab-top-padding + @tab-bottom-border-height - 2px;
top: (@tab-height + @tab-top-padding + @tab-bottom-border-height - 2px);
margin-left: -10px; // center between tabs
}
}

View File

@ -7,8 +7,8 @@
border-radius: @component-border-radius;
margin-left: @component-icon-padding;
font-family: Helvetica, Arial, sans-serif;
font-size: @font-size - 1px;
padding: @component-padding / 2;
font-size: (@font-size - 1px);
padding: (@component-padding / 2);
}
.badge {

View File

@ -0,0 +1,2 @@
node_modules
npm-debug.log

View File

@ -0,0 +1,5 @@
# Atom API Autocomplete package
Provides autocompletions for properties and methods available from the `atom.` global.
![autocomplete-atom-api](https://cloud.githubusercontent.com/assets/69169/7211322/9c402ea2-e50e-11e4-9d74-56ab91aa101d.gif)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,7 @@
const provider = require('./provider');
module.exports = {
activate() { return provider.load(); },
getProvider() { return provider; }
};

View File

@ -0,0 +1,125 @@
const fs = require('fs');
const path = require('path');
const CLASSES = require('../completions.json');
const propertyPrefixPattern = /(?:^|\[|\(|,|=|:|\s)\s*(atom\.(?:[a-zA-Z]+\.?){0,2})$/;
module.exports = {
selector: '.source.coffee, .source.js',
filterSuggestions: true,
getSuggestions({bufferPosition, editor}) {
if (!this.isEditingAnAtomPackageFile(editor)) { return; }
const line = editor.getTextInRange([[bufferPosition.row, 0], bufferPosition]);
return this.getCompletions(line);
},
load() {
this.loadCompletions();
atom.project.onDidChangePaths(() => this.scanProjectDirectories());
return this.scanProjectDirectories();
},
scanProjectDirectories() {
this.packageDirectories = [];
atom.project.getDirectories().forEach(directory => {
if (directory == null) { return; }
this.readMetadata(directory, (error, metadata) => {
if (this.isAtomPackage(metadata) || this.isAtomCore(metadata)) {
this.packageDirectories.push(directory);
}
});
});
},
readMetadata(directory, callback) {
fs.readFile(path.join(directory.getPath(), 'package.json'), function(error, contents) {
let metadata;
if (error == null) {
try {
metadata = JSON.parse(contents);
} catch (parseError) {
error = parseError;
}
}
return callback(error, metadata);
});
},
isAtomPackage(metadata) {
return metadata?.engines?.atom?.length > 0;
},
isAtomCore(metadata) {
return metadata?.name === 'atom';
},
isEditingAnAtomPackageFile(editor) {
const editorPath = editor.getPath();
if (editorPath != null) {
const parsedPath = path.parse(editorPath);
const basename = path.basename(parsedPath.dir);
if ((basename === '.atom') || (basename === '.pulsar')) {
if ((parsedPath.base === 'init.coffee') || (parsedPath.base === 'init.js')) {
return true;
}
}
}
for (let directory of (this.packageDirectories != null ? this.packageDirectories : [])) {
if (directory.contains(editorPath)) { return true; }
}
return false;
},
loadCompletions() {
if (this.completions == null) { this.completions = {}; }
return this.loadProperty('atom', 'AtomEnvironment', CLASSES);
},
getCompletions(line) {
const completions = [];
const match = propertyPrefixPattern.exec(line)?.[1];
if (!match) { return completions; }
let segments = match.split('.');
const prefix = segments.pop() ?? '';
segments = segments.filter(segment => segment);
const property = segments[segments.length - 1];
const propertyCompletions = this.completions[property]?.completions != null ? this.completions[property]?.completions : [];
for (let completion of propertyCompletions) {
if (!prefix || firstCharsEqual(completion.name, prefix)) {
completions.push(clone(completion));
}
}
return completions;
},
getPropertyClass(name) {
return atom[name]?.constructor?.name;
},
loadProperty(propertyName, className, classes, parent) {
const classCompletions = classes[className];
if (classCompletions == null) { return; }
this.completions[propertyName] = {completions: []};
for (let completion of classCompletions) {
this.completions[propertyName].completions.push(completion);
if (completion.type === 'property') {
const propertyClass = this.getPropertyClass(completion.name);
this.loadProperty(completion.name, propertyClass, classes);
}
}
}
};
const clone = function(obj) {
const newObj = {};
for (let k in obj) { const v = obj[k]; newObj[k] = v; }
return newObj;
};
const firstCharsEqual = (str1, str2) => str1[0].toLowerCase() === str2[0].toLowerCase();

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,22 @@
{
"name": "autocomplete-atom-api",
"version": "0.10.7",
"description": "Pulsar API autocompletions",
"main": "./lib/main",
"license": "MIT",
"repository": "https://github.com/pulsar-edit/pulsar",
"engines": {
"atom": ">=0.174.0 <2.0.0"
},
"providedServices": {
"autocomplete.provider": {
"versions": {
"2.0.0": "getProvider"
}
}
},
"devDependencies": {
"request": "^2.53.0",
"temp": "^0.8.1"
}
}

View File

@ -0,0 +1,6 @@
{
"name": "a-package",
"engines": {
"atom": "*"
}
}

View File

@ -0,0 +1,119 @@
const temp = require('temp');
describe("Atom API autocompletions", () => {
let [editor, provider] = [];
const getCompletions = function() {
const cursor = editor.getLastCursor();
const start = cursor.getBeginningOfCurrentWordBufferPosition();
const end = cursor.getBufferPosition();
const prefix = editor.getTextInRange([start, end]);
const request = {
editor,
bufferPosition: end,
scopeDescriptor: cursor.getScopeDescriptor(),
prefix
};
return provider.getSuggestions(request);
};
beforeEach(() => {
waitsForPromise(() => atom.packages.activatePackage('autocomplete-atom-api'));
runs(() => provider = atom.packages.getActivePackage('autocomplete-atom-api').mainModule.getProvider());
waitsFor(() => Object.keys(provider.completions).length > 0);
waitsFor(() => provider.packageDirectories?.length > 0);
waitsForPromise(() => atom.workspace.open('test.js'));
runs(() => editor = atom.workspace.getActiveTextEditor());
});
it("only includes completions in files that are in an Atom package or Atom core", () => {
const emptyProjectPath = temp.mkdirSync('atom-project-');
atom.project.setPaths([emptyProjectPath]);
waitsForPromise(() => atom.workspace.open('empty.js'));
runs(() => {
expect(provider.packageDirectories.length).toBe(0);
editor = atom.workspace.getActiveTextEditor();
editor.setText('atom.');
editor.setCursorBufferPosition([0, Infinity]);
expect(getCompletions()).toBeUndefined();
});
});
it("only includes completions in .atom/init", () => {
const emptyProjectPath = temp.mkdirSync('some-guy');
atom.project.setPaths([emptyProjectPath]);
waitsForPromise(() => atom.workspace.open('.atom/init.coffee'));
runs(() => {
expect(provider.packageDirectories.length).toBe(0);
editor = atom.workspace.getActiveTextEditor();
editor.setText('atom.');
editor.setCursorBufferPosition([0, Infinity]);
expect(getCompletions()).not.toBeUndefined();
});
});
it("does not fail when no editor path", () => {
const emptyProjectPath = temp.mkdirSync('some-guy');
atom.project.setPaths([emptyProjectPath]);
waitsForPromise(() => atom.workspace.open());
runs(() => {
expect(provider.packageDirectories.length).toBe(0);
editor = atom.workspace.getActiveTextEditor();
editor.setText('atom.');
editor.setCursorBufferPosition([0, Infinity]);
expect(getCompletions()).toBeUndefined();
});
});
it("includes properties and functions on the atom global", () => {
editor.setText('atom.');
editor.setCursorBufferPosition([0, Infinity]);
expect(getCompletions().length).toBe(53);
expect(getCompletions()[0].text).toBe('clipboard');
editor.setText('var c = atom.');
editor.setCursorBufferPosition([0, Infinity]);
expect(getCompletions().length).toBe(53);
expect(getCompletions()[0].text).toBe('clipboard');
editor.setText('atom.c');
editor.setCursorBufferPosition([0, Infinity]);
expect(getCompletions().length).toBe(7);
expect(getCompletions()[0].text).toBe('clipboard');
expect(getCompletions()[0].type).toBe('property');
expect(getCompletions()[0].leftLabel).toBe('Clipboard');
expect(getCompletions()[1].text).toBe('commands');
expect(getCompletions()[2].text).toBe('config');
expect(getCompletions()[6].snippet).toBe('confirm(${1:options})');
expect(getCompletions()[6].type).toBe('method');
expect(getCompletions()[6].leftLabel).toBe('Number');
expect(getCompletions()[6].descriptionMoreURL).toBe('https://atom.io/docs/api/latest/AtomEnvironment#instance-confirm');
});
it("includes methods on atom global properties", () => {
editor.setText('atom.clipboard.');
editor.setCursorBufferPosition([0, Infinity]);
expect(getCompletions().length).toBe(3);
expect(getCompletions()[0].text).toBe('read()');
expect(getCompletions()[1].text).toBe('readWithMetadata()');
expect(getCompletions()[2].snippet).toBe('write(${1:text}, ${2:metadata})');
editor.setText('atom.clipboard.rea');
editor.setCursorBufferPosition([0, Infinity]);
expect(getCompletions().length).toBe(2);
expect(getCompletions()[0].text).toBe('read()');
expect(getCompletions()[1].text).toBe('readWithMetadata()');
});
});

View File

@ -0,0 +1,84 @@
# Run this to update the static list of properties stored in the
# completions.json file at the root of this repository.
fs = require 'fs'
request = require 'request'
requestOptions =
url: 'https://api.github.com/repos/atom/atom/releases/latest'
json: true
headers:
'User-Agent': 'agent'
request requestOptions, (error, response, release) ->
if error?
console.error(error.message)
return process.exit(1)
[apiAsset] = release.assets.filter ({name}) -> name is 'atom-api.json'
unless apiAsset?.browser_download_url
console.error('No atom-api.json asset found in latest release')
return process.exit(1)
apiRequestOptions =
json: true
url: apiAsset.browser_download_url
request apiRequestOptions, (error, response, atomApi) ->
if error?
console.error(error.message)
return process.exit(1)
{classes} = atomApi
publicClasses = {}
for name, {instanceProperties, instanceMethods} of classes
pluckPropertyAttributes = convertPropertyToSuggestion.bind(this, name)
pluckMethodAttributes = convertMethodToSuggestion.bind(this, name)
properties = instanceProperties.filter(isVisible).map(pluckPropertyAttributes).sort(textComparator)
methods = instanceMethods.filter(isVisible).map(pluckMethodAttributes).sort(textComparator)
if properties?.length > 0 or methods.length > 0
publicClasses[name] = properties.concat(methods)
fs.writeFileSync('completions.json', JSON.stringify(publicClasses, null, ' '))
isVisible = ({visibility}) ->
visibility in ['Essential', 'Extended', 'Public']
convertMethodToSuggestion = (className, method) ->
{name, summary, returnValues} = method
args = method['arguments']
snippets = []
if args?.length
for arg, i in args
snippets.push("${#{i+1}:#{arg.name}}")
text = null
snippet = null
if snippets.length
snippet = "#{name}(#{snippets.join(', ')})"
else
text = "#{name}()"
returnValue = returnValues?[0]?.type
description = summary
descriptionMoreURL = getDocLink(className, name)
{name, text, snippet, description, descriptionMoreURL, leftLabel: returnValue, type: 'method'}
convertPropertyToSuggestion = (className, {name, summary}) ->
text = name
returnValue = summary?.match(/\{(\w+)\}/)?[1]
description = summary
descriptionMoreURL = getDocLink(className, name)
{name, text, description, descriptionMoreURL, leftLabel: returnValue, type: 'property'}
getDocLink = (className, instanceName) ->
"https://atom.io/docs/api/latest/#{className}#instance-#{instanceName}"
textComparator = (a, b) ->
return 1 if a.name > b.name
return -1 if a.name < b.name
0

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +0,0 @@
provider = require './provider'
module.exports =
activate: ->
getProvider: -> provider

View File

@ -0,0 +1,7 @@
const provider = require('./provider.js');
module.exports = {
activate() {},
getProvider() { return provider; }
};

View File

@ -1,318 +0,0 @@
COMPLETIONS = require('../completions.json')
firstInlinePropertyNameWithColonPattern = /{\s*(\S+)\s*:/ # .example { display: }
inlinePropertyNameWithColonPattern = /(?:;.+?)*;\s*(\S+)\s*:/ # .example { display: block; float: left; color: } (match the last one)
propertyNameWithColonPattern = /^\s*(\S+)\s*:/ # display:
propertyNamePrefixPattern = /[a-zA-Z]+[-a-zA-Z]*$/
pseudoSelectorPrefixPattern = /:(:)?([a-z]+[a-z-]*)?$/
tagSelectorPrefixPattern = /(^|\s|,)([a-z]+)?$/
importantPrefixPattern = /(![a-z]+)$/
cssDocsURL = "https://developer.mozilla.org/en-US/docs/Web/CSS"
module.exports =
selector: '.source.css, .source.sass, .source.css.postcss'
disableForSelector: '.source.css .comment, .source.css .string, .source.sass .comment, .source.sass .string, .source.css.postcss .comment, source.css.postcss .string'
properties: COMPLETIONS.properties
pseudoSelectors: COMPLETIONS.pseudoSelectors
tags: COMPLETIONS.tags
# Tell autocomplete to fuzzy filter the results of getSuggestions(). We are
# still filtering by the first character of the prefix in this provider for
# efficiency.
filterSuggestions: true
getSuggestions: (request) ->
completions = null
scopes = request.scopeDescriptor.getScopesArray()
isSass = hasScope(scopes, 'source.sass', true)
if @isCompletingValue(request)
completions = @getPropertyValueCompletions(request)
else if @isCompletingPseudoSelector(request)
completions = @getPseudoSelectorCompletions(request)
else
if isSass and @isCompletingNameOrTag(request)
completions = @getPropertyNameCompletions(request)
.concat(@getTagCompletions(request))
else if not isSass and @isCompletingName(request)
completions = @getPropertyNameCompletions(request)
if not isSass and @isCompletingTagSelector(request)
tagCompletions = @getTagCompletions(request)
if tagCompletions?.length
completions ?= []
completions = completions.concat(tagCompletions)
completions
onDidInsertSuggestion: ({editor, suggestion}) ->
setTimeout(@triggerAutocomplete.bind(this, editor), 1) if suggestion.type is 'property'
triggerAutocomplete: (editor) ->
atom.commands.dispatch(atom.views.getView(editor), 'autocomplete-plus:activate', {activatedManually: false})
isCompletingValue: ({scopeDescriptor, bufferPosition, prefix, editor}) ->
scopes = scopeDescriptor.getScopesArray()
beforePrefixBufferPosition = [bufferPosition.row, Math.max(0, bufferPosition.column - prefix.length - 1)]
beforePrefixScopes = editor.scopeDescriptorForBufferPosition(beforePrefixBufferPosition)
beforePrefixScopesArray = beforePrefixScopes.getScopesArray()
previousBufferPosition = [bufferPosition.row, Math.max(0, bufferPosition.column - 1)]
previousScopes = editor.scopeDescriptorForBufferPosition(previousBufferPosition)
previousScopesArray = previousScopes.getScopesArray()
(hasScope(scopes, 'meta.property-list.css') and prefix.trim() is ":") or
(hasScope(previousScopesArray, 'meta.property-value.css')) or
(hasScope(scopes, 'meta.property-list.scss') and prefix.trim() is ":") or
(hasScope(previousScopesArray, 'meta.property-value.scss')) or
(hasScope(scopes, 'meta.property-list.postcss') and prefix.trim() is ":") or
(hasScope(previousScopesArray, 'meta.property-value.postcss')) or
(hasScope(scopes, 'source.sass', true) and (hasScope(scopes, 'meta.property-value.sass') or
(not hasScope(beforePrefixScopesArray, 'entity.name.tag.css') and prefix.trim() is ":")
))
isCompletingName: ({scopeDescriptor, bufferPosition, prefix, editor}) ->
scopes = scopeDescriptor.getScopesArray()
isAtTerminator = prefix.endsWith(';')
isAtParentSymbol = prefix.endsWith('&')
isVariable = hasScope(scopes, 'variable.css') or
hasScope(scopes, 'variable.scss') or
hasScope(scopes, 'variable.var.postcss')
isInPropertyList = not isAtTerminator and
(hasScope(scopes, 'meta.property-list.css') or
hasScope(scopes, 'meta.property-list.scss') or
hasScope(scopes, 'meta.property-list.postcss'))
return false unless isInPropertyList
return false if isAtParentSymbol or isVariable
previousBufferPosition = [bufferPosition.row, Math.max(0, bufferPosition.column - prefix.length - 1)]
previousScopes = editor.scopeDescriptorForBufferPosition(previousBufferPosition)
previousScopesArray = previousScopes.getScopesArray()
return false if hasScope(previousScopesArray, 'entity.other.attribute-name.class.css') or
hasScope(previousScopesArray, 'entity.other.attribute-name.id.css') or
hasScope(previousScopesArray, 'entity.other.attribute-name.id') or
hasScope(previousScopesArray, 'entity.other.attribute-name.parent-selector.css') or
hasScope(previousScopesArray, 'entity.name.tag.reference.scss') or
hasScope(previousScopesArray, 'entity.name.tag.scss') or
hasScope(previousScopesArray, 'entity.name.tag.reference.postcss') or
hasScope(previousScopesArray, 'entity.name.tag.postcss')
isAtBeginScopePunctuation = hasScope(scopes, 'punctuation.section.property-list.begin.bracket.curly.css') or
hasScope(scopes, 'punctuation.section.property-list.begin.bracket.curly.scss') or
hasScope(scopes, 'punctuation.section.property-list.begin.postcss')
isAtEndScopePunctuation = hasScope(scopes, 'punctuation.section.property-list.end.bracket.curly.css') or
hasScope(scopes, 'punctuation.section.property-list.end.bracket.curly.scss') or
hasScope(scopes, 'punctuation.section.property-list.end.postcss')
if isAtBeginScopePunctuation
# * Disallow here: `canvas,|{}`
# * Allow here: `canvas,{| }`
prefix.endsWith('{')
else if isAtEndScopePunctuation
# * Disallow here: `canvas,{}|`
# * Allow here: `canvas,{ |}`
not prefix.endsWith('}')
else
true
isCompletingNameOrTag: ({scopeDescriptor, bufferPosition, editor}) ->
scopes = scopeDescriptor.getScopesArray()
prefix = @getPropertyNamePrefix(bufferPosition, editor)
return @isPropertyNamePrefix(prefix) and
hasScope(scopes, 'meta.selector.css') and
not hasScope(scopes, 'entity.other.attribute-name.id.css.sass') and
not hasScope(scopes, 'entity.other.attribute-name.class.sass')
isCompletingTagSelector: ({editor, scopeDescriptor, bufferPosition}) ->
scopes = scopeDescriptor.getScopesArray()
tagSelectorPrefix = @getTagSelectorPrefix(editor, bufferPosition)
return false unless tagSelectorPrefix?.length
previousBufferPosition = [bufferPosition.row, Math.max(0, bufferPosition.column - 1)]
previousScopes = editor.scopeDescriptorForBufferPosition(previousBufferPosition)
previousScopesArray = previousScopes.getScopesArray()
if hasScope(scopes, 'meta.selector.css') or hasScope(previousScopesArray, 'meta.selector.css')
true
else if hasScope(scopes, 'source.css.scss', true) or hasScope(scopes, 'source.css.less', true) or hasScope(scopes, 'source.css.postcss', true)
not hasScope(previousScopesArray, 'meta.property-value.scss') and
not hasScope(previousScopesArray, 'meta.property-value.css') and
not hasScope(previousScopesArray, 'meta.property-value.postcss') and
not hasScope(previousScopesArray, 'support.type.property-value.css')
else
false
isCompletingPseudoSelector: ({editor, scopeDescriptor, bufferPosition}) ->
scopes = scopeDescriptor.getScopesArray()
previousBufferPosition = [bufferPosition.row, Math.max(0, bufferPosition.column - 1)]
previousScopes = editor.scopeDescriptorForBufferPosition(previousBufferPosition)
previousScopesArray = previousScopes.getScopesArray()
if (hasScope(scopes, 'meta.selector.css') or hasScope(previousScopesArray, 'meta.selector.css')) and not hasScope(scopes, 'source.sass', true)
true
else if hasScope(scopes, 'source.css.scss', true) or hasScope(scopes, 'source.css.less', true) or hasScope(scopes, 'source.sass', true) or hasScope(scopes, 'source.css.postcss', true)
prefix = @getPseudoSelectorPrefix(editor, bufferPosition)
if prefix
previousBufferPosition = [bufferPosition.row, Math.max(0, bufferPosition.column - prefix.length - 1)]
previousScopes = editor.scopeDescriptorForBufferPosition(previousBufferPosition)
previousScopesArray = previousScopes.getScopesArray()
not hasScope(previousScopesArray, 'meta.property-name.scss') and
not hasScope(previousScopesArray, 'meta.property-value.scss') and
not hasScope(previousScopesArray, 'meta.property-value.postcss') and
not hasScope(previousScopesArray, 'support.type.property-name.css') and
not hasScope(previousScopesArray, 'support.type.property-value.css') and
not hasScope(previousScopesArray, 'support.type.property-name.postcss')
else
false
else
false
isPropertyValuePrefix: (prefix) ->
prefix = prefix.trim()
prefix.length > 0 and prefix isnt ':'
isPropertyNamePrefix: (prefix) ->
return false unless prefix?
prefix = prefix.trim()
prefix.length > 0 and prefix.match(/^[a-zA-Z-]+$/)
getImportantPrefix: (editor, bufferPosition) ->
line = editor.getTextInRange([[bufferPosition.row, 0], bufferPosition])
importantPrefixPattern.exec(line)?[1]
getPreviousPropertyName: (bufferPosition, editor) ->
{row, column} = bufferPosition
while row >= 0
line = editor.lineTextForBufferRow(row)
line = line.substr(0, column) if row is bufferPosition.row
propertyName = inlinePropertyNameWithColonPattern.exec(line)?[1]
propertyName ?= firstInlinePropertyNameWithColonPattern.exec(line)?[1]
propertyName ?= propertyNameWithColonPattern.exec(line)?[1]
return propertyName if propertyName
row--
return
getPropertyValueCompletions: ({bufferPosition, editor, prefix, scopeDescriptor}) ->
property = @getPreviousPropertyName(bufferPosition, editor)
values = @properties[property]?.values
return null unless values?
scopes = scopeDescriptor.getScopesArray()
addSemicolon = not lineEndsWithSemicolon(bufferPosition, editor) and not hasScope(scopes, 'source.sass', true)
completions = []
if @isPropertyValuePrefix(prefix)
for value in values when firstCharsEqual(value, prefix)
completions.push(@buildPropertyValueCompletion(value, property, addSemicolon))
else if not hasScope(scopes, 'keyword.other.unit.percentage.css') and # CSS
not hasScope(scopes, 'keyword.other.unit.scss') and # SCSS (TODO: remove in Atom 1.19.0)
not hasScope(scopes, 'keyword.other.unit.css') # Less, Sass (TODO: remove in Atom 1.19.0)
# Don't complete here: `width: 100%|`
for value in values
completions.push(@buildPropertyValueCompletion(value, property, addSemicolon))
if importantPrefix = @getImportantPrefix(editor, bufferPosition)
# attention: règle dangereux
completions.push
type: 'keyword'
text: '!important'
displayText: '!important'
replacementPrefix: importantPrefix
description: "Forces this property to override any other declaration of the same property. Use with caution."
descriptionMoreURL: "#{cssDocsURL}/Specificity#The_!important_exception"
completions
buildPropertyValueCompletion: (value, propertyName, addSemicolon) ->
text = value
text += ';' if addSemicolon
{
type: 'value'
text: text
displayText: value
description: "#{value} value for the #{propertyName} property"
descriptionMoreURL: "#{cssDocsURL}/#{propertyName}#Values"
}
getPropertyNamePrefix: (bufferPosition, editor) ->
line = editor.getTextInRange([[bufferPosition.row, 0], bufferPosition])
propertyNamePrefixPattern.exec(line)?[0]
getPropertyNameCompletions: ({bufferPosition, editor, scopeDescriptor, activatedManually}) ->
# Don't autocomplete property names in SASS on root level
scopes = scopeDescriptor.getScopesArray()
line = editor.getTextInRange([[bufferPosition.row, 0], bufferPosition])
return [] if hasScope(scopes, 'source.sass', true) and not line.match(/^(\s|\t)/)
prefix = @getPropertyNamePrefix(bufferPosition, editor)
return [] unless activatedManually or prefix
completions = []
for property, options of @properties when not prefix or firstCharsEqual(property, prefix)
completions.push(@buildPropertyNameCompletion(property, prefix, options))
completions
buildPropertyNameCompletion: (propertyName, prefix, {description}) ->
type: 'property'
text: "#{propertyName}: "
displayText: propertyName
replacementPrefix: prefix
description: description
descriptionMoreURL: "#{cssDocsURL}/#{propertyName}"
getPseudoSelectorPrefix: (editor, bufferPosition) ->
line = editor.getTextInRange([[bufferPosition.row, 0], bufferPosition])
line.match(pseudoSelectorPrefixPattern)?[0]
getPseudoSelectorCompletions: ({bufferPosition, editor}) ->
prefix = @getPseudoSelectorPrefix(editor, bufferPosition)
return null unless prefix
completions = []
for pseudoSelector, options of @pseudoSelectors when firstCharsEqual(pseudoSelector, prefix)
completions.push(@buildPseudoSelectorCompletion(pseudoSelector, prefix, options))
completions
buildPseudoSelectorCompletion: (pseudoSelector, prefix, {argument, description}) ->
completion =
type: 'pseudo-selector'
replacementPrefix: prefix
description: description
descriptionMoreURL: "#{cssDocsURL}/#{pseudoSelector}"
if argument?
completion.snippet = "#{pseudoSelector}(${1:#{argument}})"
else
completion.text = pseudoSelector
completion
getTagSelectorPrefix: (editor, bufferPosition) ->
line = editor.getTextInRange([[bufferPosition.row, 0], bufferPosition])
tagSelectorPrefixPattern.exec(line)?[2]
getTagCompletions: ({bufferPosition, editor, prefix}) ->
completions = []
if prefix
for tag in @tags when firstCharsEqual(tag, prefix)
completions.push(@buildTagCompletion(tag))
completions
buildTagCompletion: (tag) ->
type: 'tag'
text: tag
description: "Selector for <#{tag}> elements"
lineEndsWithSemicolon = (bufferPosition, editor) ->
{row} = bufferPosition
line = editor.lineTextForBufferRow(row)
/;\s*$/.test(line)
hasScope = (scopesArray, scope, checkEmbedded = false) ->
scopesArray.indexOf(scope) isnt -1 or
(checkEmbedded and scopesArray.indexOf("#{scope}.embedded.html") isnt -1)
firstCharsEqual = (str1, str2) ->
str1[0].toLowerCase() is str2[0].toLowerCase()

View File

@ -0,0 +1,380 @@
const COMPLETIONS = require('../completions.json');
const firstInlinePropertyNameWithColonPattern = /{\s*(\S+)\s*:/; // .example { display: }
const inlinePropertyNameWithColonPattern = /(?:;.+?)*;\s*(\S+)\s*:/; // .example { display: block; float: left; color: } (match the last one)
const propertyNameWithColonPattern = /^\s*(\S+)\s*:/; // display:
const propertyNamePrefixPattern = /[a-zA-Z]+[-a-zA-Z]*$/;
const pseudoSelectorPrefixPattern = /:(:)?([a-z]+[a-z-]*)?$/;
const tagSelectorPrefixPattern = /(^|\s|,)([a-z]+)?$/;
const importantPrefixPattern = /(![a-z]+)$/;
const cssDocsURL = "https://developer.mozilla.org/en-US/docs/Web/CSS";
module.exports = {
selector: '.source.css, .source.sass, .source.css.postcss',
disableForSelector: '.source.css .comment, .source.css .string, .source.sass .comment, .source.sass .string, .source.css.postcss .comment, source.css.postcss .string',
properties: COMPLETIONS.properties,
pseudoSelectors: COMPLETIONS.pseudoSelectors,
tags: COMPLETIONS.tags,
// Tell autocomplete to fuzzy filter the results of getSuggestions(). We are
// still filtering by the first character of the prefix in this provider for
// efficiency.
filterSuggestions: true,
getSuggestions(request) {
let completions = null;
const scopes = request.scopeDescriptor.getScopesArray();
const isSass = hasScope(scopes, 'source.sass');
if (this.isCompletingValue(request)) {
completions = this.getPropertyValueCompletions(request);
} else if (this.isCompletingPseudoSelector(request)) {
completions = this.getPseudoSelectorCompletions(request);
} else {
if (isSass && this.isCompletingNameOrTag(request)) {
completions = this.getPropertyNameCompletions(request)
.concat(this.getTagCompletions(request));
} else if (!isSass && this.isCompletingName(request)) {
completions = this.getPropertyNameCompletions(request);
}
}
if (!isSass && this.isCompletingTagSelector(request)) {
const tagCompletions = this.getTagCompletions(request);
if (tagCompletions?.length) {
if (completions == null) { completions = []; }
completions = completions.concat(tagCompletions);
}
}
return completions;
},
onDidInsertSuggestion({editor, suggestion}) {
if (suggestion.type === 'property') { return setTimeout(this.triggerAutocomplete.bind(this, editor), 1); }
},
triggerAutocomplete(editor) {
return atom.commands.dispatch(atom.views.getView(editor), 'autocomplete-plus:activate', {activatedManually: false});
},
isCompletingValue({scopeDescriptor, bufferPosition, prefix, editor}) {
const scopes = scopeDescriptor.getScopesArray();
const beforePrefixBufferPosition = [bufferPosition.row, Math.max(0, bufferPosition.column - prefix.length - 1)];
const beforePrefixScopes = editor.scopeDescriptorForBufferPosition(beforePrefixBufferPosition);
const beforePrefixScopesArray = beforePrefixScopes.getScopesArray();
const previousBufferPosition = [bufferPosition.row, Math.max(0, bufferPosition.column - 1)];
const previousScopes = editor.scopeDescriptorForBufferPosition(previousBufferPosition);
const previousScopesArray = previousScopes.getScopesArray();
const inMetaPropertyList = hasScope(scopes, 'meta.property-list', ['css', 'scss', 'postcss']) && (prefix.trim() === ":");
const inMetaSelector = hasScope(scopes, 'meta.block.inside-selector', ['css', 'scss', 'postcss']) && (prefix.trim() === ':');
return (inMetaPropertyList || inMetaSelector) ||
(hasScope(previousScopesArray, 'meta.property-value', ['css', 'scss', 'postcss'])) ||
(hasScope(scopes, 'source.sass') && (hasScope(scopes, 'meta.property-value.sass') ||
(!hasScope(beforePrefixScopesArray, 'entity.name.tag.css') && (prefix.trim() === ":"))
));
},
isCompletingName({scopeDescriptor, bufferPosition, prefix, editor}) {
const scopes = scopeDescriptor.getScopesArray();
const isAtTerminator = prefix.endsWith(';');
const isAtParentSymbol = prefix.endsWith('&');
const isVariable = hasScope(scopes, 'variable', ['css', 'scss', 'postcss']);
const isInPropertyList = !isAtTerminator &&
(hasScope(scopes, 'meta.property-list') ||
hasScope(scopes, 'meta.block.inside-selector'));
if (!isInPropertyList) { return false; }
if (isAtParentSymbol || isVariable) { return false; }
const previousBufferPosition = [bufferPosition.row, Math.max(0, bufferPosition.column - prefix.length - 1)];
const previousScopes = editor.scopeDescriptorForBufferPosition(previousBufferPosition);
const previousScopesArray = previousScopes.getScopesArray();
if (hasScope(previousScopesArray, 'entity.other.attribute-name.class') ||
hasScope(previousScopesArray, 'entity.other.attribute-name.id') ||
hasScope(previousScopesArray, 'entity.other.attribute-name.parent-selector') ||
hasScope(previousScopesArray, 'entity.name.tag.reference', ['scss', 'postcss']) ||
hasScope(previousScopesArray, 'entity.name.tag', ['scss', 'postcss'])) { return false; }
const isAtBeginScopePunctuation = hasScope(scopes, 'punctuation.section.property-list.begin', ['css', 'scss', 'postcss']) || hasScope(scopes, 'punctuation.definition.property-list.begin', ['css', 'scss', 'postcss']);
const isAtEndScopePunctuation = hasScope(scopes, 'punctuation.section.property-list.end', ['css', 'scss', 'postcss']) || hasScope(scopes, 'punctuation.definition.property-list.end', ['css', 'scss', 'postcss']);
if (isAtBeginScopePunctuation) {
// * Disallow here: `canvas,|{}`
// * Allow here: `canvas,{| }`
return prefix.endsWith('{');
} else if (isAtEndScopePunctuation) {
// * Disallow here: `canvas,{}|`
// * Allow here: `canvas,{ |}`
return !prefix.endsWith('}');
} else {
return true;
}
},
isCompletingNameOrTag({scopeDescriptor, bufferPosition, editor}) {
const scopes = scopeDescriptor.getScopesArray();
const prefix = this.getPropertyNamePrefix(bufferPosition, editor);
return this.isPropertyNamePrefix(prefix) &&
hasScope(scopes, 'meta.selector.css') &&
!hasScope(scopes, 'entity.other.attribute-name.id.css.sass') &&
!hasScope(scopes, 'entity.other.attribute-name.class.sass');
},
isCompletingTagSelector({editor, scopeDescriptor, bufferPosition}) {
const scopes = scopeDescriptor.getScopesArray();
const tagSelectorPrefix = this.getTagSelectorPrefix(editor, bufferPosition);
if (!(tagSelectorPrefix != null ? tagSelectorPrefix.length : undefined)) { return false; }
const previousBufferPosition = [bufferPosition.row, Math.max(0, bufferPosition.column - 1)];
const previousScopes = editor.scopeDescriptorForBufferPosition(previousBufferPosition);
const previousScopesArray = previousScopes.getScopesArray();
if (hasScope(scopes, 'meta.selector.css') || hasScope(previousScopesArray, 'meta.selector.css')) {
return true;
} else if (hasScope(scopes, 'source.css', ['scss', 'less', 'postcss']) || hasScope(scopes, 'source.css')) {
return !hasScope(previousScopesArray, 'meta.property-value', ['scss', 'css', 'postcss']) &&
!hasScope(previousScopesArray, 'support.type.property-value.css');
} else {
return false;
}
},
isCompletingPseudoSelector({editor, scopeDescriptor, bufferPosition}) {
const scopes = scopeDescriptor.getScopesArray();
let previousBufferPosition = [bufferPosition.row, Math.max(0, bufferPosition.column - 1)];
let previousScopes = editor.scopeDescriptorForBufferPosition(previousBufferPosition);
let previousScopesArray = previousScopes.getScopesArray();
if ((hasScope(scopes, 'meta.selector.css') || hasScope(previousScopesArray, 'meta.selector.css')) && !hasScope(scopes, 'source.sass')) {
return true;
} else if (hasScope(scopes, 'source.css', ['scss', 'less', 'postcss']) || hasScope(scopes, 'source.sass')) {
const prefix = this.getPseudoSelectorPrefix(editor, bufferPosition);
if (prefix) {
previousBufferPosition = [bufferPosition.row, Math.max(0, bufferPosition.column - prefix.length - 1)];
previousScopes = editor.scopeDescriptorForBufferPosition(previousBufferPosition);
previousScopesArray = previousScopes.getScopesArray();
return !hasScope(previousScopesArray, 'meta.property-name.scss') &&
!hasScope(previousScopesArray, 'meta.property-value.scss') &&
!hasScope(previousScopesArray, 'meta.property-value.postcss') &&
!hasScope(previousScopesArray, 'support.type.property-name.css') &&
!hasScope(previousScopesArray, 'support.type.property-value.css') &&
!hasScope(previousScopesArray, 'support.type.property-name.postcss');
} else {
return false;
}
} else {
return false;
}
},
isPropertyValuePrefix(prefix) {
prefix = prefix.trim();
return (prefix.length > 0) && (prefix !== ':');
},
isPropertyNamePrefix(prefix) {
if (prefix == null) { return false; }
prefix = prefix.trim();
return (prefix.length > 0) && prefix.match(/^[a-zA-Z-]+$/);
},
getImportantPrefix(editor, bufferPosition) {
const line = editor.getTextInRange([[bufferPosition.row, 0], bufferPosition]);
let importantPrefixPatternExec = importantPrefixPattern.exec(line);
return (typeof importantPrefixPatternExec !== "undefined" && importantPrefixPatternExec !== null) ? importantPrefixPatternExec[1] : undefined;
},
getPreviousPropertyName(bufferPosition, editor) {
let {row, column} = bufferPosition;
while (row >= 0) {
let line = editor.lineTextForBufferRow(row);
if (row === bufferPosition.row) { line = line.substr(0, column); }
let propertyName = (typeof inlinePropertyNameWithColonPattern.exec(line) !== "undefined" && inlinePropertyNameWithColonPattern.exec(line) !== null) ? inlinePropertyNameWithColonPattern.exec(line)[1] : undefined;
if (propertyName == null) {
propertyName = (typeof firstInlinePropertyNameWithColonPattern.exec(line) !== "undefined" && firstInlinePropertyNameWithColonPattern.exec(line) !== null) ? firstInlinePropertyNameWithColonPattern.exec(line)[1] : undefined;
}
if (propertyName == null) {
propertyName = (typeof propertyNameWithColonPattern.exec(line) !== "undefined" && propertyNameWithColonPattern.exec(line) !== null) ? propertyNameWithColonPattern.exec(line)[1] : undefined;
}
if (propertyName) { return propertyName; }
row--;
}
},
getPropertyValueCompletions({bufferPosition, editor, prefix, scopeDescriptor}) {
let importantPrefix, value;
const property = this.getPreviousPropertyName(bufferPosition, editor);
const values = this.properties[property] != null ? this.properties[property].values : undefined;
if (values == null) { return null; }
const scopes = scopeDescriptor.getScopesArray();
const addSemicolon = !lineEndsWithSemicolon(bufferPosition, editor) && !hasScope(scopes, 'source.sass');
const completions = [];
if (this.isPropertyValuePrefix(prefix)) {
for (value of Array.from(values)) {
if (firstCharsEqual(value, prefix)) {
completions.push(this.buildPropertyValueCompletion(value, property, addSemicolon));
}
}
} else if (!hasScope(scopes, 'keyword.other.unit.percentage.css')) { // CSS
// Don't complete here: `width: 100%|`
for (value of Array.from(values)) {
completions.push(this.buildPropertyValueCompletion(value, property, addSemicolon));
}
}
if (importantPrefix = this.getImportantPrefix(editor, bufferPosition)) {
// attention: règle dangereux
completions.push({
type: 'keyword',
text: '!important',
displayText: '!important',
replacementPrefix: importantPrefix,
description: "Forces this property to override any other declaration of the same property. Use with caution.",
descriptionMoreURL: `${cssDocsURL}/Specificity#The_!important_exception`
});
}
return completions;
},
buildPropertyValueCompletion(value, propertyName, addSemicolon) {
let text = value;
if (addSemicolon) { text += ';'; }
return {
type: 'value',
text,
displayText: value,
description: `${value} value for the ${propertyName} property`,
descriptionMoreURL: `${cssDocsURL}/${propertyName}#Values`
};
},
getPropertyNamePrefix(bufferPosition, editor) {
const line = editor.getTextInRange([[bufferPosition.row, 0], bufferPosition]);
return (typeof propertyNamePrefixPattern.exec(line) !== "undefined" && propertyNamePrefixPattern.exec(line) !== null) ? propertyNamePrefixPattern.exec(line)[0] : undefined;
},
getPropertyNameCompletions({bufferPosition, editor, scopeDescriptor, activatedManually}) {
// Don't autocomplete property names in SASS on root level
const scopes = scopeDescriptor.getScopesArray();
const line = editor.getTextInRange([[bufferPosition.row, 0], bufferPosition]);
if (hasScope(scopes, 'source.sass') && !line.match(/^(\s|\t)/)) { return []; }
const prefix = this.getPropertyNamePrefix(bufferPosition, editor);
if (!activatedManually && !prefix) { return []; }
const completions = [];
for (let property in this.properties) {
const options = this.properties[property];
if (!prefix || firstCharsEqual(property, prefix)) {
completions.push(this.buildPropertyNameCompletion(property, prefix, options));
}
}
return completions;
},
buildPropertyNameCompletion(propertyName, prefix, {description}) {
return {
type: 'property',
text: `${propertyName}: `,
displayText: propertyName,
replacementPrefix: prefix,
description,
descriptionMoreURL: `${cssDocsURL}/${propertyName}`
};
},
getPseudoSelectorPrefix(editor, bufferPosition) {
const line = editor.getTextInRange([[bufferPosition.row, 0], bufferPosition]);
return (typeof line.match(pseudoSelectorPrefixPattern) !== "undefined" && line.match(pseudoSelectorPrefixPattern) !== null) ? line.match(pseudoSelectorPrefixPattern)[0] : undefined;
},
getPseudoSelectorCompletions({bufferPosition, editor}) {
const prefix = this.getPseudoSelectorPrefix(editor, bufferPosition);
if (!prefix) { return null; }
const completions = [];
for (let pseudoSelector in this.pseudoSelectors) {
const options = this.pseudoSelectors[pseudoSelector];
if (firstCharsEqual(pseudoSelector, prefix)) {
completions.push(this.buildPseudoSelectorCompletion(pseudoSelector, prefix, options));
}
}
return completions;
},
buildPseudoSelectorCompletion(pseudoSelector, prefix, {argument, description}) {
const completion = {
type: 'pseudo-selector',
replacementPrefix: prefix,
description,
descriptionMoreURL: `${cssDocsURL}/${pseudoSelector}`
};
if (argument != null) {
completion.snippet = `${pseudoSelector}(\${1:${argument}})`;
} else {
completion.text = pseudoSelector;
}
return completion;
},
getTagSelectorPrefix(editor, bufferPosition) {
const line = editor.getTextInRange([[bufferPosition.row, 0], bufferPosition]);
return (typeof tagSelectorPrefixPattern.exec(line) !== "undefined" && tagSelectorPrefixPattern.exec(line) !== null) ? tagSelectorPrefixPattern.exec(line)[2] : undefined;
},
getTagCompletions({bufferPosition, editor, prefix}) {
const completions = [];
if (prefix) {
for (let tag of Array.from(this.tags)) {
if (firstCharsEqual(tag, prefix)) {
completions.push(this.buildTagCompletion(tag));
}
}
}
return completions;
},
buildTagCompletion(tag) {
return {
type: 'tag',
text: tag,
description: `Selector for <${tag}> elements`
};
}
};
const lineEndsWithSemicolon = function(bufferPosition, editor) {
const {row} = bufferPosition;
const line = editor.lineTextForBufferRow(row);
return /;\s*$/.test(line);
};
// Checks if the given scope descriptor includes a scope that _starts with_ the
// given string, or is the given string exactly. Can optionally include a list
// of final path segments for checking against multiple of css/sass/less/postcss
// at once.
const hasScope = function(scopesArray, scope, endsWith = null) {
if (endsWith && (typeof endsWith === 'string')) {
endsWith = [endsWith];
}
for (var otherScope of Array.from(scopesArray)) {
if (endsWith && Array.isArray(endsWith)) {
if (!endsWith.some(ending => otherScope.endsWith(`.${ending}`))) { continue; }
}
if (otherScope === scope) { return true; }
if (otherScope.startsWith(`${scope}.`)) { return true; }
}
};
const firstCharsEqual = (str1, str2) => str1[0].toLowerCase() === str2[0].toLowerCase();

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,7 @@
"name": "autocomplete-css",
"version": "0.17.5",
"description": "CSS property name and value autocompletions",
"main": "./lib/main",
"main": "./lib/main.js",
"license": "MIT",
"repository": "https://github.com/pulsar-edit/pulsar",
"engines": {
@ -18,6 +18,6 @@
"devDependencies": {
"@webref/css": "^6.3.4",
"content": "github:mdn/content",
"request": "^2.53.0"
"superagent": "^8.0.9"
}
}

View File

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

View File

@ -1,929 +0,0 @@
packagesToTest =
CSS:
name: 'language-css'
file: 'test.css'
SCSS:
name: 'language-sass'
file: 'test.scss'
Less:
name: 'language-less'
file: 'test.less'
PostCSS:
name: 'language-postcss'
file: 'test.postcss'
# Throughout the entirety of this test document there are many places that the
# original Atom tests would check for exact values of returned items such as
# matching properties or matching tags. But as the web changes this is
# combersome to maintain, to do Pulsar's best to avoid regressions in this aspect
# these locations will now check for more than the last good value.
# This of course assumes that the web won't start removing matching items faster
# than adding. But locations of this behavior will be marked accordingly with: #398
# https://github.com/pulsar-edit/pulsar/pull/398
Object.keys(packagesToTest).forEach (packageLabel) ->
unless atom.packages.getAvailablePackageNames().includes(packagesToTest[packageLabel].name)
console.warn "Skipping tests for #{packageLabel} because it is not installed"
delete packagesToTest[packageLabel]
describe "CSS property name and value autocompletions", ->
[editor, provider] = []
getCompletions = (options={}) ->
cursor = editor.getLastCursor()
start = cursor.getBeginningOfCurrentWordBufferPosition()
end = cursor.getBufferPosition()
prefix = editor.getTextInRange([start, end])
request =
editor: editor
bufferPosition: end
scopeDescriptor: cursor.getScopeDescriptor()
prefix: prefix
activatedManually: options.activatedManually ? true
provider.getSuggestions(request)
isValueInCompletions = (value, completions) ->
completionsNodesText = []
for completion in completions
completionsNodesText.push(completion.text)
return value in completionsNodesText
beforeEach ->
waitsForPromise -> atom.packages.activatePackage('autocomplete-css')
waitsForPromise -> atom.packages.activatePackage('language-css') # Used in all CSS languages
runs ->
provider = atom.packages.getActivePackage('autocomplete-css').mainModule.getProvider()
waitsFor -> Object.keys(provider.properties).length > 0
Object.keys(packagesToTest).forEach (packageLabel) ->
describe "#{packageLabel} files", ->
beforeEach ->
waitsForPromise -> atom.packages.activatePackage(packagesToTest[packageLabel].name)
waitsForPromise -> atom.workspace.open(packagesToTest[packageLabel].file)
runs -> editor = atom.workspace.getActiveTextEditor()
it "returns tag completions when not in a property list", ->
editor.setText('')
expect(getCompletions()).toBe null
editor.setText('d')
editor.setCursorBufferPosition([0, 0])
expect(getCompletions()).toBe null
editor.setCursorBufferPosition([0, 1])
completions = getCompletions()
expect(completions.length).toBeGreaterThan 9 # #398
for completion in completions
expect(completion.text.length).toBeGreaterThan 0
expect(completion.type).toBe 'tag'
it "autocompletes property names without a prefix when activated manually", ->
editor.setText """
body {
}
"""
editor.setCursorBufferPosition([1, 0])
completions = getCompletions(activatedManually: true)
expect(completions.length).toBeGreaterThan 237 # #398 Fun Fact last check this was 673
for completion in completions
expect(completion.text.length).toBeGreaterThan 0
expect(completion.type).toBe 'property'
expect(completion.descriptionMoreURL.length).toBeGreaterThan 0
it "does not autocomplete property names without a prefix when not activated manually", ->
editor.setText """
body {
}
"""
editor.setCursorBufferPosition([1, 0])
completions = getCompletions(activatedManually: false)
expect(completions).toEqual []
it "autocompletes property names with a prefix", ->
editor.setText """
body {
d
}
"""
editor.setCursorBufferPosition([1, 3])
completions = getCompletions()
expect(isValueInCompletions('display: ', completions)).toBe true
expect(isValueInCompletions('direction: ', completions)).toBe true
# Then no matter what the top results are there's still some we can expect of them.
expect(completions[0].type).toBe 'property'
expect(completions[0].replacementPrefix).toBe 'd'
expect(completions[0].description.length).toBeGreaterThan 0
expect(completions[0].descriptionMoreURL.length).toBeGreaterThan 0
expect(completions[1].type).toBe 'property'
expect(completions[1].replacementPrefix).toBe 'd'
editor.setText """
body {
D
}
"""
editor.setCursorBufferPosition([1, 3])
completions = getCompletions()
expect(completions.length).toBeGreaterThan 2 # #398
expect(isValueInCompletions('display: ', completions)).toBe true
expect(isValueInCompletions('direction: ', completions)).toBe true
expect(completions[1].replacementPrefix).toBe 'D'
editor.setText """
body {
d:
}
"""
editor.setCursorBufferPosition([1, 3])
completions = getCompletions()
expect(isValueInCompletions('display: ', completions)).toBe true
expect(isValueInCompletions('direction: ', completions)).toBe true
editor.setText """
body {
bord
}
"""
editor.setCursorBufferPosition([1, 6])
completions = getCompletions()
expect(isValueInCompletions('border: ', completions)).toBe true
expect(completions[0].replacementPrefix).toBe 'bord'
it "does not autocomplete when at a terminator", ->
editor.setText """
body {
.somemixin();
}
"""
editor.setCursorBufferPosition([1, 15])
completions = getCompletions()
expect(completions).toBe null
it "does not autocomplete property names when preceding a {", ->
editor.setText """
body,{
}
"""
editor.setCursorBufferPosition([0, 5])
completions = getCompletions()
expect(completions).toBe null
editor.setText """
body,{}
"""
editor.setCursorBufferPosition([0, 5])
completions = getCompletions()
expect(completions).toBe null
editor.setText """
body
{
}
"""
editor.setCursorBufferPosition([1, 0])
completions = getCompletions()
expect(completions).toBe null
it "does not autocomplete property names when immediately after a }", ->
editor.setText """
body{}
"""
editor.setCursorBufferPosition([0, 6])
completions = getCompletions()
expect(completions).toBe null
editor.setText """
body{
}
"""
editor.setCursorBufferPosition([1, 1])
completions = getCompletions()
expect(completions).toBe null
it "autocompletes property names when the cursor is up against the punctuation inside the property list", ->
editor.setText """
body {
}
"""
editor.setCursorBufferPosition([0, 6])
completions = getCompletions()
expect(isValueInCompletions('width: ', completions)).toBe true
editor.setText """
body {
}
"""
editor.setCursorBufferPosition([1, 0])
completions = getCompletions()
expect(isValueInCompletions('width: ', completions)).toBe true
editor.setText """
body { }
"""
editor.setCursorBufferPosition([0, 6])
completions = getCompletions()
expect(isValueInCompletions('width: ', completions)).toBe true
editor.setText """
body { }
"""
editor.setCursorBufferPosition([0, 7])
completions = getCompletions()
expect(isValueInCompletions('width: ', completions)).toBe true
it "triggers autocomplete when an property name has been inserted", ->
spyOn(atom.commands, 'dispatch')
suggestion = {type: 'property', text: 'whatever'}
provider.onDidInsertSuggestion({editor, suggestion})
advanceClock 1
expect(atom.commands.dispatch).toHaveBeenCalled()
args = atom.commands.dispatch.mostRecentCall.args
expect(args[0].tagName.toLowerCase()).toBe 'atom-text-editor'
expect(args[1]).toBe 'autocomplete-plus:activate'
it "autocompletes property values without a prefix", ->
editor.setText """
body {
display:
}
"""
editor.setCursorBufferPosition([1, 10])
completions = getCompletions()
expect(completions.length).toBeGreaterThan 24 # #398
for completion in completions
expect(completion.text.length).toBeGreaterThan 0
expect(completion.description.length).toBeGreaterThan 0
expect(completion.descriptionMoreURL.length).toBeGreaterThan 0
editor.setText """
body {
display:
}
"""
editor.setCursorBufferPosition([2, 0])
completions = getCompletions()
expect(completions.length).toBeGreaterThan 24 # #398
for completion in completions
expect(completion.text.length).toBeGreaterThan 0
it "autocompletes property values with a prefix", ->
editor.setText """
body {
display: i
}
"""
editor.setCursorBufferPosition([1, 12])
completions = getCompletions()
expect(isValueInCompletions('inline;', completions)).toBe true
expect(isValueInCompletions('inline-block;', completions)).toBe true
expect(isValueInCompletions('inline-flex;', completions)).toBe true
expect(isValueInCompletions('inline-grid;', completions)).toBe true
expect(isValueInCompletions('inline-table;', completions)).toBe true
expect(isValueInCompletions('inherit;', completions)).toBe true
expect(completions[0].description.length).toBeGreaterThan 0
expect(completions[0].descriptionMoreURL.length).toBeGreaterThan 0
editor.setText """
body {
display: I
}
"""
editor.setCursorBufferPosition([1, 12])
completions = getCompletions()
expect(completions.length).toBeGreaterThan 6 # #398
expect(isValueInCompletions('inline;', completions)).toBe true
expect(isValueInCompletions('inline-block;', completions)).toBe true
expect(isValueInCompletions('inline-flex;', completions)).toBe true
expect(isValueInCompletions('inline-grid;', completions)).toBe true
expect(isValueInCompletions('inline-table;', completions)).toBe true
expect(isValueInCompletions('inherit;', completions)).toBe true
editor.setText """
body {
display:
i
}
"""
editor.setCursorBufferPosition([2, 5])
completions = getCompletions()
expect(isValueInCompletions('inline;', completions)).toBe true
expect(isValueInCompletions('inline-block;', completions)).toBe true
expect(isValueInCompletions('inline-flex;', completions)).toBe true
expect(isValueInCompletions('inline-grid;', completions)).toBe true
expect(isValueInCompletions('inline-table;', completions)).toBe true
expect(isValueInCompletions('inherit;', completions)).toBe true
editor.setText """
body {
text-align:
}
"""
editor.setCursorBufferPosition([1, 13])
completions = getCompletions()
expect(completions.length).toBeGreaterThan 5 # #398
expect(isValueInCompletions('center;', completions)).toBe true
expect(isValueInCompletions('left;', completions)).toBe true
expect(isValueInCompletions('justify;', completions)).toBe true
expect(isValueInCompletions('right;', completions)).toBe true
expect(isValueInCompletions('inherit;', completions)).toBe true
editor.setText """
body {
text-align: c
}
"""
editor.setCursorBufferPosition([1, 15])
completions = getCompletions()
expect(completions).toHaveLength 1
expect(completions[0].text).toBe 'center;'
it "does not complete property values after percentage signs", ->
editor.setText """
body {
width: 100%
}
"""
editor.setCursorBufferPosition([1, 13])
completions = getCompletions()
expect(completions).toHaveLength 0
it "it doesn't add semicolon after a property if one is already present", ->
editor.setText """
body {
display: i;
}
"""
editor.setCursorBufferPosition([1, 12])
completions = getCompletions()
completions.forEach (completion) ->
expect(completion.text).not.toMatch(/;\s*$/)
it "autocompletes inline property values", ->
editor.setText "body { display: }"
editor.setCursorBufferPosition([0, 16])
completions = getCompletions()
expect(completions.length).toBeGreaterThan 24 # #398
expect(isValueInCompletions('block;', completions)).toBe true
editor.setText """
body {
display: block; float:
}
"""
editor.setCursorBufferPosition([1, 24])
completions = getCompletions()
expect(completions.length).toBeGreaterThan 4 # #398
expect(isValueInCompletions('left;', completions)).toBe true
it "autocompletes more than one inline property value", ->
editor.setText "body { display: block; float: }"
editor.setCursorBufferPosition([0, 30])
completions = getCompletions()
expect(completions.length).toBeGreaterThan 4 # #398
expect(isValueInCompletions('left;', completions)).toBe true
editor.setText "body { display: block; float: left; cursor: alias; text-decoration: }"
editor.setCursorBufferPosition([0, 68])
completions = getCompletions()
expect(completions.length).toBeGreaterThan 5 # #398
expect(isValueInCompletions('line-through;', completions)).toBe true
it "autocompletes inline property values with a prefix", ->
editor.setText "body { display: i }"
editor.setCursorBufferPosition([0, 17])
completions = getCompletions()
expect(completions.length).toBeGreaterThan 6 # #398
expect(isValueInCompletions('inline;', completions)).toBe true
expect(isValueInCompletions('inline-block;', completions)).toBe true
expect(isValueInCompletions('inline-flex;', completions)).toBe true
expect(isValueInCompletions('inline-grid;', completions)).toBe true
expect(isValueInCompletions('inline-table;', completions)).toBe true
expect(isValueInCompletions('inherit;', completions)).toBe true
editor.setText "body { display: i}"
editor.setCursorBufferPosition([0, 17])
completions = getCompletions()
expect(completions.length).toBeGreaterThan 6 # #398
expect(isValueInCompletions('inline;', completions)).toBe true
expect(isValueInCompletions('inline-block;', completions)).toBe true
expect(isValueInCompletions('inline-flex;', completions)).toBe true
expect(isValueInCompletions('inline-grid;', completions)).toBe true
expect(isValueInCompletions('inline-table;', completions)).toBe true
expect(isValueInCompletions('inherit;', completions)).toBe true
it "autocompletes inline property values that aren't at the end of the line", ->
editor.setText "body { float: display: inline; font-weight: bold; }"
editor.setCursorBufferPosition([0, 14]) # right before display
completions = getCompletions()
expect(completions.length).toBeGreaterThan 4 # #398
expect(isValueInCompletions('left;', completions)).toBe true
expect(isValueInCompletions('right;', completions)).toBe true
expect(isValueInCompletions('none;', completions)).toBe true
expect(isValueInCompletions('inherit;', completions)).toBe true
it "autocompletes !important in property-value scope", ->
editor.setText """
body {
display: inherit !im
}
"""
editor.setCursorBufferPosition([1, 22])
completions = getCompletions()
important = null
for c in completions
important = c if c.displayText is '!important'
expect(important.displayText).toBe '!important'
it "does not autocomplete !important in property-name scope", ->
editor.setText """
body {
!im
}
"""
editor.setCursorBufferPosition([1, 5])
completions = getCompletions()
important = null
for c in completions
important = c if c.displayText is '!important'
expect(important).toBe null
describe "tags", ->
it "autocompletes with a prefix", ->
editor.setText """
ca {
}
"""
editor.setCursorBufferPosition([0, 2])
completions = getCompletions()
expect(completions.length).toBeGreaterThan 7 # #398
expect(isValueInCompletions('canvas', completions)).toBe true
expect(isValueInCompletions('code', completions)).toBe true
expect(completions[0].type).toBe 'tag'
expect(completions[0].description.length).toBeGreaterThan 0
editor.setText """
canvas,ca {
}
"""
editor.setCursorBufferPosition([0, 9])
completions = getCompletions()
expect(completions.length).toBeGreaterThan 7 # #398
expect(completions[0].text).toBe 'canvas'
editor.setText """
canvas ca {
}
"""
editor.setCursorBufferPosition([0, 9])
completions = getCompletions()
expect(completions.length).toBeGreaterThan 7 # #398
expect(completions[0].text).toBe 'canvas'
editor.setText """
canvas, ca {
}
"""
editor.setCursorBufferPosition([0, 10])
completions = getCompletions()
expect(completions.length).toBeGreaterThan 7 # #398
expect(completions[0].text).toBe 'canvas'
it "does not autocompletes when prefix is preceded by class or id char", ->
editor.setText """
.ca {
}
"""
editor.setCursorBufferPosition([0, 3])
completions = getCompletions()
expect(completions).toBe null
editor.setText """
#ca {
}
"""
editor.setCursorBufferPosition([0, 3])
completions = getCompletions()
expect(completions).toBe null
describe "pseudo selectors", ->
it "autocompletes without a prefix", ->
editor.setText """
div: {
}
"""
editor.setCursorBufferPosition([0, 4])
completions = getCompletions()
expect(completions.length).toBe 43
for completion in completions
text = (completion.text or completion.snippet)
expect(text.length).toBeGreaterThan 0
expect(completion.type).toBe 'pseudo-selector'
# TODO: Enable these tests when we can enable autocomplete and test the
# entire path.
xit "autocompletes with a prefix", ->
editor.setText """
div:f {
}
"""
editor.setCursorBufferPosition([0, 5])
completions = getCompletions()
expect(completions.length).toBeGreaterThan 5 # #398
expect(completions[0].text).toBe ':first'
expect(completions[0].type).toBe 'pseudo-selector'
expect(completions[0].description.length).toBeGreaterThan 0
expect(completions[0].descriptionMoreURL.length).toBeGreaterThan 0
xit "autocompletes with arguments", ->
editor.setText """
div:nth {
}
"""
editor.setCursorBufferPosition([0, 7])
completions = getCompletions()
expect(completions.length).toBeGreaterThan 4 # #398
expect(completions[0].snippet).toBe ':nth-child(${1:an+b})'
expect(completions[0].type).toBe 'pseudo-selector'
expect(completions[0].description.length).toBeGreaterThan 0
expect(completions[0].descriptionMoreURL.length).toBeGreaterThan 0
xit "autocompletes when nothing precedes the colon", ->
editor.setText """
:f {
}
"""
editor.setCursorBufferPosition([0, 2])
completions = getCompletions()
expect(completions.length).toBe 5
expect(completions[0].text).toBe ':first'
Object.keys(packagesToTest).forEach (packageLabel) ->
unless packagesToTest[packageLabel].name is 'language-css'
describe "#{packageLabel} files", ->
beforeEach ->
waitsForPromise -> atom.packages.activatePackage(packagesToTest[packageLabel].name)
waitsForPromise -> atom.workspace.open(packagesToTest[packageLabel].file)
runs -> editor = atom.workspace.getActiveTextEditor()
it "autocompletes tags and properties when nesting inside the property list", ->
editor.setText """
.ca {
di
}
"""
editor.setCursorBufferPosition([1, 4])
completions = getCompletions()
expect(isValueInCompletions('display: ', completions)).toBe true
expect(isValueInCompletions('direction: ', completions)).toBe true
expect(isValueInCompletions('div', completions)).toBe true
# FIXME: This is an issue with the grammar. It thinks nested
# pseudo-selectors are meta.property-value.scss/less
xit "autocompletes pseudo selectors when nested in LESS and SCSS files", ->
editor.setText """
.some-class {
.a:f
}
"""
editor.setCursorBufferPosition([1, 6])
completions = getCompletions()
expect(completions.length).toBe 5
expect(completions[0].text).toBe ':first'
it "does not show property names when in a class selector", ->
editor.setText """
body {
.a
}
"""
editor.setCursorBufferPosition([1, 4])
completions = getCompletions()
expect(completions).toBe null
it "does not show property names when in an id selector", ->
editor.setText """
body {
#a
}
"""
editor.setCursorBufferPosition([1, 4])
completions = getCompletions()
expect(completions).toBe null
it "does not show property names when in a parent selector", ->
editor.setText """
body {
&
}
"""
editor.setCursorBufferPosition([1, 4])
completions = getCompletions()
expect(completions).toBe null
it "does not show property names when in a parent selector with a prefix", ->
editor.setText """
body {
&a
}
"""
editor.setCursorBufferPosition([1, 4])
completions = getCompletions()
expect(completions).toBe null
describe "SASS files", ->
beforeEach ->
waitsForPromise -> atom.packages.activatePackage('language-sass')
waitsForPromise -> atom.workspace.open('test.sass')
runs -> editor = atom.workspace.getActiveTextEditor()
it "autocompletes property names with a prefix", ->
editor.setText """
body
d
"""
editor.setCursorBufferPosition([1, 3])
completions = getCompletions()
expect(isValueInCompletions('display: ', completions)).toBe true
expect(isValueInCompletions('direction: ', completions)).toBe true
expect(completions[0].type).toBe 'property'
expect(completions[0].replacementPrefix).toBe 'd'
expect(completions[0].description.length).toBeGreaterThan 0
expect(completions[0].descriptionMoreURL.length).toBeGreaterThan 0
expect(completions[1].type).toBe 'property'
expect(completions[1].replacementPrefix).toBe 'd'
editor.setText """
body
D
"""
editor.setCursorBufferPosition([1, 3])
completions = getCompletions()
expect(completions.length).toBeGreaterThan 11 # #398
expect(isValueInCompletions('display: ', completions)).toBe true
expect(isValueInCompletions('direction: ', completions)).toBe true
expect(completions[1].replacementPrefix).toBe 'D'
editor.setText """
body
d:
"""
editor.setCursorBufferPosition([1, 3])
completions = getCompletions()
expect(isValueInCompletions('display: ', completions)).toBe true
expect(isValueInCompletions('direction: ', completions)).toBe true
editor.setText """
body
bord
"""
editor.setCursorBufferPosition([1, 6])
completions = getCompletions()
expect(isValueInCompletions('border: ', completions)).toBe true
expect(completions[0].replacementPrefix).toBe 'bord'
it "triggers autocomplete when an property name has been inserted", ->
spyOn(atom.commands, 'dispatch')
suggestion = {type: 'property', text: 'whatever'}
provider.onDidInsertSuggestion({editor, suggestion})
advanceClock 1
expect(atom.commands.dispatch).toHaveBeenCalled()
args = atom.commands.dispatch.mostRecentCall.args
expect(args[0].tagName.toLowerCase()).toBe 'atom-text-editor'
expect(args[1]).toBe 'autocomplete-plus:activate'
it "autocompletes property values without a prefix", ->
editor.setText """
body
display:
"""
editor.setCursorBufferPosition([1, 10])
completions = getCompletions()
expect(completions.length).toBeGreaterThan 24 # #398
for completion in completions
expect(completion.text.length).toBeGreaterThan 0
expect(completion.description.length).toBeGreaterThan 0
expect(completion.descriptionMoreURL.length).toBeGreaterThan 0
editor.setText """
body
display:
"""
editor.setCursorBufferPosition([2, 0])
completions = getCompletions()
expect(completions.length).toBeGreaterThan 24 # #398
for completion in completions
expect(completion.text.length).toBeGreaterThan 0
it "autocompletes property values with a prefix", ->
editor.setText """
body
display: i
"""
editor.setCursorBufferPosition([1, 12])
completions = getCompletions()
expect(isValueInCompletions('inline', completions)).toBe true
expect(isValueInCompletions('inline-block', completions)).toBe true
expect(isValueInCompletions('inline-flex', completions)).toBe true
expect(isValueInCompletions('inline-grid', completions)).toBe true
expect(isValueInCompletions('inline-table', completions)).toBe true
expect(isValueInCompletions('inherit', completions)).toBe true
expect(completions[0].description.length).toBeGreaterThan 0
expect(completions[0].descriptionMoreURL.length).toBeGreaterThan 0
editor.setText """
body
display: I
"""
editor.setCursorBufferPosition([1, 12])
completions = getCompletions()
expect(completions.length).toBeGreaterThan 6 # #398
expect(isValueInCompletions('inline', completions)).toBe true
expect(isValueInCompletions('inline-block', completions)).toBe true
expect(isValueInCompletions('inline-flex', completions)).toBe true
expect(isValueInCompletions('inline-grid', completions)).toBe true
expect(isValueInCompletions('inline-table', completions)).toBe true
expect(isValueInCompletions('inherit', completions)).toBe true
it "autocompletes !important in property-value scope", ->
editor.setText """
body
display: inherit !im
"""
editor.setCursorBufferPosition([1, 22])
completions = getCompletions()
important = null
for c in completions
important = c if c.displayText is '!important'
expect(important.displayText).toBe '!important'
it "does not autocomplete when indented and prefix is not a char", ->
editor.setText """
body
.
"""
editor.setCursorBufferPosition([1, 3])
completions = getCompletions(activatedManually: false)
expect(completions).toBe null
editor.setText """
body
#
"""
editor.setCursorBufferPosition([1, 3])
completions = getCompletions(activatedManually: false)
expect(completions).toBe null
editor.setText """
body
.foo,
"""
editor.setCursorBufferPosition([1, 7])
completions = getCompletions(activatedManually: false)
expect(completions).toBe null
editor.setText """
body
foo -
"""
editor.setCursorBufferPosition([1, 8])
completions = getCompletions(activatedManually: false)
expect(completions).toBe null
# As spaces at end of line will be removed, we'll test with a char
# after the space and with the cursor before that char.
editor.setCursorBufferPosition([1, 7])
completions = getCompletions(activatedManually: false)
expect(completions).toBe null
it 'does not autocomplete when inside a nth-child selector', ->
editor.setText """
body
&:nth-child(4
"""
editor.setCursorBufferPosition([1, 15])
completions = getCompletions(activatedManually: false)
expect(completions).toBe null
it 'autocompletes a property name with a dash', ->
editor.setText """
body
border-
"""
editor.setCursorBufferPosition([1, 9])
completions = getCompletions(activatedManually: false)
expect(completions).not.toBe null
expect(isValueInCompletions('border: ', completions)).toBe true
expect(isValueInCompletions('border-radius: ', completions)).toBe true
expect(completions[0].replacementPrefix).toBe 'border-'
expect(completions[1].replacementPrefix).toBe 'border-'
it "does not autocomplete !important in property-name scope", ->
editor.setText """
body {
!im
}
"""
editor.setCursorBufferPosition([1, 5])
completions = getCompletions()
important = null
for c in completions
important = c if c.displayText is '!important'
expect(important).toBe null
describe "tags", ->
it "autocompletes with a prefix", ->
editor.setText """
ca
"""
editor.setCursorBufferPosition([0, 2])
completions = getCompletions()
expect(completions.length).toBeGreaterThan 7 # #398
expect(isValueInCompletions('canvas', completions)).toBe true
expect(isValueInCompletions('code', completions)).toBe true
expect(completions[0].type).toBe 'tag'
expect(completions[0].description.length).toBeGreaterThan 0
editor.setText """
canvas,ca
"""
editor.setCursorBufferPosition([0, 9])
completions = getCompletions()
expect(completions.length).toBeGreaterThan 7 # #398
expect(completions[0].text).toBe 'canvas'
editor.setText """
canvas ca
"""
editor.setCursorBufferPosition([0, 9])
completions = getCompletions()
expect(completions.length).toBeGreaterThan 7 # #398
expect(completions[0].text).toBe 'canvas'
editor.setText """
canvas, ca
"""
editor.setCursorBufferPosition([0, 10])
completions = getCompletions()
expect(completions.length).toBeGreaterThan 7 # #398
expect(completions[0].text).toBe 'canvas'
it "does not autocomplete when prefix is preceded by class or id char", ->
editor.setText """
.ca
"""
editor.setCursorBufferPosition([0, 3])
completions = getCompletions()
expect(completions).toBe null
editor.setText """
#ca
"""
editor.setCursorBufferPosition([0, 3])
completions = getCompletions()
expect(completions).toBe null
describe "pseudo selectors", ->
it "autocompletes without a prefix", ->
editor.setText """
div:
"""
editor.setCursorBufferPosition([0, 4])
completions = getCompletions()
expect(completions.length).toBe 43
for completion in completions
text = (completion.text or completion.snippet)
expect(text.length).toBeGreaterThan 0
expect(completion.type).toBe 'pseudo-selector'

File diff suppressed because it is too large Load Diff

View File

@ -87,6 +87,7 @@
const css = require("@webref/css");
const fs = require("fs");
const superagent = require("superagent");
const CSSParser = require("./cssValueDefinitionSyntaxExtractor.js");
const manualPropertyDesc = require("./manual-property-desc.json");
@ -103,8 +104,11 @@ async function update(params) {
pseudoSelectors: pseudoSelectors
};
completions.properties = sortByLength(completions.properties);
completions.properties = await sortByPopularity(completions.properties);
// Now to write out our updated file
fs.writeFileSync("completions.json", JSON.stringify(completions, null, 2));
fs.writeFileSync("./completions.json", JSON.stringify(completions, null, 2));
// Now to determine how many properties have empty descriptions.
@ -134,6 +138,65 @@ async function update(params) {
};
}
function sortByLength(obj) {
let keys = Object.keys(obj);
keys.sort((a, b) => {
if (a.length > b.length) {
return 1;
} else if (a.length < b.length) {
return -1;
} else {
return 0;
}
});
let newObj = {};
// Now rebuild the object according to our new keys
for (i in keys) {
newObj[keys[i]] = obj[keys[i]];
}
return newObj;
}
async function sortByPopularity(obj) {
try {
const res = await superagent.get("https://chromestatus.com/data/csspopularity");
if (res.status !== 200) {
console.error(res);
process.exit(1);
}
let newObj = {};
for (prop in res.body) {
let property = res.body[prop].property_name;
if (typeof obj[property] === "object") {
newObj[property] = obj[property];
}
}
if (Object.keys(obj).length === Object.keys(newObj).length) {
return newObj;
}
for (prop in obj) {
if (typeof newObj[prop] !== "object") {
newObj[prop] = obj[prop];
}
}
return newObj;
} catch(err) {
console.error(err);
process.exit(1);
}
}
async function buildProperties(css) {
// This function will take a CSS object of all values from @webref/css
// and will gather descriptions from mdn/content for these properties.
@ -346,7 +409,7 @@ async function getPseudoSelectors() {
// For now since there is no best determined way to collect all modern psudoselectors
// We will just grab the existing list for our existing `completions.json`
let existingCompletions = require("./completions.json");
let existingCompletions = require("../completions.json");
return existingCompletions.pseudoSelectors;
}

File diff suppressed because it is too large Load Diff

View File

@ -4,7 +4,7 @@ Displays possible autocomplete suggestions on keystroke (or manually by typing `
![autocomplete+](https://cloud.githubusercontent.com/assets/744740/7656861/9fb8bcc4-faea-11e4-9814-9dca218ded93.png)
[Changelog](https://github.com/atom/autocomplete-plus/releases)
[Changelog](https://github.com/pulsar-edit/pulsar/blob/master/CHANGELOG.md)
## Installation
@ -85,7 +85,7 @@ If the default `SymbolProvider` is missing useful information for the language /
## The `watchEditor` API
The `watchEditor` method on the `AutocompleteManager` object is exposed as a [provided service](http://flight-manual.atom.io/behind-atom/sections/interacting-with-other-packages-via-services/), named `autocomplete.watchEditor`. The method allows external editors to register for autocompletions from providers with a given set of labels. Disposing the returned object will undo this request. External packages can access this service with the following code.
The `watchEditor` method on the `AutocompleteManager` object is exposed as a [provided service](https://pulsar-edit.dev/docs/launch-manual/sections/behind-pulsar/#interacting-with-other-packages-via-services), named `autocomplete.watchEditor`. The method allows external editors to register for autocompletions from providers with a given set of labels. Disposing the returned object will undo this request. External packages can access this service with the following code.
In `package.json`:
```

View File

@ -1,37 +0,0 @@
{
"max_line_length": {
"level": "ignore"
},
"no_empty_param_list": {
"level": "error"
},
"arrow_spacing": {
"level": "error"
},
"no_interpolation_in_single_quotes": {
"level": "error"
},
"no_debugger": {
"level": "error"
},
"prefer_english_operator": {
"level": "error"
},
"colon_assignment_spacing": {
"spacing": {
"left": 0,
"right": 1
},
"level": "error"
},
"braces_spacing": {
"spaces": 0,
"level": "error"
},
"spacing_after_comma": {
"level": "error"
},
"no_stand_alone_at": {
"level": "error"
}
}

View File

@ -141,7 +141,9 @@ module.exports = class SuggestionListElement {
marked(item.descriptionMarkdown, {
gfm: true,
breaks: true,
sanitize: false
sanitize: false,
mangle: false,
headerIds: false
})
)
this.setDescriptionMoreLink(item)

View File

@ -4,7 +4,7 @@ const SuggestionListElement = require('./suggestion-list-element')
module.exports =
class SuggestionList {
constructor () {
constructor() {
this.wordPrefixRegex = null
this.cancel = this.cancel.bind(this)
this.confirm = this.confirm.bind(this)
@ -16,9 +16,10 @@ class SuggestionList {
this.hide = this.hide.bind(this)
this.destroyOverlay = this.destroyOverlay.bind(this)
this.activeEditor = null
this.lastActiveAt = 0
}
initialize () {
initialize() {
this.emitter = new Emitter()
this.subscriptions = new CompositeDisposable()
@ -37,7 +38,7 @@ class SuggestionList {
}))
}
get suggestionListElement () {
get suggestionListElement() {
if (!this._suggestionListElement) {
this._suggestionListElement = new SuggestionListElement(this)
}
@ -45,7 +46,7 @@ class SuggestionList {
return this._suggestionListElement
}
addBindings (editor) {
addBindings(editor) {
if (this.bindings && this.bindings.dispose) {
this.bindings.dispose()
}
@ -124,47 +125,47 @@ class SuggestionList {
Section: Event Triggers
*/
cancel () {
cancel() {
return this.emitter.emit('did-cancel')
}
confirm (match) {
confirm(match) {
return this.emitter.emit('did-confirm', match)
}
confirmSelection () {
confirmSelection() {
return this.emitter.emit('did-confirm-selection')
}
confirmSelectionIfNonDefault (event) {
confirmSelectionIfNonDefault(event) {
return this.emitter.emit('did-confirm-selection-if-non-default', event)
}
select (suggestion) {
select(suggestion) {
return this.emitter.emit('did-select', suggestion)
}
selectNext () {
selectNext() {
return this.emitter.emit('did-select-next')
}
selectPrevious () {
selectPrevious() {
return this.emitter.emit('did-select-previous')
}
selectPageUp () {
selectPageUp() {
return this.emitter.emit('did-select-page-up')
}
selectPageDown () {
selectPageDown() {
return this.emitter.emit('did-select-page-down')
}
selectTop () {
selectTop() {
return this.emitter.emit('did-select-top')
}
selectBottom () {
selectBottom() {
return this.emitter.emit('did-select-bottom')
}
@ -172,67 +173,67 @@ class SuggestionList {
Section: Events
*/
onDidConfirmSelection (fn) {
onDidConfirmSelection(fn) {
return this.emitter.on('did-confirm-selection', fn)
}
onDidconfirmSelectionIfNonDefault (fn) {
onDidconfirmSelectionIfNonDefault(fn) {
return this.emitter.on('did-confirm-selection-if-non-default', fn)
}
onDidConfirm (fn) {
onDidConfirm(fn) {
return this.emitter.on('did-confirm', fn)
}
onDidSelect (fn) {
onDidSelect(fn) {
return this.emitter.on('did-select', fn)
}
onDidSelectNext (fn) {
onDidSelectNext(fn) {
return this.emitter.on('did-select-next', fn)
}
onDidSelectPrevious (fn) {
onDidSelectPrevious(fn) {
return this.emitter.on('did-select-previous', fn)
}
onDidSelectPageUp (fn) {
onDidSelectPageUp(fn) {
return this.emitter.on('did-select-page-up', fn)
}
onDidSelectPageDown (fn) {
onDidSelectPageDown(fn) {
return this.emitter.on('did-select-page-down', fn)
}
onDidSelectTop (fn) {
onDidSelectTop(fn) {
return this.emitter.on('did-select-top', fn)
}
onDidSelectBottom (fn) {
onDidSelectBottom(fn) {
return this.emitter.on('did-select-bottom', fn)
}
onDidCancel (fn) {
onDidCancel(fn) {
return this.emitter.on('did-cancel', fn)
}
onDidDispose (fn) {
onDidDispose(fn) {
return this.emitter.on('did-dispose', fn)
}
onDidChangeItems (fn) {
onDidChangeItems(fn) {
return this.emitter.on('did-change-items', fn)
}
onDidChangeItem (fn) {
onDidChangeItem(fn) {
return this.emitter.on('did-change-item', fn)
}
isActive () {
isActive() {
return (this.activeEditor != null)
}
show (editor, options) {
show(editor, options) {
if (atom.config.get('autocomplete-plus.suggestionListFollows') === 'Cursor') {
return this.showAtCursorPosition(editor, options)
} else {
@ -250,7 +251,7 @@ class SuggestionList {
}
}
showAtBeginningOfPrefix (editor, prefix, followRawPrefix = false) {
showAtBeginningOfPrefix(editor, prefix, followRawPrefix = false) {
let bufferPosition
if (editor) {
bufferPosition = editor.getCursorBufferPosition()
@ -275,6 +276,7 @@ class SuggestionList {
this.overlayDecoration = editor.decorateMarker(marker, {type: 'overlay', item: this.suggestionListElement, position: 'tail', class: 'autocomplete-plus'})
const editorElement = atom.views.getView(this.activeEditor)
if (editorElement && editorElement.classList) {
this.lastActiveAt = performance.now()
editorElement.classList.add('autocomplete-active')
}
@ -283,7 +285,7 @@ class SuggestionList {
}
}
showAtCursorPosition (editor) {
showAtCursorPosition(editor) {
if (this.activeEditor === editor || (editor == null)) { return }
this.destroyOverlay()
@ -295,6 +297,7 @@ class SuggestionList {
this.activeEditor = editor
const editorElement = atom.views.getView(this.activeEditor)
if (editorElement && editorElement.classList) {
this.lastActiveAt = performance.now()
editorElement.classList.add('autocomplete-active')
}
@ -303,7 +306,7 @@ class SuggestionList {
}
}
hide () {
hide() {
this.destroyOverlay()
if (this.activeEditor === null) {
return
@ -317,7 +320,7 @@ class SuggestionList {
return this.activeEditor
}
destroyOverlay () {
destroyOverlay() {
if (this.suggestionMarker && this.suggestionMarker.destroy) {
this.suggestionMarker.destroy()
} else if (this.overlayDecoration && this.overlayDecoration.destroy) {
@ -325,7 +328,11 @@ class SuggestionList {
}
const editorElement = atom.views.getView(this.activeEditor)
if (editorElement && editorElement.classList) {
let timestamp = this.lastActiveAt
atom.views.updateDocument(() => {
// A newer timestamp here means that the menu is open again and we
// shouldn't remove this class name anymore.
if (this.lastActiveAt > timestamp) return
editorElement.classList.remove('autocomplete-active')
})
}
@ -334,12 +341,12 @@ class SuggestionList {
return this.overlayDecoration
}
changeItems (items) {
changeItems(items) {
this.items = items
return this.emitter.emit('did-change-items', this.items)
}
replaceItem (oldSuggestion, newSuggestion) {
replaceItem(oldSuggestion, newSuggestion) {
if (newSuggestion == null) {
return
}
@ -368,7 +375,7 @@ class SuggestionList {
}
// Public: Clean up, stop listening to events
dispose () {
dispose() {
if (this.subscriptions) {
this.subscriptions.dispose()
}

File diff suppressed because it is too large Load Diff

View File

@ -1,30 +1,28 @@
{
"name": "autocomplete-plus",
"version": "2.42.6",
"main": "./lib/main",
"main": "./lib/main.js",
"description": "Display possible completions in the editor while typing",
"repository": "https://github.com/atom/autocomplete-plus",
"repository": "https://github.com/pulsar-edit/pulsar",
"license": "MIT",
"engines": {
"atom": ">=0.189.0 <2.0.0"
},
"dependencies": {
"atom-slick": "^2.0.0",
"dompurify": "^2.2.6",
"dompurify": "^3.0.4",
"fuzzaldrin": "^2.1.0",
"fuzzaldrin-plus": "^0.6.0",
"grim": "^2.0.1",
"marked": "^4.0.10",
"marked": "^5.1.1",
"minimatch": "^3.0.3",
"selector-kit": "^0.1",
"stable": "^0.1.5",
"underscore-plus": "^1.6.6"
"selector-kit": "^0.1.0",
"stable": "^0.1.8",
"underscore-plus": "^1.7.0"
},
"devDependencies": {
"coffeelint": "^1.9.7",
"fs-plus": ">=2.4.0",
"standard": "^8.3.0",
"temp": ">=0.7.0"
"fs-plus": "^3.1.1",
"temp": "^0.9.4"
},
"providedServices": {
"autocomplete.watchEditor": {
@ -88,7 +86,7 @@
},
"useCoreMovementCommands": {
"title": "Use Core Movement Commands",
"description": "Disable this if you want to bind your own keystrokes to move around the suggestion list. You will also need to add definitions to your keymap. See: https://github.com/atom/autocomplete-plus#remapping-movement-commands",
"description": "Disable this if you want to bind your own keystrokes to move around the suggestion list. You will also need to add definitions to your keymap. See: https://github.com/pulsar-edit/pulsar/blob/master/packages/autocomplete-plus/README.md#remapping-movement-commands",
"type": "boolean",
"default": true,
"order": 5
@ -107,7 +105,7 @@
},
"scopeBlacklist": {
"title": "Scope Blacklist",
"description": "Suggestions will not be provided for scopes matching this list. See: http://flight-manual.atom.io/behind-atom/sections/scoped-settings-scopes-and-scope-descriptors/",
"description": "Suggestions will not be provided for scopes matching this list. See: https://pulsar-edit.dev/docs/launch-manual/sections/behind-pulsar/#scoped-settings-scopes-and-scope-descriptors",
"type": "array",
"default": [],
"items": {
@ -241,18 +239,5 @@
"default": false,
"order": 23
}
},
"standard": {
"globals": [
"CustomEvent",
"HTMLElement",
"advanceClock",
"atom",
"getComputedStyle",
"waitsForPromise"
],
"ignore": [
"**/spec/fixtures/**"
]
}
}

View File

@ -42,7 +42,7 @@ autocomplete-suggestion-list.select-list.popover-list {
}
.suggestion-description-content {
font-size: @font-size + 1px;
font-size: (@font-size + 1px);
font-family: @font-family;
max-height: 33vh;
display: block;
@ -51,7 +51,7 @@ autocomplete-suggestion-list.select-list.popover-list {
}
.suggestion-description-more-link {
font-size: @font-size + 1px;
font-size: (@font-size + 1px);
font-family: @font-family;
color: @text-color-info;
}

View File

@ -1,142 +0,0 @@
_ = require 'underscore-plus'
CharacterPattern = ///
[
^\s
]
///
module.exports =
activate: ->
@commandDisposable = atom.commands.add 'atom-text-editor',
'autoflow:reflow-selection': (event) =>
@reflowSelection(event.currentTarget.getModel())
deactivate: ->
@commandDisposable?.dispose()
@commandDisposable = null
reflowSelection: (editor) ->
range = editor.getSelectedBufferRange()
range = editor.getCurrentParagraphBufferRange() if range.isEmpty()
return unless range?
reflowOptions =
wrapColumn: @getPreferredLineLength(editor)
tabLength: @getTabLength(editor)
reflowedText = @reflow(editor.getTextInRange(range), reflowOptions)
editor.getBuffer().setTextInRange(range, reflowedText)
reflow: (text, {wrapColumn, tabLength}) ->
paragraphs = []
# Convert all \r\n and \r to \n. The text buffer will normalize them later
text = text.replace(/\r\n?/g, '\n')
leadingVerticalSpace = text.match(/^\s*\n/)
if leadingVerticalSpace
text = text.substr(leadingVerticalSpace.length)
else
leadingVerticalSpace = ''
trailingVerticalSpace = text.match(/\n\s*$/)
if trailingVerticalSpace
text = text.substr(0, text.length - trailingVerticalSpace.length)
else
trailingVerticalSpace = ''
paragraphBlocks = text.split(/\n\s*\n/g)
if tabLength
tabLengthInSpaces = Array(tabLength + 1).join(' ')
else
tabLengthInSpaces = ''
for block in paragraphBlocks
blockLines = block.split('\n')
# For LaTeX tags surrounding the text, we simply ignore them, and
# reproduce them verbatim in the wrapped text.
beginningLinesToIgnore = []
endingLinesToIgnore = []
latexTagRegex = /^\s*\\\w+(\[.*\])?\{\w+\}(\[.*\])?\s*$/g # e.g. \begin{verbatim}
latexTagStartRegex = /^\s*\\\w+\s*\{\s*$/g # e.g. \item{
latexTagEndRegex = /^\s*\}\s*$/g # e.g. }
while blockLines.length > 0 and (
blockLines[0].match(latexTagRegex) or
blockLines[0].match(latexTagStartRegex))
beginningLinesToIgnore.push(blockLines[0])
blockLines.shift()
while blockLines.length > 0 and (
blockLines[blockLines.length - 1].match(latexTagRegex) or
blockLines[blockLines.length - 1].match(latexTagEndRegex))
endingLinesToIgnore.unshift(blockLines[blockLines.length - 1])
blockLines.pop()
# The paragraph might be a LaTeX section with no text, only tags:
# \documentclass{article}
# In that case, we have nothing to reflow.
# Push the tags verbatim and continue to the next paragraph.
unless blockLines.length > 0
paragraphs.push(block)
continue
# TODO: this could be more language specific. Use the actual comment char.
# Remember that `-` has to be the last character in the character class.
linePrefix = blockLines[0].match(/^\s*(\/\/|\/\*|;;|#'|\|\|\||--|[#%*>-])?\s*/g)[0]
linePrefixTabExpanded = linePrefix
if tabLengthInSpaces
linePrefixTabExpanded = linePrefix.replace(/\t/g, tabLengthInSpaces)
if linePrefix
escapedLinePrefix = _.escapeRegExp(linePrefix)
blockLines = blockLines.map (blockLine) ->
blockLine.replace(///^#{escapedLinePrefix}///, '')
blockLines = blockLines.map (blockLine) ->
blockLine.replace(/^\s+/, '')
lines = []
currentLine = []
currentLineLength = linePrefixTabExpanded.length
wrappedLinePrefix = linePrefix
.replace(/^(\s*)\/\*/, '$1 ')
.replace(/^(\s*)-(?!-)/, '$1 ')
firstLine = true
for segment in @segmentText(blockLines.join(' '))
if @wrapSegment(segment, currentLineLength, wrapColumn)
# Independent of line prefix don't mess with it on the first line
if firstLine isnt true
# Handle C comments
if linePrefix.search(/^\s*\/\*/) isnt -1 or linePrefix.search(/^\s*-(?!-)/) isnt -1
linePrefix = wrappedLinePrefix
lines.push(linePrefix + currentLine.join(''))
currentLine = []
currentLineLength = linePrefixTabExpanded.length
firstLine = false
currentLine.push(segment)
currentLineLength += segment.length
lines.push(linePrefix + currentLine.join(''))
wrappedLines = beginningLinesToIgnore.concat(lines.concat(endingLinesToIgnore))
paragraphs.push(wrappedLines.join('\n').replace(/\s+\n/g, '\n'))
return leadingVerticalSpace + paragraphs.join('\n\n') + trailingVerticalSpace
getTabLength: (editor) ->
atom.config.get('editor.tabLength', scope: editor.getRootScopeDescriptor()) ? 2
getPreferredLineLength: (editor) ->
atom.config.get('editor.preferredLineLength', scope: editor.getRootScopeDescriptor())
wrapSegment: (segment, currentLineLength, wrapColumn) ->
CharacterPattern.test(segment) and
(currentLineLength + segment.length > wrapColumn) and
(currentLineLength > 0 or segment.length < wrapColumn)
segmentText: (text) ->
segments = []
re = /[\s]+|[^\s]+/g
segments.push(match[0]) while match = re.exec(text)
segments

View File

@ -0,0 +1,165 @@
const _ = require('underscore-plus');
const CharacterPattern = new RegExp(/[^\s]/);
module.exports = {
activate() {
this.commandDisposable = atom.commands.add('atom-text-editor', {
'autoflow:reflow-selection': event => {
this.reflowSelection(event.currentTarget.getModel());
}
}
);
},
deactivate() {
this.commandDisposable?.dispose();
this.commandDisposable = null;
},
reflowSelection(editor) {
let range = editor.getSelectedBufferRange();
if (range.isEmpty()) { range = editor.getCurrentParagraphBufferRange(); }
if (range == null) { return; }
const reflowOptions = {
wrapColumn: this.getPreferredLineLength(editor),
tabLength: this.getTabLength(editor)
};
const reflowedText = this.reflow(editor.getTextInRange(range), reflowOptions);
return editor.getBuffer().setTextInRange(range, reflowedText);
},
reflow(text, {wrapColumn, tabLength}) {
let tabLengthInSpaces;
const paragraphs = [];
// Convert all \r\n and \r to \n. The text buffer will normalize them later
text = text.replace(/\r\n?/g, '\n');
let leadingVerticalSpace = text.match(/^\s*\n/);
if (leadingVerticalSpace) {
text = text.substr(leadingVerticalSpace.length);
} else {
leadingVerticalSpace = '';
}
let trailingVerticalSpace = text.match(/\n\s*$/);
if (trailingVerticalSpace) {
text = text.substr(0, text.length - trailingVerticalSpace.length);
} else {
trailingVerticalSpace = '';
}
const paragraphBlocks = text.split(/\n\s*\n/g);
if (tabLength) {
tabLengthInSpaces = Array(tabLength + 1).join(' ');
} else {
tabLengthInSpaces = '';
}
for (let block of paragraphBlocks) {
let blockLines = block.split('\n');
// For LaTeX tags surrounding the text, we simply ignore them, and
// reproduce them verbatim in the wrapped text.
const beginningLinesToIgnore = [];
const endingLinesToIgnore = [];
const latexTagRegex = /^\s*\\\w+(\[.*\])?\{\w+\}(\[.*\])?\s*$/g; // e.g. \begin{verbatim}
const latexTagStartRegex = /^\s*\\\w+\s*\{\s*$/g; // e.g. \item{
const latexTagEndRegex = /^\s*\}\s*$/g; // e.g. }
while ((blockLines.length > 0) && (
blockLines[0].match(latexTagRegex) ||
blockLines[0].match(latexTagStartRegex))) {
beginningLinesToIgnore.push(blockLines[0]);
blockLines.shift();
}
while ((blockLines.length > 0) && (
blockLines[blockLines.length - 1].match(latexTagRegex) ||
blockLines[blockLines.length - 1].match(latexTagEndRegex))) {
endingLinesToIgnore.unshift(blockLines[blockLines.length - 1]);
blockLines.pop();
}
// The paragraph might be a LaTeX section with no text, only tags:
// \documentclass{article}
// In that case, we have nothing to reflow.
// Push the tags verbatim and continue to the next paragraph.
if (!(blockLines.length > 0)) {
paragraphs.push(block);
continue;
}
// TODO: this could be more language specific. Use the actual comment char.
// Remember that `-` has to be the last character in the character class.
let linePrefix = blockLines[0].match(/^\s*(\/\/|\/\*|;;|#'|\|\|\||--|[#%*>-])?\s*/g)[0];
let linePrefixTabExpanded = linePrefix;
if (tabLengthInSpaces) {
linePrefixTabExpanded = linePrefix.replace(/\t/g, tabLengthInSpaces);
}
if (linePrefix) {
var escapedLinePrefix = _.escapeRegExp(linePrefix);
blockLines = blockLines.map(blockLine => blockLine.replace(new RegExp(`^${escapedLinePrefix}`), ''));
}
blockLines = blockLines.map(blockLine => blockLine.replace(/^\s+/, ''));
const lines = [];
let currentLine = [];
let currentLineLength = linePrefixTabExpanded.length;
const wrappedLinePrefix = linePrefix
.replace(/^(\s*)\/\*/, '$1 ')
.replace(/^(\s*)-(?!-)/, '$1 ');
let firstLine = true;
for (let segment of this.segmentText(blockLines.join(' '))) {
if (this.wrapSegment(segment, currentLineLength, wrapColumn)) {
// Independent of line prefix don't mess with it on the first line
if (firstLine !== true) {
// Handle C comments
if ((linePrefix.search(/^\s*\/\*/) !== -1) || (linePrefix.search(/^\s*-(?!-)/) !== -1)) {
linePrefix = wrappedLinePrefix;
}
}
lines.push(linePrefix + currentLine.join(''));
currentLine = [];
currentLineLength = linePrefixTabExpanded.length;
firstLine = false;
}
currentLine.push(segment);
currentLineLength += segment.length;
}
lines.push(linePrefix + currentLine.join(''));
const wrappedLines = beginningLinesToIgnore.concat(lines.concat(endingLinesToIgnore));
paragraphs.push(wrappedLines.join('\n').replace(/\s+\n/g, '\n'));
}
return leadingVerticalSpace + paragraphs.join('\n\n') + trailingVerticalSpace;
},
getTabLength(editor) {
return atom.config.get('editor.tabLength', { scope: editor.getRootScopeDescriptor() }) ?? 2;
},
getPreferredLineLength(editor) {
return atom.config.get('editor.preferredLineLength', {scope: editor.getRootScopeDescriptor()});
},
wrapSegment(segment, currentLineLength, wrapColumn) {
return CharacterPattern.test(segment) &&
((currentLineLength + segment.length) > wrapColumn) &&
((currentLineLength > 0) || (segment.length < wrapColumn));
},
segmentText(text) {
let match;
const segments = [];
const re = /[\s]+|[^\s]+/g;
while ((match = re.exec(text))) { segments.push(match[0]); }
return segments;
}
};

View File

@ -1,639 +0,0 @@
describe "Autoflow package", ->
[autoflow, editor, editorElement] = []
tabLength = 4
describe "autoflow:reflow-selection", ->
beforeEach ->
activationPromise = null
waitsForPromise ->
atom.workspace.open()
runs ->
editor = atom.workspace.getActiveTextEditor()
editorElement = atom.views.getView(editor)
atom.config.set('editor.preferredLineLength', 30)
atom.config.set('editor.tabLength', tabLength)
activationPromise = atom.packages.activatePackage('autoflow')
atom.commands.dispatch editorElement, 'autoflow:reflow-selection'
waitsForPromise ->
activationPromise
it "uses the preferred line length based on the editor's scope", ->
atom.config.set('editor.preferredLineLength', 4, scopeSelector: '.text.plain.null-grammar')
editor.setText("foo bar")
editor.selectAll()
atom.commands.dispatch editorElement, 'autoflow:reflow-selection'
expect(editor.getText()).toBe """
foo
bar
"""
it "rearranges line breaks in the current selection to ensure lines are shorter than config.editor.preferredLineLength honoring tabLength", ->
editor.setText "\t\tThis is the first paragraph and it is longer than the preferred line length so it should be reflowed.\n\n\t\tThis is a short paragraph.\n\n\t\tAnother long paragraph, it should also be reflowed with the use of this single command."
editor.selectAll()
atom.commands.dispatch editorElement, 'autoflow:reflow-selection'
exedOut = editor.getText().replace(/\t/g, Array(tabLength+1).join 'X')
expect(exedOut).toBe "XXXXXXXXThis is the first\nXXXXXXXXparagraph and it is\nXXXXXXXXlonger than the\nXXXXXXXXpreferred line length\nXXXXXXXXso it should be\nXXXXXXXXreflowed.\n\nXXXXXXXXThis is a short\nXXXXXXXXparagraph.\n\nXXXXXXXXAnother long\nXXXXXXXXparagraph, it should\nXXXXXXXXalso be reflowed with\nXXXXXXXXthe use of this single\nXXXXXXXXcommand."
it "rearranges line breaks in the current selection to ensure lines are shorter than config.editor.preferredLineLength", ->
editor.setText """
This is the first paragraph and it is longer than the preferred line length so it should be reflowed.
This is a short paragraph.
Another long paragraph, it should also be reflowed with the use of this single command.
"""
editor.selectAll()
atom.commands.dispatch editorElement, 'autoflow:reflow-selection'
expect(editor.getText()).toBe """
This is the first paragraph
and it is longer than the
preferred line length so it
should be reflowed.
This is a short paragraph.
Another long paragraph, it
should also be reflowed with
the use of this single
command.
"""
it "is not confused when the selection boundary is between paragraphs", ->
editor.setText """
v--- SELECTION STARTS AT THE BEGINNING OF THE NEXT LINE (pos 1,0)
The preceding newline should not be considered part of this paragraph.
The newline at the end of this paragraph should be preserved and not
converted into a space.
^--- SELECTION ENDS AT THE BEGINNING OF THE PREVIOUS LINE (pos 6,0)
"""
editor.setCursorBufferPosition([1, 0])
editor.selectToBufferPosition([6, 0])
atom.commands.dispatch editorElement, 'autoflow:reflow-selection'
expect(editor.getText()).toBe """
v--- SELECTION STARTS AT THE BEGINNING OF THE NEXT LINE (pos 1,0)
The preceding newline should
not be considered part of this
paragraph.
The newline at the end of this
paragraph should be preserved
and not converted into a
space.
^--- SELECTION ENDS AT THE BEGINNING OF THE PREVIOUS LINE (pos 6,0)
"""
it "reflows the current paragraph if nothing is selected", ->
editor.setText """
This is a preceding paragraph, which shouldn't be modified by a reflow of the following paragraph.
The quick brown fox jumps over the lazy
dog. The preceding sentence contains every letter
in the entire English alphabet, which has absolutely no relevance
to this test.
This is a following paragraph, which shouldn't be modified by a reflow of the preciding paragraph.
"""
editor.setCursorBufferPosition([3, 5])
atom.commands.dispatch editorElement, 'autoflow:reflow-selection'
expect(editor.getText()).toBe """
This is a preceding paragraph, which shouldn't be modified by a reflow of the following paragraph.
The quick brown fox jumps over
the lazy dog. The preceding
sentence contains every letter
in the entire English
alphabet, which has absolutely
no relevance to this test.
This is a following paragraph, which shouldn't be modified by a reflow of the preciding paragraph.
"""
it "allows for single words that exceed the preferred wrap column length", ->
editor.setText("this-is-a-super-long-word-that-shouldn't-break-autoflow and these are some smaller words")
editor.selectAll()
atom.commands.dispatch editorElement, 'autoflow:reflow-selection'
expect(editor.getText()).toBe """
this-is-a-super-long-word-that-shouldn't-break-autoflow
and these are some smaller
words
"""
describe "reflowing text", ->
beforeEach ->
autoflow = require("../lib/autoflow")
it 'respects current paragraphs', ->
text = '''
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus gravida nibh id magna ullamcorper sagittis. Maecenas
et enim eu orci tincidunt adipiscing
aliquam ligula.
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Phasellus gravida
nibh id magna ullamcorper
tincidunt adipiscing lacinia a dui. Etiam quis erat dolor.
rutrum nisl fermentum rhoncus. Duis blandit ligula facilisis fermentum.
'''
res = '''
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus gravida nibh
id magna ullamcorper sagittis. Maecenas et enim eu orci tincidunt adipiscing
aliquam ligula.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus gravida nibh
id magna ullamcorper tincidunt adipiscing lacinia a dui. Etiam quis erat dolor.
rutrum nisl fermentum rhoncus. Duis blandit ligula facilisis fermentum.
'''
expect(autoflow.reflow(text, wrapColumn: 80)).toEqual res
it 'respects indentation', ->
text = '''
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus gravida nibh id magna ullamcorper sagittis. Maecenas
et enim eu orci tincidunt adipiscing
aliquam ligula.
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Phasellus gravida
nibh id magna ullamcorper
tincidunt adipiscing lacinia a dui. Etiam quis erat dolor.
rutrum nisl fermentum rhoncus. Duis blandit ligula facilisis fermentum
'''
res = '''
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus gravida nibh
id magna ullamcorper sagittis. Maecenas et enim eu orci tincidunt adipiscing
aliquam ligula.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus gravida
nibh id magna ullamcorper tincidunt adipiscing lacinia a dui. Etiam quis
erat dolor. rutrum nisl fermentum rhoncus. Duis blandit ligula facilisis
fermentum
'''
expect(autoflow.reflow(text, wrapColumn: 80)).toEqual res
it 'respects prefixed text (comments!)', ->
text = '''
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus gravida nibh id magna ullamcorper sagittis. Maecenas
et enim eu orci tincidunt adipiscing
aliquam ligula.
# Lorem ipsum dolor sit amet, consectetur adipiscing elit.
# Phasellus gravida
# nibh id magna ullamcorper
# tincidunt adipiscing lacinia a dui. Etiam quis erat dolor.
# rutrum nisl fermentum rhoncus. Duis blandit ligula facilisis fermentum
'''
res = '''
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus gravida nibh
id magna ullamcorper sagittis. Maecenas et enim eu orci tincidunt adipiscing
aliquam ligula.
# Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus gravida
# nibh id magna ullamcorper tincidunt adipiscing lacinia a dui. Etiam quis
# erat dolor. rutrum nisl fermentum rhoncus. Duis blandit ligula facilisis
# fermentum
'''
expect(autoflow.reflow(text, wrapColumn: 80)).toEqual res
it 'respects multiple prefixes (js/c comments)', ->
text = '''
// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus gravida
et enim eu orci tincidunt adipiscing
aliquam ligula.
'''
res = '''
// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus gravida et
// enim eu orci tincidunt adipiscing aliquam ligula.
'''
expect(autoflow.reflow(text, wrapColumn: 80)).toEqual res
it 'properly handles * prefix', ->
text = '''
* Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus gravida
et enim eu orci tincidunt adipiscing
aliquam ligula.
* soidjfiojsoidj foi
'''
res = '''
* Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus gravida et
* enim eu orci tincidunt adipiscing aliquam ligula.
* soidjfiojsoidj foi
'''
expect(autoflow.reflow(text, wrapColumn: 80)).toEqual res
it "does not throw invalid regular expression errors (regression)", ->
text = '''
*** Lorem ipsum dolor sit amet
'''
expect(autoflow.reflow(text, wrapColumn: 80)).toEqual text
it 'handles different initial indentation', ->
text = '''
Magna ea magna fugiat nisi minim in id duis. Culpa sit sint consequat quis elit magna pariatur incididunt
proident laborum deserunt est aliqua reprehenderit. Occaecat et ex non do Lorem irure adipisicing mollit excepteur
eu ullamco consectetur. Ex ex Lorem duis labore quis ad exercitation elit dolor non adipisicing. Pariatur commodo ullamco
culpa dolor sunt enim. Ullamco dolore do ea nulla ut commodo minim consequat cillum ad velit quis.
'''
res = '''
Magna ea magna fugiat nisi minim in id duis. Culpa sit sint consequat quis elit
magna pariatur incididunt proident laborum deserunt est aliqua reprehenderit.
Occaecat et ex non do Lorem irure adipisicing mollit excepteur eu ullamco
consectetur. Ex ex Lorem duis labore quis ad exercitation elit dolor non
adipisicing. Pariatur commodo ullamco culpa dolor sunt enim. Ullamco dolore do
ea nulla ut commodo minim consequat cillum ad velit quis.
'''
expect(autoflow.reflow(text, wrapColumn: 80)).toEqual res
it 'properly handles CRLF', ->
text = "This is the first line and it is longer than the preferred line length so it should be reflowed.\r\nThis is a short line which should\r\nbe reflowed with the following line.\rAnother long line, it should also be reflowed with everything above it when it is all reflowed."
res =
'''
This is the first line and it is longer than the preferred line length so it
should be reflowed. This is a short line which should be reflowed with the
following line. Another long line, it should also be reflowed with everything
above it when it is all reflowed.
'''
expect(autoflow.reflow(text, wrapColumn: 80)).toEqual res
it 'handles cyrillic text', ->
text = '''
В начале июля, в чрезвычайно жаркое время, под вечер, один молодой человек вышел из своей каморки, которую нанимал от жильцов в С-м переулке, на улицу и медленно, как бы в нерешимости, отправился к К-ну мосту.
'''
res = '''
В начале июля, в чрезвычайно жаркое время, под вечер, один молодой человек вышел
из своей каморки, которую нанимал от жильцов в С-м переулке, на улицу и
медленно, как бы в нерешимости, отправился к К-ну мосту.
'''
expect(autoflow.reflow(text, wrapColumn: 80)).toEqual res
it 'handles `yo` character properly', ->
# Because there're known problems with this character in major regex engines
text = 'Ё Ё Ё'
res = '''
Ё
Ё
Ё
'''
expect(autoflow.reflow(text, wrapColumn: 2)).toEqual res
it 'properly reflows // comments ', ->
text =
'''
// Beard pinterest actually brunch brooklyn jean shorts YOLO. Knausgaard sriracha banh mi, cold-pressed retro whatever ethical man braid asymmetrical fingerstache narwhal. Intelligentsia wolf photo booth, tumblr pinterest quinoa leggings four loko poutine. DIY tattooed drinking vinegar, wolf retro actually aesthetic austin keffiyeh marfa beard. Marfa trust fund salvia sartorial letterpress, keffiyeh plaid butcher. Swag try-hard dreamcatcher direct trade, tacos pickled fanny pack literally meh pinterest slow-carb. Meditation microdosing distillery 8-bit humblebrag migas.
'''
res =
'''
// Beard pinterest actually brunch brooklyn jean shorts YOLO. Knausgaard
// sriracha banh mi, cold-pressed retro whatever ethical man braid asymmetrical
// fingerstache narwhal. Intelligentsia wolf photo booth, tumblr pinterest
// quinoa leggings four loko poutine. DIY tattooed drinking vinegar, wolf retro
// actually aesthetic austin keffiyeh marfa beard. Marfa trust fund salvia
// sartorial letterpress, keffiyeh plaid butcher. Swag try-hard dreamcatcher
// direct trade, tacos pickled fanny pack literally meh pinterest slow-carb.
// Meditation microdosing distillery 8-bit humblebrag migas.
'''
expect(autoflow.reflow(text, wrapColumn: 80)).toEqual res
it 'properly reflows /* comments ', ->
text =
'''
/* Beard pinterest actually brunch brooklyn jean shorts YOLO. Knausgaard sriracha banh mi, cold-pressed retro whatever ethical man braid asymmetrical fingerstache narwhal. Intelligentsia wolf photo booth, tumblr pinterest quinoa leggings four loko poutine. DIY tattooed drinking vinegar, wolf retro actually aesthetic austin keffiyeh marfa beard. Marfa trust fund salvia sartorial letterpress, keffiyeh plaid butcher. Swag try-hard dreamcatcher direct trade, tacos pickled fanny pack literally meh pinterest slow-carb. Meditation microdosing distillery 8-bit humblebrag migas. */
'''
res =
'''
/* Beard pinterest actually brunch brooklyn jean shorts YOLO. Knausgaard
sriracha banh mi, cold-pressed retro whatever ethical man braid asymmetrical
fingerstache narwhal. Intelligentsia wolf photo booth, tumblr pinterest
quinoa leggings four loko poutine. DIY tattooed drinking vinegar, wolf retro
actually aesthetic austin keffiyeh marfa beard. Marfa trust fund salvia
sartorial letterpress, keffiyeh plaid butcher. Swag try-hard dreamcatcher
direct trade, tacos pickled fanny pack literally meh pinterest slow-carb.
Meditation microdosing distillery 8-bit humblebrag migas. */
'''
expect(autoflow.reflow(text, wrapColumn: 80)).toEqual res
it 'properly reflows pound comments ', ->
text =
'''
# Beard pinterest actually brunch brooklyn jean shorts YOLO. Knausgaard sriracha banh mi, cold-pressed retro whatever ethical man braid asymmetrical fingerstache narwhal. Intelligentsia wolf photo booth, tumblr pinterest quinoa leggings four loko poutine. DIY tattooed drinking vinegar, wolf retro actually aesthetic austin keffiyeh marfa beard. Marfa trust fund salvia sartorial letterpress, keffiyeh plaid butcher. Swag try-hard dreamcatcher direct trade, tacos pickled fanny pack literally meh pinterest slow-carb. Meditation microdosing distillery 8-bit humblebrag migas.
'''
res =
'''
# Beard pinterest actually brunch brooklyn jean shorts YOLO. Knausgaard sriracha
# banh mi, cold-pressed retro whatever ethical man braid asymmetrical
# fingerstache narwhal. Intelligentsia wolf photo booth, tumblr pinterest quinoa
# leggings four loko poutine. DIY tattooed drinking vinegar, wolf retro actually
# aesthetic austin keffiyeh marfa beard. Marfa trust fund salvia sartorial
# letterpress, keffiyeh plaid butcher. Swag try-hard dreamcatcher direct trade,
# tacos pickled fanny pack literally meh pinterest slow-carb. Meditation
# microdosing distillery 8-bit humblebrag migas.
'''
expect(autoflow.reflow(text, wrapColumn: 80)).toEqual res
it 'properly reflows - list items ', ->
text =
'''
- Beard pinterest actually brunch brooklyn jean shorts YOLO. Knausgaard sriracha banh mi, cold-pressed retro whatever ethical man braid asymmetrical fingerstache narwhal. Intelligentsia wolf photo booth, tumblr pinterest quinoa leggings four loko poutine. DIY tattooed drinking vinegar, wolf retro actually aesthetic austin keffiyeh marfa beard. Marfa trust fund salvia sartorial letterpress, keffiyeh plaid butcher. Swag try-hard dreamcatcher direct trade, tacos pickled fanny pack literally meh pinterest slow-carb. Meditation microdosing distillery 8-bit humblebrag migas.
'''
res =
'''
- Beard pinterest actually brunch brooklyn jean shorts YOLO. Knausgaard sriracha
banh mi, cold-pressed retro whatever ethical man braid asymmetrical
fingerstache narwhal. Intelligentsia wolf photo booth, tumblr pinterest quinoa
leggings four loko poutine. DIY tattooed drinking vinegar, wolf retro actually
aesthetic austin keffiyeh marfa beard. Marfa trust fund salvia sartorial
letterpress, keffiyeh plaid butcher. Swag try-hard dreamcatcher direct trade,
tacos pickled fanny pack literally meh pinterest slow-carb. Meditation
microdosing distillery 8-bit humblebrag migas.
'''
expect(autoflow.reflow(text, wrapColumn: 80)).toEqual res
it 'properly reflows % comments ', ->
text =
'''
% Beard pinterest actually brunch brooklyn jean shorts YOLO. Knausgaard sriracha banh mi, cold-pressed retro whatever ethical man braid asymmetrical fingerstache narwhal. Intelligentsia wolf photo booth, tumblr pinterest quinoa leggings four loko poutine. DIY tattooed drinking vinegar, wolf retro actually aesthetic austin keffiyeh marfa beard. Marfa trust fund salvia sartorial letterpress, keffiyeh plaid butcher. Swag try-hard dreamcatcher direct trade, tacos pickled fanny pack literally meh pinterest slow-carb. Meditation microdosing distillery 8-bit humblebrag migas.
'''
res =
'''
% Beard pinterest actually brunch brooklyn jean shorts YOLO. Knausgaard sriracha
% banh mi, cold-pressed retro whatever ethical man braid asymmetrical
% fingerstache narwhal. Intelligentsia wolf photo booth, tumblr pinterest quinoa
% leggings four loko poutine. DIY tattooed drinking vinegar, wolf retro actually
% aesthetic austin keffiyeh marfa beard. Marfa trust fund salvia sartorial
% letterpress, keffiyeh plaid butcher. Swag try-hard dreamcatcher direct trade,
% tacos pickled fanny pack literally meh pinterest slow-carb. Meditation
% microdosing distillery 8-bit humblebrag migas.
'''
expect(autoflow.reflow(text, wrapColumn: 80)).toEqual res
it "properly reflows roxygen comments ", ->
text =
'''
#' Beard pinterest actually brunch brooklyn jean shorts YOLO. Knausgaard sriracha banh mi, cold-pressed retro whatever ethical man braid asymmetrical fingerstache narwhal. Intelligentsia wolf photo booth, tumblr pinterest quinoa leggings four loko poutine. DIY tattooed drinking vinegar, wolf retro actually aesthetic austin keffiyeh marfa beard. Marfa trust fund salvia sartorial letterpress, keffiyeh plaid butcher. Swag try-hard dreamcatcher direct trade, tacos pickled fanny pack literally meh pinterest slow-carb. Meditation microdosing distillery 8-bit humblebrag migas.
'''
res =
'''
#' Beard pinterest actually brunch brooklyn jean shorts YOLO. Knausgaard
#' sriracha banh mi, cold-pressed retro whatever ethical man braid asymmetrical
#' fingerstache narwhal. Intelligentsia wolf photo booth, tumblr pinterest
#' quinoa leggings four loko poutine. DIY tattooed drinking vinegar, wolf retro
#' actually aesthetic austin keffiyeh marfa beard. Marfa trust fund salvia
#' sartorial letterpress, keffiyeh plaid butcher. Swag try-hard dreamcatcher
#' direct trade, tacos pickled fanny pack literally meh pinterest slow-carb.
#' Meditation microdosing distillery 8-bit humblebrag migas.
'''
expect(autoflow.reflow(text, wrapColumn: 80)).toEqual res
it "properly reflows -- comments ", ->
text =
'''
-- Beard pinterest actually brunch brooklyn jean shorts YOLO. Knausgaard sriracha banh mi, cold-pressed retro whatever ethical man braid asymmetrical fingerstache narwhal. Intelligentsia wolf photo booth, tumblr pinterest quinoa leggings four loko poutine. DIY tattooed drinking vinegar, wolf retro actually aesthetic austin keffiyeh marfa beard. Marfa trust fund salvia sartorial letterpress, keffiyeh plaid butcher. Swag try-hard dreamcatcher direct trade, tacos pickled fanny pack literally meh pinterest slow-carb. Meditation microdosing distillery 8-bit humblebrag migas.
'''
res =
'''
-- Beard pinterest actually brunch brooklyn jean shorts YOLO. Knausgaard
-- sriracha banh mi, cold-pressed retro whatever ethical man braid asymmetrical
-- fingerstache narwhal. Intelligentsia wolf photo booth, tumblr pinterest
-- quinoa leggings four loko poutine. DIY tattooed drinking vinegar, wolf retro
-- actually aesthetic austin keffiyeh marfa beard. Marfa trust fund salvia
-- sartorial letterpress, keffiyeh plaid butcher. Swag try-hard dreamcatcher
-- direct trade, tacos pickled fanny pack literally meh pinterest slow-carb.
-- Meditation microdosing distillery 8-bit humblebrag migas.
'''
expect(autoflow.reflow(text, wrapColumn: 80)).toEqual res
it "properly reflows ||| comments ", ->
text =
'''
||| Beard pinterest actually brunch brooklyn jean shorts YOLO. Knausgaard sriracha banh mi, cold-pressed retro whatever ethical man braid asymmetrical fingerstache narwhal. Intelligentsia wolf photo booth, tumblr pinterest quinoa leggings four loko poutine. DIY tattooed drinking vinegar, wolf retro actually aesthetic austin keffiyeh marfa beard. Marfa trust fund salvia sartorial letterpress, keffiyeh plaid butcher. Swag try-hard dreamcatcher direct trade, tacos pickled fanny pack literally meh pinterest slow-carb. Meditation microdosing distillery 8-bit humblebrag migas.
'''
res =
'''
||| Beard pinterest actually brunch brooklyn jean shorts YOLO. Knausgaard
||| sriracha banh mi, cold-pressed retro whatever ethical man braid asymmetrical
||| fingerstache narwhal. Intelligentsia wolf photo booth, tumblr pinterest
||| quinoa leggings four loko poutine. DIY tattooed drinking vinegar, wolf retro
||| actually aesthetic austin keffiyeh marfa beard. Marfa trust fund salvia
||| sartorial letterpress, keffiyeh plaid butcher. Swag try-hard dreamcatcher
||| direct trade, tacos pickled fanny pack literally meh pinterest slow-carb.
||| Meditation microdosing distillery 8-bit humblebrag migas.
'''
expect(autoflow.reflow(text, wrapColumn: 80)).toEqual res
it 'properly reflows ;; comments ', ->
text =
'''
;; Beard pinterest actually brunch brooklyn jean shorts YOLO. Knausgaard sriracha banh mi, cold-pressed retro whatever ethical man braid asymmetrical fingerstache narwhal. Intelligentsia wolf photo booth, tumblr pinterest quinoa leggings four loko poutine. DIY tattooed drinking vinegar, wolf retro actually aesthetic austin keffiyeh marfa beard. Marfa trust fund salvia sartorial letterpress, keffiyeh plaid butcher. Swag try-hard dreamcatcher direct trade, tacos pickled fanny pack literally meh pinterest slow-carb. Meditation microdosing distillery 8-bit humblebrag migas.
'''
res =
'''
;; Beard pinterest actually brunch brooklyn jean shorts YOLO. Knausgaard
;; sriracha banh mi, cold-pressed retro whatever ethical man braid asymmetrical
;; fingerstache narwhal. Intelligentsia wolf photo booth, tumblr pinterest
;; quinoa leggings four loko poutine. DIY tattooed drinking vinegar, wolf retro
;; actually aesthetic austin keffiyeh marfa beard. Marfa trust fund salvia
;; sartorial letterpress, keffiyeh plaid butcher. Swag try-hard dreamcatcher
;; direct trade, tacos pickled fanny pack literally meh pinterest slow-carb.
;; Meditation microdosing distillery 8-bit humblebrag migas.
'''
expect(autoflow.reflow(text, wrapColumn: 80)).toEqual res
it 'does not treat lines starting with a single semicolon as ;; comments', ->
text =
'''
;! Beard pinterest actually brunch brooklyn jean shorts YOLO. Knausgaard sriracha banh mi, cold-pressed retro whatever ethical man braid asymmetrical fingerstache narwhal. Intelligentsia wolf photo booth, tumblr pinterest quinoa leggings four loko poutine. DIY tattooed drinking vinegar, wolf retro actually aesthetic austin keffiyeh marfa beard. Marfa trust fund salvia sartorial letterpress, keffiyeh plaid butcher. Swag try-hard dreamcatcher direct trade, tacos pickled fanny pack literally meh pinterest slow-carb. Meditation microdosing distillery 8-bit humblebrag migas.
'''
res =
'''
;! Beard pinterest actually brunch brooklyn jean shorts YOLO. Knausgaard
sriracha banh mi, cold-pressed retro whatever ethical man braid asymmetrical
fingerstache narwhal. Intelligentsia wolf photo booth, tumblr pinterest quinoa
leggings four loko poutine. DIY tattooed drinking vinegar, wolf retro actually
aesthetic austin keffiyeh marfa beard. Marfa trust fund salvia sartorial
letterpress, keffiyeh plaid butcher. Swag try-hard dreamcatcher direct trade,
tacos pickled fanny pack literally meh pinterest slow-carb. Meditation
microdosing distillery 8-bit humblebrag migas.
'''
expect(autoflow.reflow(text, wrapColumn: 80)).toEqual res
it 'properly reflows > ascii email inclusions ', ->
text =
'''
> Beard pinterest actually brunch brooklyn jean shorts YOLO. Knausgaard sriracha banh mi, cold-pressed retro whatever ethical man braid asymmetrical fingerstache narwhal. Intelligentsia wolf photo booth, tumblr pinterest quinoa leggings four loko poutine. DIY tattooed drinking vinegar, wolf retro actually aesthetic austin keffiyeh marfa beard. Marfa trust fund salvia sartorial letterpress, keffiyeh plaid butcher. Swag try-hard dreamcatcher direct trade, tacos pickled fanny pack literally meh pinterest slow-carb. Meditation microdosing distillery 8-bit humblebrag migas.
'''
res =
'''
> Beard pinterest actually brunch brooklyn jean shorts YOLO. Knausgaard sriracha
> banh mi, cold-pressed retro whatever ethical man braid asymmetrical
> fingerstache narwhal. Intelligentsia wolf photo booth, tumblr pinterest quinoa
> leggings four loko poutine. DIY tattooed drinking vinegar, wolf retro actually
> aesthetic austin keffiyeh marfa beard. Marfa trust fund salvia sartorial
> letterpress, keffiyeh plaid butcher. Swag try-hard dreamcatcher direct trade,
> tacos pickled fanny pack literally meh pinterest slow-carb. Meditation
> microdosing distillery 8-bit humblebrag migas.
'''
expect(autoflow.reflow(text, wrapColumn: 80)).toEqual res
it "doesn't allow special characters to surpass wrapColumn", ->
test =
'''
Imagine that I'm writing some LaTeX code. I start a comment, but change my mind. %
Now I'm just kind of trucking along, doing some math and stuff. For instance, $3 + 4 = 7$. But maybe I'm getting really crazy and I use subtraction. It's kind of an obscure technique, but often it goes a bit like this: let $x = 2 + 2$, so $x - 1 = 3$ (quick maths).
That's OK I guess, but now look at this cool thing called set theory: $\\{n + 42 : n \\in \\mathbb{N}\\}$. Wow. Neat. But we all know why we're really here. If you peer deep down into your heart, and you stare into the depths of yourself: is $P = NP$? Beware, though; many have tried and failed to answer this question. It is by no means for the faint of heart.
'''
res =
'''
Imagine that I'm writing some LaTeX code. I start a comment, but change my mind.
%
Now I'm just kind of trucking along, doing some math and stuff. For instance, $3
+ 4 = 7$. But maybe I'm getting really crazy and I use subtraction. It's kind of
an obscure technique, but often it goes a bit like this: let $x = 2 + 2$, so $x
- 1 = 3$ (quick maths).
That's OK I guess, but now look at this cool thing called set theory: $\\{n + 42
: n \\in \\mathbb{N}\\}$. Wow. Neat. But we all know why we're really here. If you
peer deep down into your heart, and you stare into the depths of yourself: is $P
= NP$? Beware, though; many have tried and failed to answer this question. It is
by no means for the faint of heart.
'''
expect(autoflow.reflow(test, wrapColumn: 80)).toEqual res
describe 'LaTeX', ->
it 'properly reflows text around LaTeX tags', ->
text =
'''
\\begin{verbatim}
Lorem ipsum dolor sit amet, nisl odio amet, et tempor netus neque at at blandit, vel vestibulum libero dolor, semper lobortis ligula praesent. Eget condimentum integer, porta sagittis nam, fusce vitae a vitae augue. Nec semper quis sed ut, est porttitor praesent. Nisl velit quam dolore velit quam, elementum neque pellentesque pulvinar et vestibulum.
\\end{verbatim}
'''
res =
'''
\\begin{verbatim}
Lorem ipsum dolor sit amet, nisl odio amet, et tempor netus neque at at
blandit, vel vestibulum libero dolor, semper lobortis ligula praesent. Eget
condimentum integer, porta sagittis nam, fusce vitae a vitae augue. Nec
semper quis sed ut, est porttitor praesent. Nisl velit quam dolore velit
quam, elementum neque pellentesque pulvinar et vestibulum.
\\end{verbatim}
'''
expect(autoflow.reflow(text, wrapColumn: 80)).toEqual res
it 'properly reflows text inside LaTeX tags', ->
text =
'''
\\item{
Lorem ipsum dolor sit amet, nisl odio amet, et tempor netus neque at at blandit, vel vestibulum libero dolor, semper lobortis ligula praesent. Eget condimentum integer, porta sagittis nam, fusce vitae a vitae augue. Nec semper quis sed ut, est porttitor praesent. Nisl velit quam dolore velit quam, elementum neque pellentesque pulvinar et vestibulum.
}
'''
res =
'''
\\item{
Lorem ipsum dolor sit amet, nisl odio amet, et tempor netus neque at at
blandit, vel vestibulum libero dolor, semper lobortis ligula praesent. Eget
condimentum integer, porta sagittis nam, fusce vitae a vitae augue. Nec
semper quis sed ut, est porttitor praesent. Nisl velit quam dolore velit
quam, elementum neque pellentesque pulvinar et vestibulum.
}
'''
expect(autoflow.reflow(text, wrapColumn: 80)).toEqual res
it 'properly reflows text inside nested LaTeX tags', ->
text =
'''
\\begin{enumerate}[label=(\\alph*)]
\\item{
Lorem ipsum dolor sit amet, nisl odio amet, et tempor netus neque at at blandit, vel vestibulum libero dolor, semper lobortis ligula praesent. Eget condimentum integer, porta sagittis nam, fusce vitae a vitae augue. Nec semper quis sed ut, est porttitor praesent. Nisl velit quam dolore velit quam, elementum neque pellentesque pulvinar et vestibulum.
}
\\end{enumerate}
'''
res =
'''
\\begin{enumerate}[label=(\\alph*)]
\\item{
Lorem ipsum dolor sit amet, nisl odio amet, et tempor netus neque at at
blandit, vel vestibulum libero dolor, semper lobortis ligula praesent.
Eget condimentum integer, porta sagittis nam, fusce vitae a vitae augue.
Nec semper quis sed ut, est porttitor praesent. Nisl velit quam dolore
velit quam, elementum neque pellentesque pulvinar et vestibulum.
}
\\end{enumerate}
'''
expect(autoflow.reflow(text, wrapColumn: 80)).toEqual res
it 'does not attempt to reflow a selection that contains only LaTeX tags and nothing else', ->
text =
'''
\\begin{enumerate}
\\end{enumerate}
'''
expect(autoflow.reflow(text, wrapColumn: 5)).toEqual text

View File

@ -0,0 +1,683 @@
describe("Autoflow package", () => {
let [autoflow, editor, editorElement] = [];
const tabLength = 4;
describe("autoflow:reflow-selection", () => {
beforeEach(() => {
let activationPromise = null;
waitsForPromise(() => atom.workspace.open());
runs(() => {
editor = atom.workspace.getActiveTextEditor();
editorElement = atom.views.getView(editor);
atom.config.set('editor.preferredLineLength', 30);
atom.config.set('editor.tabLength', tabLength);
activationPromise = atom.packages.activatePackage('autoflow');
atom.commands.dispatch(editorElement, 'autoflow:reflow-selection');
});
waitsForPromise(() => activationPromise);
});
it("uses the preferred line length based on the editor's scope", () => {
atom.config.set('editor.preferredLineLength', 4, {scopeSelector: '.text.plain.null-grammar'});
editor.setText("foo bar");
editor.selectAll();
atom.commands.dispatch(editorElement, 'autoflow:reflow-selection');
expect(editor.getText()).toBe(`\
foo
bar\
`
);
});
it("rearranges line breaks in the current selection to ensure lines are shorter than config.editor.preferredLineLength honoring tabLength", () => {
editor.setText("\t\tThis is the first paragraph and it is longer than the preferred line length so it should be reflowed.\n\n\t\tThis is a short paragraph.\n\n\t\tAnother long paragraph, it should also be reflowed with the use of this single command.");
editor.selectAll();
atom.commands.dispatch(editorElement, 'autoflow:reflow-selection');
const exedOut = editor.getText().replace(/\t/g, Array(tabLength+1).join('X'));
expect(exedOut).toBe("XXXXXXXXThis is the first\nXXXXXXXXparagraph and it is\nXXXXXXXXlonger than the\nXXXXXXXXpreferred line length\nXXXXXXXXso it should be\nXXXXXXXXreflowed.\n\nXXXXXXXXThis is a short\nXXXXXXXXparagraph.\n\nXXXXXXXXAnother long\nXXXXXXXXparagraph, it should\nXXXXXXXXalso be reflowed with\nXXXXXXXXthe use of this single\nXXXXXXXXcommand.");
});
it("rearranges line breaks in the current selection to ensure lines are shorter than config.editor.preferredLineLength", () => {
editor.setText(`\
This is the first paragraph and it is longer than the preferred line length so it should be reflowed.
This is a short paragraph.
Another long paragraph, it should also be reflowed with the use of this single command.\
`
);
editor.selectAll();
atom.commands.dispatch(editorElement, 'autoflow:reflow-selection');
expect(editor.getText()).toBe(`\
This is the first paragraph
and it is longer than the
preferred line length so it
should be reflowed.
This is a short paragraph.
Another long paragraph, it
should also be reflowed with
the use of this single
command.\
`
);
});
it("is not confused when the selection boundary is between paragraphs", () => {
editor.setText(`\
v--- SELECTION STARTS AT THE BEGINNING OF THE NEXT LINE (pos 1,0)
The preceding newline should not be considered part of this paragraph.
The newline at the end of this paragraph should be preserved and not
converted into a space.
^--- SELECTION ENDS AT THE BEGINNING OF THE PREVIOUS LINE (pos 6,0)\
`
);
editor.setCursorBufferPosition([1, 0]);
editor.selectToBufferPosition([6, 0]);
atom.commands.dispatch(editorElement, 'autoflow:reflow-selection');
expect(editor.getText()).toBe(`\
v--- SELECTION STARTS AT THE BEGINNING OF THE NEXT LINE (pos 1,0)
The preceding newline should
not be considered part of this
paragraph.
The newline at the end of this
paragraph should be preserved
and not converted into a
space.
^--- SELECTION ENDS AT THE BEGINNING OF THE PREVIOUS LINE (pos 6,0)\
`
);
});
it("reflows the current paragraph if nothing is selected", () => {
editor.setText(`\
This is a preceding paragraph, which shouldn't be modified by a reflow of the following paragraph.
The quick brown fox jumps over the lazy
dog. The preceding sentence contains every letter
in the entire English alphabet, which has absolutely no relevance
to this test.
This is a following paragraph, which shouldn't be modified by a reflow of the preciding paragraph.
\
`
);
editor.setCursorBufferPosition([3, 5]);
atom.commands.dispatch(editorElement, 'autoflow:reflow-selection');
expect(editor.getText()).toBe(`\
This is a preceding paragraph, which shouldn't be modified by a reflow of the following paragraph.
The quick brown fox jumps over
the lazy dog. The preceding
sentence contains every letter
in the entire English
alphabet, which has absolutely
no relevance to this test.
This is a following paragraph, which shouldn't be modified by a reflow of the preciding paragraph.
\
`
);
});
it("allows for single words that exceed the preferred wrap column length", () => {
editor.setText("this-is-a-super-long-word-that-shouldn't-break-autoflow and these are some smaller words");
editor.selectAll();
atom.commands.dispatch(editorElement, 'autoflow:reflow-selection');
expect(editor.getText()).toBe(`\
this-is-a-super-long-word-that-shouldn't-break-autoflow
and these are some smaller
words\
`
);
});
});
describe("reflowing text", () => {
beforeEach(() => autoflow = require("../lib/autoflow"));
it('respects current paragraphs', () => {
const text = `\
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus gravida nibh id magna ullamcorper sagittis. Maecenas
et enim eu orci tincidunt adipiscing
aliquam ligula.
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Phasellus gravida
nibh id magna ullamcorper
tincidunt adipiscing lacinia a dui. Etiam quis erat dolor.
rutrum nisl fermentum rhoncus. Duis blandit ligula facilisis fermentum.\
`;
const res = `\
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus gravida nibh
id magna ullamcorper sagittis. Maecenas et enim eu orci tincidunt adipiscing
aliquam ligula.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus gravida nibh
id magna ullamcorper tincidunt adipiscing lacinia a dui. Etiam quis erat dolor.
rutrum nisl fermentum rhoncus. Duis blandit ligula facilisis fermentum.\
`;
expect(autoflow.reflow(text, {wrapColumn: 80})).toEqual(res);
});
it('respects indentation', () => {
const text = `\
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus gravida nibh id magna ullamcorper sagittis. Maecenas
et enim eu orci tincidunt adipiscing
aliquam ligula.
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Phasellus gravida
nibh id magna ullamcorper
tincidunt adipiscing lacinia a dui. Etiam quis erat dolor.
rutrum nisl fermentum rhoncus. Duis blandit ligula facilisis fermentum\
`;
const res = `\
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus gravida nibh
id magna ullamcorper sagittis. Maecenas et enim eu orci tincidunt adipiscing
aliquam ligula.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus gravida
nibh id magna ullamcorper tincidunt adipiscing lacinia a dui. Etiam quis
erat dolor. rutrum nisl fermentum rhoncus. Duis blandit ligula facilisis
fermentum\
`;
expect(autoflow.reflow(text, {wrapColumn: 80})).toEqual(res);
});
it('respects prefixed text (comments!)', () => {
const text = `\
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus gravida nibh id magna ullamcorper sagittis. Maecenas
et enim eu orci tincidunt adipiscing
aliquam ligula.
# Lorem ipsum dolor sit amet, consectetur adipiscing elit.
# Phasellus gravida
# nibh id magna ullamcorper
# tincidunt adipiscing lacinia a dui. Etiam quis erat dolor.
# rutrum nisl fermentum rhoncus. Duis blandit ligula facilisis fermentum\
`;
const res = `\
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus gravida nibh
id magna ullamcorper sagittis. Maecenas et enim eu orci tincidunt adipiscing
aliquam ligula.
# Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus gravida
# nibh id magna ullamcorper tincidunt adipiscing lacinia a dui. Etiam quis
# erat dolor. rutrum nisl fermentum rhoncus. Duis blandit ligula facilisis
# fermentum\
`;
expect(autoflow.reflow(text, {wrapColumn: 80})).toEqual(res);
});
it('respects multiple prefixes (js/c comments)', () => {
const text = `\
// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus gravida
et enim eu orci tincidunt adipiscing
aliquam ligula.\
`;
const res = `\
// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus gravida et
// enim eu orci tincidunt adipiscing aliquam ligula.\
`;
expect(autoflow.reflow(text, {wrapColumn: 80})).toEqual(res);
});
it('properly handles * prefix', () => {
const text = `\
* Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus gravida
et enim eu orci tincidunt adipiscing
aliquam ligula.
* soidjfiojsoidj foi\
`;
const res = `\
* Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus gravida et
* enim eu orci tincidunt adipiscing aliquam ligula.
* soidjfiojsoidj foi\
`;
expect(autoflow.reflow(text, {wrapColumn: 80})).toEqual(res);
});
it("does not throw invalid regular expression errors (regression)", () => {
const text = `\
*** Lorem ipsum dolor sit amet\
`;
expect(autoflow.reflow(text, {wrapColumn: 80})).toEqual(text);
});
it('handles different initial indentation', () => {
const text = `\
Magna ea magna fugiat nisi minim in id duis. Culpa sit sint consequat quis elit magna pariatur incididunt
proident laborum deserunt est aliqua reprehenderit. Occaecat et ex non do Lorem irure adipisicing mollit excepteur
eu ullamco consectetur. Ex ex Lorem duis labore quis ad exercitation elit dolor non adipisicing. Pariatur commodo ullamco
culpa dolor sunt enim. Ullamco dolore do ea nulla ut commodo minim consequat cillum ad velit quis.\
`;
const res = `\
Magna ea magna fugiat nisi minim in id duis. Culpa sit sint consequat quis elit
magna pariatur incididunt proident laborum deserunt est aliqua reprehenderit.
Occaecat et ex non do Lorem irure adipisicing mollit excepteur eu ullamco
consectetur. Ex ex Lorem duis labore quis ad exercitation elit dolor non
adipisicing. Pariatur commodo ullamco culpa dolor sunt enim. Ullamco dolore do
ea nulla ut commodo minim consequat cillum ad velit quis.\
`;
expect(autoflow.reflow(text, {wrapColumn: 80})).toEqual(res);
});
it('properly handles CRLF', () => {
const text = "This is the first line and it is longer than the preferred line length so it should be reflowed.\r\nThis is a short line which should\r\nbe reflowed with the following line.\rAnother long line, it should also be reflowed with everything above it when it is all reflowed.";
const res =
`\
This is the first line and it is longer than the preferred line length so it
should be reflowed. This is a short line which should be reflowed with the
following line. Another long line, it should also be reflowed with everything
above it when it is all reflowed.\
`;
expect(autoflow.reflow(text, {wrapColumn: 80})).toEqual(res);
});
it('handles cyrillic text', () => {
const text = `\
В начале июля, в чрезвычайно жаркое время, под вечер, один молодой человек вышел из своей каморки, которую нанимал от жильцов в С-м переулке, на улицу и медленно, как бы в нерешимости, отправился к К-ну мосту.\
`;
const res = `\
В начале июля, в чрезвычайно жаркое время, под вечер, один молодой человек вышел
из своей каморки, которую нанимал от жильцов в С-м переулке, на улицу и
медленно, как бы в нерешимости, отправился к К-ну мосту.\
`;
expect(autoflow.reflow(text, {wrapColumn: 80})).toEqual(res);
});
it('handles `yo` character properly', () => {
// Because there're known problems with this character in major regex engines
const text = 'Ё Ё Ё';
const res = `\
Ё
Ё
Ё\
`;
expect(autoflow.reflow(text, {wrapColumn: 2})).toEqual(res);
});
it('properly reflows // comments ', () => {
const text =
`\
// Beard pinterest actually brunch brooklyn jean shorts YOLO. Knausgaard sriracha banh mi, cold-pressed retro whatever ethical man braid asymmetrical fingerstache narwhal. Intelligentsia wolf photo booth, tumblr pinterest quinoa leggings four loko poutine. DIY tattooed drinking vinegar, wolf retro actually aesthetic austin keffiyeh marfa beard. Marfa trust fund salvia sartorial letterpress, keffiyeh plaid butcher. Swag try-hard dreamcatcher direct trade, tacos pickled fanny pack literally meh pinterest slow-carb. Meditation microdosing distillery 8-bit humblebrag migas.\
`;
const res =
`\
// Beard pinterest actually brunch brooklyn jean shorts YOLO. Knausgaard
// sriracha banh mi, cold-pressed retro whatever ethical man braid asymmetrical
// fingerstache narwhal. Intelligentsia wolf photo booth, tumblr pinterest
// quinoa leggings four loko poutine. DIY tattooed drinking vinegar, wolf retro
// actually aesthetic austin keffiyeh marfa beard. Marfa trust fund salvia
// sartorial letterpress, keffiyeh plaid butcher. Swag try-hard dreamcatcher
// direct trade, tacos pickled fanny pack literally meh pinterest slow-carb.
// Meditation microdosing distillery 8-bit humblebrag migas.\
`;
expect(autoflow.reflow(text, {wrapColumn: 80})).toEqual(res);
});
it('properly reflows /* comments ', () => {
const text =
`\
/* Beard pinterest actually brunch brooklyn jean shorts YOLO. Knausgaard sriracha banh mi, cold-pressed retro whatever ethical man braid asymmetrical fingerstache narwhal. Intelligentsia wolf photo booth, tumblr pinterest quinoa leggings four loko poutine. DIY tattooed drinking vinegar, wolf retro actually aesthetic austin keffiyeh marfa beard. Marfa trust fund salvia sartorial letterpress, keffiyeh plaid butcher. Swag try-hard dreamcatcher direct trade, tacos pickled fanny pack literally meh pinterest slow-carb. Meditation microdosing distillery 8-bit humblebrag migas. */\
`;
const res =
`\
/* Beard pinterest actually brunch brooklyn jean shorts YOLO. Knausgaard
sriracha banh mi, cold-pressed retro whatever ethical man braid asymmetrical
fingerstache narwhal. Intelligentsia wolf photo booth, tumblr pinterest
quinoa leggings four loko poutine. DIY tattooed drinking vinegar, wolf retro
actually aesthetic austin keffiyeh marfa beard. Marfa trust fund salvia
sartorial letterpress, keffiyeh plaid butcher. Swag try-hard dreamcatcher
direct trade, tacos pickled fanny pack literally meh pinterest slow-carb.
Meditation microdosing distillery 8-bit humblebrag migas. */\
`;
expect(autoflow.reflow(text, {wrapColumn: 80})).toEqual(res);
});
it('properly reflows pound comments ', () => {
const text =
`\
# Beard pinterest actually brunch brooklyn jean shorts YOLO. Knausgaard sriracha banh mi, cold-pressed retro whatever ethical man braid asymmetrical fingerstache narwhal. Intelligentsia wolf photo booth, tumblr pinterest quinoa leggings four loko poutine. DIY tattooed drinking vinegar, wolf retro actually aesthetic austin keffiyeh marfa beard. Marfa trust fund salvia sartorial letterpress, keffiyeh plaid butcher. Swag try-hard dreamcatcher direct trade, tacos pickled fanny pack literally meh pinterest slow-carb. Meditation microdosing distillery 8-bit humblebrag migas.\
`;
const res =
`\
# Beard pinterest actually brunch brooklyn jean shorts YOLO. Knausgaard sriracha
# banh mi, cold-pressed retro whatever ethical man braid asymmetrical
# fingerstache narwhal. Intelligentsia wolf photo booth, tumblr pinterest quinoa
# leggings four loko poutine. DIY tattooed drinking vinegar, wolf retro actually
# aesthetic austin keffiyeh marfa beard. Marfa trust fund salvia sartorial
# letterpress, keffiyeh plaid butcher. Swag try-hard dreamcatcher direct trade,
# tacos pickled fanny pack literally meh pinterest slow-carb. Meditation
# microdosing distillery 8-bit humblebrag migas.\
`;
expect(autoflow.reflow(text, {wrapColumn: 80})).toEqual(res);
});
it('properly reflows - list items ', () => {
const text =
`\
- Beard pinterest actually brunch brooklyn jean shorts YOLO. Knausgaard sriracha banh mi, cold-pressed retro whatever ethical man braid asymmetrical fingerstache narwhal. Intelligentsia wolf photo booth, tumblr pinterest quinoa leggings four loko poutine. DIY tattooed drinking vinegar, wolf retro actually aesthetic austin keffiyeh marfa beard. Marfa trust fund salvia sartorial letterpress, keffiyeh plaid butcher. Swag try-hard dreamcatcher direct trade, tacos pickled fanny pack literally meh pinterest slow-carb. Meditation microdosing distillery 8-bit humblebrag migas.\
`;
const res =
`\
- Beard pinterest actually brunch brooklyn jean shorts YOLO. Knausgaard sriracha
banh mi, cold-pressed retro whatever ethical man braid asymmetrical
fingerstache narwhal. Intelligentsia wolf photo booth, tumblr pinterest quinoa
leggings four loko poutine. DIY tattooed drinking vinegar, wolf retro actually
aesthetic austin keffiyeh marfa beard. Marfa trust fund salvia sartorial
letterpress, keffiyeh plaid butcher. Swag try-hard dreamcatcher direct trade,
tacos pickled fanny pack literally meh pinterest slow-carb. Meditation
microdosing distillery 8-bit humblebrag migas.\
`;
expect(autoflow.reflow(text, {wrapColumn: 80})).toEqual(res);
});
it('properly reflows % comments ', () => {
const text =
`\
% Beard pinterest actually brunch brooklyn jean shorts YOLO. Knausgaard sriracha banh mi, cold-pressed retro whatever ethical man braid asymmetrical fingerstache narwhal. Intelligentsia wolf photo booth, tumblr pinterest quinoa leggings four loko poutine. DIY tattooed drinking vinegar, wolf retro actually aesthetic austin keffiyeh marfa beard. Marfa trust fund salvia sartorial letterpress, keffiyeh plaid butcher. Swag try-hard dreamcatcher direct trade, tacos pickled fanny pack literally meh pinterest slow-carb. Meditation microdosing distillery 8-bit humblebrag migas.\
`;
const res =
`\
% Beard pinterest actually brunch brooklyn jean shorts YOLO. Knausgaard sriracha
% banh mi, cold-pressed retro whatever ethical man braid asymmetrical
% fingerstache narwhal. Intelligentsia wolf photo booth, tumblr pinterest quinoa
% leggings four loko poutine. DIY tattooed drinking vinegar, wolf retro actually
% aesthetic austin keffiyeh marfa beard. Marfa trust fund salvia sartorial
% letterpress, keffiyeh plaid butcher. Swag try-hard dreamcatcher direct trade,
% tacos pickled fanny pack literally meh pinterest slow-carb. Meditation
% microdosing distillery 8-bit humblebrag migas.\
`;
expect(autoflow.reflow(text, {wrapColumn: 80})).toEqual(res);
});
it("properly reflows roxygen comments ", () => {
const text =
`\
#' Beard pinterest actually brunch brooklyn jean shorts YOLO. Knausgaard sriracha banh mi, cold-pressed retro whatever ethical man braid asymmetrical fingerstache narwhal. Intelligentsia wolf photo booth, tumblr pinterest quinoa leggings four loko poutine. DIY tattooed drinking vinegar, wolf retro actually aesthetic austin keffiyeh marfa beard. Marfa trust fund salvia sartorial letterpress, keffiyeh plaid butcher. Swag try-hard dreamcatcher direct trade, tacos pickled fanny pack literally meh pinterest slow-carb. Meditation microdosing distillery 8-bit humblebrag migas.\
`;
const res =
`\
#' Beard pinterest actually brunch brooklyn jean shorts YOLO. Knausgaard
#' sriracha banh mi, cold-pressed retro whatever ethical man braid asymmetrical
#' fingerstache narwhal. Intelligentsia wolf photo booth, tumblr pinterest
#' quinoa leggings four loko poutine. DIY tattooed drinking vinegar, wolf retro
#' actually aesthetic austin keffiyeh marfa beard. Marfa trust fund salvia
#' sartorial letterpress, keffiyeh plaid butcher. Swag try-hard dreamcatcher
#' direct trade, tacos pickled fanny pack literally meh pinterest slow-carb.
#' Meditation microdosing distillery 8-bit humblebrag migas.\
`;
expect(autoflow.reflow(text, {wrapColumn: 80})).toEqual(res);
});
it("properly reflows -- comments ", () => {
const text =
`\
-- Beard pinterest actually brunch brooklyn jean shorts YOLO. Knausgaard sriracha banh mi, cold-pressed retro whatever ethical man braid asymmetrical fingerstache narwhal. Intelligentsia wolf photo booth, tumblr pinterest quinoa leggings four loko poutine. DIY tattooed drinking vinegar, wolf retro actually aesthetic austin keffiyeh marfa beard. Marfa trust fund salvia sartorial letterpress, keffiyeh plaid butcher. Swag try-hard dreamcatcher direct trade, tacos pickled fanny pack literally meh pinterest slow-carb. Meditation microdosing distillery 8-bit humblebrag migas.\
`;
const res =
`\
-- Beard pinterest actually brunch brooklyn jean shorts YOLO. Knausgaard
-- sriracha banh mi, cold-pressed retro whatever ethical man braid asymmetrical
-- fingerstache narwhal. Intelligentsia wolf photo booth, tumblr pinterest
-- quinoa leggings four loko poutine. DIY tattooed drinking vinegar, wolf retro
-- actually aesthetic austin keffiyeh marfa beard. Marfa trust fund salvia
-- sartorial letterpress, keffiyeh plaid butcher. Swag try-hard dreamcatcher
-- direct trade, tacos pickled fanny pack literally meh pinterest slow-carb.
-- Meditation microdosing distillery 8-bit humblebrag migas.\
`;
expect(autoflow.reflow(text, {wrapColumn: 80})).toEqual(res);
});
it("properly reflows ||| comments ", () => {
const text =
`\
||| Beard pinterest actually brunch brooklyn jean shorts YOLO. Knausgaard sriracha banh mi, cold-pressed retro whatever ethical man braid asymmetrical fingerstache narwhal. Intelligentsia wolf photo booth, tumblr pinterest quinoa leggings four loko poutine. DIY tattooed drinking vinegar, wolf retro actually aesthetic austin keffiyeh marfa beard. Marfa trust fund salvia sartorial letterpress, keffiyeh plaid butcher. Swag try-hard dreamcatcher direct trade, tacos pickled fanny pack literally meh pinterest slow-carb. Meditation microdosing distillery 8-bit humblebrag migas.\
`;
const res =
`\
||| Beard pinterest actually brunch brooklyn jean shorts YOLO. Knausgaard
||| sriracha banh mi, cold-pressed retro whatever ethical man braid asymmetrical
||| fingerstache narwhal. Intelligentsia wolf photo booth, tumblr pinterest
||| quinoa leggings four loko poutine. DIY tattooed drinking vinegar, wolf retro
||| actually aesthetic austin keffiyeh marfa beard. Marfa trust fund salvia
||| sartorial letterpress, keffiyeh plaid butcher. Swag try-hard dreamcatcher
||| direct trade, tacos pickled fanny pack literally meh pinterest slow-carb.
||| Meditation microdosing distillery 8-bit humblebrag migas.\
`;
expect(autoflow.reflow(text, {wrapColumn: 80})).toEqual(res);
});
it('properly reflows ;; comments ', () => {
const text =
`\
;; Beard pinterest actually brunch brooklyn jean shorts YOLO. Knausgaard sriracha banh mi, cold-pressed retro whatever ethical man braid asymmetrical fingerstache narwhal. Intelligentsia wolf photo booth, tumblr pinterest quinoa leggings four loko poutine. DIY tattooed drinking vinegar, wolf retro actually aesthetic austin keffiyeh marfa beard. Marfa trust fund salvia sartorial letterpress, keffiyeh plaid butcher. Swag try-hard dreamcatcher direct trade, tacos pickled fanny pack literally meh pinterest slow-carb. Meditation microdosing distillery 8-bit humblebrag migas.\
`;
const res =
`\
;; Beard pinterest actually brunch brooklyn jean shorts YOLO. Knausgaard
;; sriracha banh mi, cold-pressed retro whatever ethical man braid asymmetrical
;; fingerstache narwhal. Intelligentsia wolf photo booth, tumblr pinterest
;; quinoa leggings four loko poutine. DIY tattooed drinking vinegar, wolf retro
;; actually aesthetic austin keffiyeh marfa beard. Marfa trust fund salvia
;; sartorial letterpress, keffiyeh plaid butcher. Swag try-hard dreamcatcher
;; direct trade, tacos pickled fanny pack literally meh pinterest slow-carb.
;; Meditation microdosing distillery 8-bit humblebrag migas.\
`;
expect(autoflow.reflow(text, {wrapColumn: 80})).toEqual(res);
});
it('does not treat lines starting with a single semicolon as ;; comments', () => {
const text =
`\
;! Beard pinterest actually brunch brooklyn jean shorts YOLO. Knausgaard sriracha banh mi, cold-pressed retro whatever ethical man braid asymmetrical fingerstache narwhal. Intelligentsia wolf photo booth, tumblr pinterest quinoa leggings four loko poutine. DIY tattooed drinking vinegar, wolf retro actually aesthetic austin keffiyeh marfa beard. Marfa trust fund salvia sartorial letterpress, keffiyeh plaid butcher. Swag try-hard dreamcatcher direct trade, tacos pickled fanny pack literally meh pinterest slow-carb. Meditation microdosing distillery 8-bit humblebrag migas.\
`;
const res =
`\
;! Beard pinterest actually brunch brooklyn jean shorts YOLO. Knausgaard
sriracha banh mi, cold-pressed retro whatever ethical man braid asymmetrical
fingerstache narwhal. Intelligentsia wolf photo booth, tumblr pinterest quinoa
leggings four loko poutine. DIY tattooed drinking vinegar, wolf retro actually
aesthetic austin keffiyeh marfa beard. Marfa trust fund salvia sartorial
letterpress, keffiyeh plaid butcher. Swag try-hard dreamcatcher direct trade,
tacos pickled fanny pack literally meh pinterest slow-carb. Meditation
microdosing distillery 8-bit humblebrag migas.\
`;
expect(autoflow.reflow(text, {wrapColumn: 80})).toEqual(res);
});
it('properly reflows > ascii email inclusions ', () => {
const text =
`\
> Beard pinterest actually brunch brooklyn jean shorts YOLO. Knausgaard sriracha banh mi, cold-pressed retro whatever ethical man braid asymmetrical fingerstache narwhal. Intelligentsia wolf photo booth, tumblr pinterest quinoa leggings four loko poutine. DIY tattooed drinking vinegar, wolf retro actually aesthetic austin keffiyeh marfa beard. Marfa trust fund salvia sartorial letterpress, keffiyeh plaid butcher. Swag try-hard dreamcatcher direct trade, tacos pickled fanny pack literally meh pinterest slow-carb. Meditation microdosing distillery 8-bit humblebrag migas.\
`;
const res =
`\
> Beard pinterest actually brunch brooklyn jean shorts YOLO. Knausgaard sriracha
> banh mi, cold-pressed retro whatever ethical man braid asymmetrical
> fingerstache narwhal. Intelligentsia wolf photo booth, tumblr pinterest quinoa
> leggings four loko poutine. DIY tattooed drinking vinegar, wolf retro actually
> aesthetic austin keffiyeh marfa beard. Marfa trust fund salvia sartorial
> letterpress, keffiyeh plaid butcher. Swag try-hard dreamcatcher direct trade,
> tacos pickled fanny pack literally meh pinterest slow-carb. Meditation
> microdosing distillery 8-bit humblebrag migas.\
`;
expect(autoflow.reflow(text, {wrapColumn: 80})).toEqual(res);
});
it("doesn't allow special characters to surpass wrapColumn", () => {
const test =
`\
Imagine that I'm writing some LaTeX code. I start a comment, but change my mind. %
Now I'm just kind of trucking along, doing some math and stuff. For instance, $3 + 4 = 7$. But maybe I'm getting really crazy and I use subtraction. It's kind of an obscure technique, but often it goes a bit like this: let $x = 2 + 2$, so $x - 1 = 3$ (quick maths).
That's OK I guess, but now look at this cool thing called set theory: $\\{n + 42 : n \\in \\mathbb{N}\\}$. Wow. Neat. But we all know why we're really here. If you peer deep down into your heart, and you stare into the depths of yourself: is $P = NP$? Beware, though; many have tried and failed to answer this question. It is by no means for the faint of heart.\
`;
const res =
`\
Imagine that I'm writing some LaTeX code. I start a comment, but change my mind.
%
Now I'm just kind of trucking along, doing some math and stuff. For instance, $3
+ 4 = 7$. But maybe I'm getting really crazy and I use subtraction. It's kind of
an obscure technique, but often it goes a bit like this: let $x = 2 + 2$, so $x
- 1 = 3$ (quick maths).
That's OK I guess, but now look at this cool thing called set theory: $\\{n + 42
: n \\in \\mathbb{N}\\}$. Wow. Neat. But we all know why we're really here. If you
peer deep down into your heart, and you stare into the depths of yourself: is $P
= NP$? Beware, though; many have tried and failed to answer this question. It is
by no means for the faint of heart.\
`;
expect(autoflow.reflow(test, {wrapColumn: 80})).toEqual(res);
});
describe('LaTeX', () => {
it('properly reflows text around LaTeX tags', () => {
const text =
`\
\\begin{verbatim}
Lorem ipsum dolor sit amet, nisl odio amet, et tempor netus neque at at blandit, vel vestibulum libero dolor, semper lobortis ligula praesent. Eget condimentum integer, porta sagittis nam, fusce vitae a vitae augue. Nec semper quis sed ut, est porttitor praesent. Nisl velit quam dolore velit quam, elementum neque pellentesque pulvinar et vestibulum.
\\end{verbatim}\
`;
const res =
`\
\\begin{verbatim}
Lorem ipsum dolor sit amet, nisl odio amet, et tempor netus neque at at
blandit, vel vestibulum libero dolor, semper lobortis ligula praesent. Eget
condimentum integer, porta sagittis nam, fusce vitae a vitae augue. Nec
semper quis sed ut, est porttitor praesent. Nisl velit quam dolore velit
quam, elementum neque pellentesque pulvinar et vestibulum.
\\end{verbatim}\
`;
expect(autoflow.reflow(text, {wrapColumn: 80})).toEqual(res);
});
it('properly reflows text inside LaTeX tags', () => {
const text =
`\
\\item{
Lorem ipsum dolor sit amet, nisl odio amet, et tempor netus neque at at blandit, vel vestibulum libero dolor, semper lobortis ligula praesent. Eget condimentum integer, porta sagittis nam, fusce vitae a vitae augue. Nec semper quis sed ut, est porttitor praesent. Nisl velit quam dolore velit quam, elementum neque pellentesque pulvinar et vestibulum.
}\
`;
const res =
`\
\\item{
Lorem ipsum dolor sit amet, nisl odio amet, et tempor netus neque at at
blandit, vel vestibulum libero dolor, semper lobortis ligula praesent. Eget
condimentum integer, porta sagittis nam, fusce vitae a vitae augue. Nec
semper quis sed ut, est porttitor praesent. Nisl velit quam dolore velit
quam, elementum neque pellentesque pulvinar et vestibulum.
}\
`;
expect(autoflow.reflow(text, {wrapColumn: 80})).toEqual(res);
});
it('properly reflows text inside nested LaTeX tags', () => {
const text =
`\
\\begin{enumerate}[label=(\\alph*)]
\\item{
Lorem ipsum dolor sit amet, nisl odio amet, et tempor netus neque at at blandit, vel vestibulum libero dolor, semper lobortis ligula praesent. Eget condimentum integer, porta sagittis nam, fusce vitae a vitae augue. Nec semper quis sed ut, est porttitor praesent. Nisl velit quam dolore velit quam, elementum neque pellentesque pulvinar et vestibulum.
}
\\end{enumerate}\
`;
const res =
`\
\\begin{enumerate}[label=(\\alph*)]
\\item{
Lorem ipsum dolor sit amet, nisl odio amet, et tempor netus neque at at
blandit, vel vestibulum libero dolor, semper lobortis ligula praesent.
Eget condimentum integer, porta sagittis nam, fusce vitae a vitae augue.
Nec semper quis sed ut, est porttitor praesent. Nisl velit quam dolore
velit quam, elementum neque pellentesque pulvinar et vestibulum.
}
\\end{enumerate}\
`;
expect(autoflow.reflow(text, {wrapColumn: 80})).toEqual(res);
});
it('does not attempt to reflow a selection that contains only LaTeX tags and nothing else', () => {
const text =
`\
\\begin{enumerate}
\\end{enumerate}\
`;
expect(autoflow.reflow(text, {wrapColumn: 5})).toEqual(text);
});
});
});
});

1
packages/autosave/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
node_modules/

View File

@ -0,0 +1,20 @@
Copyright (c) 2014 GitHub Inc.
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,32 @@
# Autosave package
Autosaves editor when they lose focus, are destroyed, or when the window is closed.
This package is disabled by default and can be enabled via the `autosave.enabled` config
setting or by checking *Enabled* in the settings for the *autosave* package in the
Settings view.
## Service API
The service exposes an object with a function `dontSaveIf`, which accepts a callback.
Callbacks will be invoked with each pane item eligible for an autosave and if the callback
returns true, the item will be skipped.
### Usage
#### package.json
``` json
"consumedServices": {
"autosave": {
"versions": {
"1.0.0": "consumeAutosave"
}
}
}
```
#### package initialize
``` javascript
consumeAutosave({dontSaveIf}) {
dontSaveIf(paneItem -> paneItem.getPath() === '/dont/autosave/me.coffee')
}
```

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