mirror of
https://github.com/pulsar-edit/pulsar.git
synced 2024-09-11 19:17:09 +03:00
Merge remote-tracking branch 'origin/master' into fix-macos-arm64
This commit is contained in:
commit
917dec4503
43
.cirrus.yml
43
.cirrus.yml
@ -10,6 +10,7 @@ linux_task:
|
||||
- apt-get update
|
||||
- export DEBIAN_FRONTEND="noninteractive"
|
||||
- apt-get install -y
|
||||
ffmpeg
|
||||
rpm
|
||||
build-essential
|
||||
git
|
||||
@ -27,7 +28,7 @@ linux_task:
|
||||
- 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
|
||||
@ -36,7 +37,11 @@ linux_task:
|
||||
binary_artifacts:
|
||||
path: ./binaries/*
|
||||
test_script:
|
||||
- Xvfb :99 & DISPLAY=:99 PLAYWRIGHT_JUNIT_OUTPUT_NAME=report.xml npx playwright test --reporter=junit,list
|
||||
- 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/**
|
||||
@ -81,17 +86,19 @@ arm_linux_task:
|
||||
- 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
|
||||
always:
|
||||
videos_artifacts:
|
||||
@ -121,7 +128,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,6 +140,9 @@ 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
|
||||
always:
|
||||
videos_artifacts:
|
||||
@ -166,7 +176,7 @@ intel_mac_task:
|
||||
- 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
|
||||
- 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
|
||||
@ -178,6 +188,9 @@ intel_mac_task:
|
||||
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:
|
||||
@ -189,20 +202,23 @@ intel_mac_task:
|
||||
|
||||
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=14.15.0 -y
|
||||
- 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 || sleep 1 && npx yarn install --ignore-engines || echo "There is a reason for so many tries"
|
||||
- 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
|
||||
@ -211,3 +227,14 @@ windows_task:
|
||||
- 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
|
||||
|
@ -6,6 +6,7 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
- The settings-view package now lists a package’s 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
|
||||
|
9
crowdin.yml
Normal file
9
crowdin.yml
Normal file
@ -0,0 +1,9 @@
|
||||
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
|
@ -98,6 +98,60 @@
|
||||
"warnings": []
|
||||
}</p>
|
||||
</dd>
|
||||
<dt><a href="#chromiumElementsShim">chromiumElementsShim</a></dt>
|
||||
<dd><p>This file will manage the updating of <code>autocomplete-html</code> <code>completions.json</code>
|
||||
We will partially utilize <code>@webref/elements</code> <code>.listAll()</code> function that returns
|
||||
a full list of HTML Elements along with a defined <code>interface</code>.
|
||||
To use this <code>interface</code> in any meaningful way, we will utilize the dataset
|
||||
of Attributes that apply to each <code>interface</code> from Chromiums DevTools resource
|
||||
<code>https://github.com/ChromeDevTools/devtools-frontend</code>.
|
||||
Finally from here we will utilize <code>https://github.com/mdn/content</code> to parse
|
||||
the Markdown docs of MDN's website to retreive descriptions for each element.</p>
|
||||
<p> Now for a summary of our <code>completions.json</code> file we aim to generate.
|
||||
There are two top level elements, <code>tags</code> and <code>attributes</code>, both objects.
|
||||
Within <code>tags</code> we expect the following:
|
||||
"tags": {
|
||||
"a": {
|
||||
"attributes": [ "href", "hreflang", "media", "rel", "target", "type" ],
|
||||
"description": "....."
|
||||
}
|
||||
};</p>
|
||||
<p> When an entry contains no <code>attributes</code> there is no empty array, the element
|
||||
simply doesn't exist.</p>
|
||||
<p> The <code>attributes</code> object contains keys of different elements that themselves
|
||||
are objects that can contain several valid keys.</p>
|
||||
<ul>
|
||||
<li>global: Seems to be used exclusively for Global Attributes. Is a boolean
|
||||
which when false, the key does not appear.</li>
|
||||
<li>type: A ?type? for the attribute. It's meaning is not immediately known.
|
||||
Nor a way to reliabley programatically collect it. Some discovered values:</li>
|
||||
</ul>
|
||||
<p>cssStyle: Exclusively used for <code>class</code> attribute
|
||||
boolean: Attributes that only accept <code>true</code> or <code>false</code>
|
||||
flag: For attributes that don't require or accept values. eg autoplay
|
||||
cssId: Exclusively used for the <code>id</code> attribute
|
||||
color: Exclusively used for the <code>bgcolor</code> attribute
|
||||
style: Exclusively used for the <code>style</code> attribute</p>
|
||||
<ul>
|
||||
<li>description: A text description of the attribute</li>
|
||||
<li>attribOption: A string array of valid values that can exist within the attribute.
|
||||
Such as the case with <code>rel</code> where only so many valid options exist.</li>
|
||||
</ul>
|
||||
<p> Although with our data sources mentioned above, we are able to collect nearly
|
||||
all the data needed. Except the <code>type</code> that is defined within our
|
||||
<code>completions.json</code> as well as the <code>attribOption</code> within our completions.</p>
|
||||
<p> Studying these closer reveals that all attributes listing with our <code>completions.json</code>
|
||||
do not appear elsewhere, and are nearly all global attributes.</p>
|
||||
<p> In this case since there is no sane way to collect this data, we will leave this
|
||||
list as a manually maintained section of our <code>completions.json</code>.
|
||||
This does mean that <code>curated-attributes.json</code> is a static document that
|
||||
will require manual updating in the future. Or most ideally, will find a way
|
||||
to automatically generate the needed data.</p>
|
||||
</dd>
|
||||
<dt><a href="#update">update</a></dt>
|
||||
<dd><p>This file aims to run some short simple tests against <code>update.js</code>. Focusing
|
||||
mainly on the Regex used within <code>sanitizeDescription()</code></p>
|
||||
</dd>
|
||||
<dt><a href="#fs">fs</a></dt>
|
||||
<dd></dd>
|
||||
<dt><a href="#dalek">dalek</a></dt>
|
||||
@ -376,6 +430,68 @@ This file will manage the updating of `autocomplete-css` `completions.json`.
|
||||
"warnings": []
|
||||
}
|
||||
|
||||
**Kind**: global constant
|
||||
<a name="chromiumElementsShim"></a>
|
||||
|
||||
## chromiumElementsShim
|
||||
This file will manage the updating of `autocomplete-html` `completions.json`
|
||||
We will partially utilize `@webref/elements` `.listAll()` function that returns
|
||||
a full list of HTML Elements along with a defined `interface`.
|
||||
To use this `interface` in any meaningful way, we will utilize the dataset
|
||||
of Attributes that apply to each `interface` from Chromiums DevTools resource
|
||||
`https://github.com/ChromeDevTools/devtools-frontend`.
|
||||
Finally from here we will utilize `https://github.com/mdn/content` to parse
|
||||
the Markdown docs of MDN's website to retreive descriptions for each element.
|
||||
|
||||
Now for a summary of our `completions.json` file we aim to generate.
|
||||
There are two top level elements, `tags` and `attributes`, both objects.
|
||||
Within `tags` we expect the following:
|
||||
"tags": {
|
||||
"a": {
|
||||
"attributes": [ "href", "hreflang", "media", "rel", "target", "type" ],
|
||||
"description": "....."
|
||||
}
|
||||
};
|
||||
|
||||
When an entry contains no `attributes` there is no empty array, the element
|
||||
simply doesn't exist.
|
||||
|
||||
The `attributes` object contains keys of different elements that themselves
|
||||
are objects that can contain several valid keys.
|
||||
- global: Seems to be used exclusively for Global Attributes. Is a boolean
|
||||
which when false, the key does not appear.
|
||||
- type: A ?type? for the attribute. It's meaning is not immediately known.
|
||||
Nor a way to reliabley programatically collect it. Some discovered values:
|
||||
cssStyle: Exclusively used for `class` attribute
|
||||
boolean: Attributes that only accept `true` or `false`
|
||||
flag: For attributes that don't require or accept values. eg autoplay
|
||||
cssId: Exclusively used for the `id` attribute
|
||||
color: Exclusively used for the `bgcolor` attribute
|
||||
style: Exclusively used for the `style` attribute
|
||||
- description: A text description of the attribute
|
||||
- attribOption: A string array of valid values that can exist within the attribute.
|
||||
Such as the case with `rel` where only so many valid options exist.
|
||||
|
||||
Although with our data sources mentioned above, we are able to collect nearly
|
||||
all the data needed. Except the `type` that is defined within our
|
||||
`completions.json` as well as the `attribOption` within our completions.
|
||||
|
||||
Studying these closer reveals that all attributes listing with our `completions.json`
|
||||
do not appear elsewhere, and are nearly all global attributes.
|
||||
|
||||
In this case since there is no sane way to collect this data, we will leave this
|
||||
list as a manually maintained section of our `completions.json`.
|
||||
This does mean that `curated-attributes.json` is a static document that
|
||||
will require manual updating in the future. Or most ideally, will find a way
|
||||
to automatically generate the needed data.
|
||||
|
||||
**Kind**: global constant
|
||||
<a name="update"></a>
|
||||
|
||||
## update
|
||||
This file aims to run some short simple tests against `update.js`. Focusing
|
||||
mainly on the Regex used within `sanitizeDescription()`
|
||||
|
||||
**Kind**: global constant
|
||||
<a name="fs"></a>
|
||||
|
||||
|
@ -98,6 +98,60 @@
|
||||
"warnings": []
|
||||
}</p>
|
||||
</dd>
|
||||
<dt><a href="#chromiumElementsShim">chromiumElementsShim</a></dt>
|
||||
<dd><p>This file will manage the updating of <code>autocomplete-html</code> <code>completions.json</code>
|
||||
We will partially utilize <code>@webref/elements</code> <code>.listAll()</code> function that returns
|
||||
a full list of HTML Elements along with a defined <code>interface</code>.
|
||||
To use this <code>interface</code> in any meaningful way, we will utilize the dataset
|
||||
of Attributes that apply to each <code>interface</code> from Chromiums DevTools resource
|
||||
<code>https://github.com/ChromeDevTools/devtools-frontend</code>.
|
||||
Finally from here we will utilize <code>https://github.com/mdn/content</code> to parse
|
||||
the Markdown docs of MDN's website to retreive descriptions for each element.</p>
|
||||
<p> Now for a summary of our <code>completions.json</code> file we aim to generate.
|
||||
There are two top level elements, <code>tags</code> and <code>attributes</code>, both objects.
|
||||
Within <code>tags</code> we expect the following:
|
||||
"tags": {
|
||||
"a": {
|
||||
"attributes": [ "href", "hreflang", "media", "rel", "target", "type" ],
|
||||
"description": "....."
|
||||
}
|
||||
};</p>
|
||||
<p> When an entry contains no <code>attributes</code> there is no empty array, the element
|
||||
simply doesn't exist.</p>
|
||||
<p> The <code>attributes</code> object contains keys of different elements that themselves
|
||||
are objects that can contain several valid keys.</p>
|
||||
<ul>
|
||||
<li>global: Seems to be used exclusively for Global Attributes. Is a boolean
|
||||
which when false, the key does not appear.</li>
|
||||
<li>type: A ?type? for the attribute. It's meaning is not immediately known.
|
||||
Nor a way to reliabley programatically collect it. Some discovered values:</li>
|
||||
</ul>
|
||||
<p>cssStyle: Exclusively used for <code>class</code> attribute
|
||||
boolean: Attributes that only accept <code>true</code> or <code>false</code>
|
||||
flag: For attributes that don't require or accept values. eg autoplay
|
||||
cssId: Exclusively used for the <code>id</code> attribute
|
||||
color: Exclusively used for the <code>bgcolor</code> attribute
|
||||
style: Exclusively used for the <code>style</code> attribute</p>
|
||||
<ul>
|
||||
<li>description: A text description of the attribute</li>
|
||||
<li>attribOption: A string array of valid values that can exist within the attribute.
|
||||
Such as the case with <code>rel</code> where only so many valid options exist.</li>
|
||||
</ul>
|
||||
<p> Although with our data sources mentioned above, we are able to collect nearly
|
||||
all the data needed. Except the <code>type</code> that is defined within our
|
||||
<code>completions.json</code> as well as the <code>attribOption</code> within our completions.</p>
|
||||
<p> Studying these closer reveals that all attributes listing with our <code>completions.json</code>
|
||||
do not appear elsewhere, and are nearly all global attributes.</p>
|
||||
<p> In this case since there is no sane way to collect this data, we will leave this
|
||||
list as a manually maintained section of our <code>completions.json</code>.
|
||||
This does mean that <code>curated-attributes.json</code> is a static document that
|
||||
will require manual updating in the future. Or most ideally, will find a way
|
||||
to automatically generate the needed data.</p>
|
||||
</dd>
|
||||
<dt><a href="#update">update</a></dt>
|
||||
<dd><p>This file aims to run some short simple tests against <code>update.js</code>. Focusing
|
||||
mainly on the Regex used within <code>sanitizeDescription()</code></p>
|
||||
</dd>
|
||||
<dt><a href="#fs">fs</a></dt>
|
||||
<dd></dd>
|
||||
<dt><a href="#dalek">dalek</a></dt>
|
||||
@ -384,6 +438,68 @@ This file will manage the updating of `autocomplete-css` `completions.json`.
|
||||
"warnings": []
|
||||
}
|
||||
|
||||
**Kind**: global constant
|
||||
<a name="chromiumElementsShim"></a>
|
||||
|
||||
## chromiumElementsShim
|
||||
This file will manage the updating of `autocomplete-html` `completions.json`
|
||||
We will partially utilize `@webref/elements` `.listAll()` function that returns
|
||||
a full list of HTML Elements along with a defined `interface`.
|
||||
To use this `interface` in any meaningful way, we will utilize the dataset
|
||||
of Attributes that apply to each `interface` from Chromiums DevTools resource
|
||||
`https://github.com/ChromeDevTools/devtools-frontend`.
|
||||
Finally from here we will utilize `https://github.com/mdn/content` to parse
|
||||
the Markdown docs of MDN's website to retreive descriptions for each element.
|
||||
|
||||
Now for a summary of our `completions.json` file we aim to generate.
|
||||
There are two top level elements, `tags` and `attributes`, both objects.
|
||||
Within `tags` we expect the following:
|
||||
"tags": {
|
||||
"a": {
|
||||
"attributes": [ "href", "hreflang", "media", "rel", "target", "type" ],
|
||||
"description": "....."
|
||||
}
|
||||
};
|
||||
|
||||
When an entry contains no `attributes` there is no empty array, the element
|
||||
simply doesn't exist.
|
||||
|
||||
The `attributes` object contains keys of different elements that themselves
|
||||
are objects that can contain several valid keys.
|
||||
- global: Seems to be used exclusively for Global Attributes. Is a boolean
|
||||
which when false, the key does not appear.
|
||||
- type: A ?type? for the attribute. It's meaning is not immediately known.
|
||||
Nor a way to reliabley programatically collect it. Some discovered values:
|
||||
cssStyle: Exclusively used for `class` attribute
|
||||
boolean: Attributes that only accept `true` or `false`
|
||||
flag: For attributes that don't require or accept values. eg autoplay
|
||||
cssId: Exclusively used for the `id` attribute
|
||||
color: Exclusively used for the `bgcolor` attribute
|
||||
style: Exclusively used for the `style` attribute
|
||||
- description: A text description of the attribute
|
||||
- attribOption: A string array of valid values that can exist within the attribute.
|
||||
Such as the case with `rel` where only so many valid options exist.
|
||||
|
||||
Although with our data sources mentioned above, we are able to collect nearly
|
||||
all the data needed. Except the `type` that is defined within our
|
||||
`completions.json` as well as the `attribOption` within our completions.
|
||||
|
||||
Studying these closer reveals that all attributes listing with our `completions.json`
|
||||
do not appear elsewhere, and are nearly all global attributes.
|
||||
|
||||
In this case since there is no sane way to collect this data, we will leave this
|
||||
list as a manually maintained section of our `completions.json`.
|
||||
This does mean that `curated-attributes.json` is a static document that
|
||||
will require manual updating in the future. Or most ideally, will find a way
|
||||
to automatically generate the needed data.
|
||||
|
||||
**Kind**: global constant
|
||||
<a name="update"></a>
|
||||
|
||||
## update
|
||||
This file aims to run some short simple tests against `update.js`. Focusing
|
||||
mainly on the Regex used within `sanitizeDescription()`
|
||||
|
||||
**Kind**: global constant
|
||||
<a name="fs"></a>
|
||||
|
||||
|
169
i18n/en.json
Normal file
169
i18n/en.json
Normal file
@ -0,0 +1,169 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
@ -2,227 +2,227 @@
|
||||
{
|
||||
label: 'Pulsar'
|
||||
submenu: [
|
||||
{ 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}
|
||||
{ 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 }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Preferences…', command: 'application:show-settings' }
|
||||
{ localisedLabel: 'core.menu.pulsar.preferences', command: 'application:show-settings' }
|
||||
{ type: 'separator' }
|
||||
{ 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' }
|
||||
{ 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' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Install Shell Commands', command: 'window:install-shell-commands' }
|
||||
{ localisedLabel: 'core.menu.pulsar.install-shell-commands', command: 'window:install-shell-commands' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Services', role: 'services', submenu: [] }
|
||||
{ localisedLabel: 'core.menu.macos.services', role: 'services', submenu: [] }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Hide Pulsar', command: 'application:hide' }
|
||||
{ label: 'Hide Others', command: 'application:hide-other-applications' }
|
||||
{ label: 'Show All', command: 'application:unhide-all-applications' }
|
||||
{ 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' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Quit Pulsar', command: 'application:quit' }
|
||||
{ localisedLabel: 'core.menu.pulsar.quit', command: 'application:quit' }
|
||||
]
|
||||
}
|
||||
{
|
||||
label: 'File'
|
||||
localisedLabel: 'core.menu.file.self'
|
||||
submenu: [
|
||||
{ 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.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: 'Reopen Project',
|
||||
localisedLabel: 'core.menu.file.project-history.reopen-project'
|
||||
submenu: [
|
||||
{ label: 'Clear Project History', command: 'application:clear-project-history' }
|
||||
{ localisedLabel: 'core.menu.file.project-history.clear', command: 'application:clear-project-history' }
|
||||
{ type: 'separator' }
|
||||
]
|
||||
}
|
||||
{ label: 'Reopen Last Item', command: 'pane:reopen-closed-item' }
|
||||
{ localisedLabel: 'core.menu.file.reopen-last-item', command: 'pane:reopen-closed-item' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Save', command: 'core:save' }
|
||||
{ label: 'Save As…', command: 'core:save-as' }
|
||||
{ label: 'Save All', command: 'window:save-all' }
|
||||
{ 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' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Close Tab', command: 'core:close' }
|
||||
{ label: 'Close Pane', command: 'pane:close' }
|
||||
{ label: 'Close Window', command: 'window:close' }
|
||||
{ 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: 'Edit'
|
||||
localisedLabel: 'core.menu.edit.self'
|
||||
submenu: [
|
||||
{ label: 'Undo', command: 'core:undo' }
|
||||
{ label: 'Redo', command: 'core:redo' }
|
||||
{ localisedLabel: 'core.menu.edit.undo', command: 'core:undo' }
|
||||
{ localisedLabel: 'core.menu.edit.redo', command: 'core:redo' }
|
||||
{ type: 'separator' }
|
||||
{ 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' }
|
||||
{ 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' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Toggle Comments', command: 'editor:toggle-line-comments' }
|
||||
{ localisedLabel: 'core.menu.edit.toggle-comments', command: 'editor:toggle-line-comments' }
|
||||
{
|
||||
label: 'Lines',
|
||||
localisedLabel: 'core.menu.edit.lines.self',
|
||||
submenu: [
|
||||
{ label: 'Indent', command: 'editor:indent-selected-rows' }
|
||||
{ label: 'Outdent', command: 'editor:outdent-selected-rows' }
|
||||
{ label: 'Auto Indent', command: 'editor:auto-indent' }
|
||||
{ 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' }
|
||||
{ type: 'separator' }
|
||||
{ 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.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: 'Columns',
|
||||
localisedLabel: 'core.menu.edit.columns.self'
|
||||
submenu: [
|
||||
{ label: 'Move Selection Left', command: 'editor:move-selection-left' }
|
||||
{ label: 'Move Selection Right', command: 'editor:move-selection-right' }
|
||||
{ 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: 'Text',
|
||||
localisedLabel: 'core.menu.edit.text.self'
|
||||
submenu: [
|
||||
{ label: 'Upper Case', command: 'editor:upper-case' }
|
||||
{ label: 'Lower Case', command: 'editor:lower-case' }
|
||||
{ localisedLabel: 'core.menu.edit.text.upper-case', command: 'editor:upper-case' }
|
||||
{ localisedLabel: 'core.menu.edit.text.lower-case', command: 'editor:lower-case' }
|
||||
{ type: 'separator' }
|
||||
{ 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' }
|
||||
{ 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' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Transpose', command: 'editor:transpose' }
|
||||
{ localisedLabel: 'core.menu.edit.text.transpose', command: 'editor:transpose' }
|
||||
]
|
||||
}
|
||||
{
|
||||
label: 'Folding',
|
||||
localisedLabel: 'core.menu.edit.folding.self'
|
||||
submenu: [
|
||||
{ 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' }
|
||||
{ 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' }
|
||||
{ type: 'separator' }
|
||||
{ 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.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: 'View'
|
||||
localisedLabel: 'core.menu.view.self'
|
||||
submenu: [
|
||||
{ label: 'Toggle Full Screen', command: 'window:toggle-full-screen' }
|
||||
{ localisedLabel: 'core.menu.view.toggle-full-screen', command: 'window:toggle-full-screen' }
|
||||
{
|
||||
label: 'Panes'
|
||||
localisedLabel: 'core.menu.view.panes.self'
|
||||
submenu: [
|
||||
{ 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' }
|
||||
{ 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' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Focus Next Pane', command: 'window:focus-next-pane' }
|
||||
{ label: 'Focus Previous Pane', command: 'window:focus-previous-pane' }
|
||||
{ localisedLabel: 'core.menu.view.panes.focus-next', command: 'window:focus-next-pane' }
|
||||
{ localisedLabel: 'core.menu.view.panes.focus-previous', command: 'window:focus-previous-pane' }
|
||||
{ type: 'separator' }
|
||||
{ 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' }
|
||||
{ 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' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Close Pane', command: 'pane:close' }
|
||||
{ localisedLabel: 'core.menu.view.panes.close', command: 'pane:close' }
|
||||
]
|
||||
}
|
||||
{
|
||||
label: 'Developer'
|
||||
localisedLabel: 'core.menu.view.developer.self'
|
||||
submenu: [
|
||||
{ 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' }
|
||||
{ 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' }
|
||||
]
|
||||
}
|
||||
{ type: 'separator' }
|
||||
{ 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' }
|
||||
{ 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' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Toggle Soft Wrap', command: 'editor:toggle-soft-wrap' }
|
||||
{ localisedLabel: 'core.menu.view.toggle-soft-wrap', command: 'editor:toggle-soft-wrap' }
|
||||
]
|
||||
}
|
||||
|
||||
{
|
||||
label: 'Selection'
|
||||
localisedLabel: 'core.menu.selection.self'
|
||||
submenu: [
|
||||
{ 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'}
|
||||
{ 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' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Select to Top', command: 'core:select-to-top' }
|
||||
{ label: 'Select to Bottom', command: 'core:select-to-bottom' }
|
||||
{ localisedLabel: 'core.menu.selection.to-top', command: 'core:select-to-top' }
|
||||
{ localisedLabel: 'core.menu.selection.to-bottom', command: 'core:select-to-bottom' }
|
||||
{ type: 'separator' }
|
||||
{ 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.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: 'Find'
|
||||
localisedLabel: 'core.menu.find.self'
|
||||
submenu: []
|
||||
}
|
||||
|
||||
{
|
||||
label: 'Packages'
|
||||
localisedLabel: 'core.menu.packages.self'
|
||||
submenu: [
|
||||
{ label: 'Open Package Manager', command: 'settings-view:view-installed-packages' }
|
||||
{ localisedLabel: 'core.menu.packages.open-package-manager', command: 'settings-view:view-installed-packages' }
|
||||
{ type: 'separator' }
|
||||
]
|
||||
}
|
||||
|
||||
{
|
||||
label: 'Window'
|
||||
localisedLabel: 'core.menu.window.self'
|
||||
role: 'window'
|
||||
submenu: [
|
||||
{ label: 'Minimize', command: 'application:minimize' }
|
||||
{ label: 'Zoom', command: 'application:zoom' }
|
||||
{ localisedLabel: 'core.menu.window.minimise', command: 'application:minimize' }
|
||||
{ localisedLabel: 'core.menu.window.zoom', command: 'application:zoom' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Bring All to Front', command: 'application:bring-all-windows-to-front' }
|
||||
{ localisedLabel: 'core.menu.window.bring-all-to-front', command: 'application:bring-all-windows-to-front' }
|
||||
]
|
||||
}
|
||||
|
||||
{
|
||||
label: 'Help'
|
||||
localisedLabel: 'core.menu.help.self'
|
||||
role: 'help'
|
||||
submenu: [
|
||||
{ label: 'Terms of Use', command: 'application:open-terms-of-use' }
|
||||
{ label: 'Documentation', command: 'application:open-documentation' }
|
||||
{ label: 'Frequently Asked Questions', command: 'application:open-faq' }
|
||||
{ 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' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Community Discussions', command: 'application:open-discussions' }
|
||||
{ label: 'Report Issue', command: 'application:report-issue' }
|
||||
{ label: 'Search Issues', command: 'application:search-issues' }
|
||||
{ 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' }
|
||||
{ type: 'separator' }
|
||||
]
|
||||
}
|
||||
|
234
menus/linux.cson
234
menus/linux.cson
@ -1,204 +1,204 @@
|
||||
'menu': [
|
||||
{
|
||||
label: '&File'
|
||||
localisedLabel: 'core.menu.file.self' # accelerator F
|
||||
submenu: [
|
||||
{ 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.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: 'Reopen Project',
|
||||
localisedLabel: 'core.menu.file.project-history.reopen-project'
|
||||
submenu: [
|
||||
{ label: 'Clear Project History', command: 'application:clear-project-history' }
|
||||
{ localisedLabel: 'core.menu.file.project-history.clear', command: 'application:clear-project-history' }
|
||||
{ type: 'separator' }
|
||||
]
|
||||
}
|
||||
{ label: 'Reopen Last &Item', command: 'pane:reopen-closed-item' }
|
||||
{ localisedLabel: 'core.menu.file.reopen-last-item', command: 'pane:reopen-closed-item' } # accelerator I
|
||||
{ type: 'separator' }
|
||||
{ label: '&Save', command: 'core:save' }
|
||||
{ label: 'Save &As…', command: 'core:save-as' }
|
||||
{ label: 'Save A&ll', command: 'window:save-all' }
|
||||
{ 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
|
||||
{ type: 'separator' }
|
||||
{ label: '&Close Tab', command: 'core:close' }
|
||||
{ label: 'Close &Pane', command: 'pane:close' }
|
||||
{ label: 'Clos&e Window', command: 'window:close' }
|
||||
{ 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
|
||||
{ type: 'separator' }
|
||||
{ label: 'Quit', command: 'application:quit' }
|
||||
{ localisedLabel: 'core.menu.pulsar.quit', command: 'application:quit' }
|
||||
]
|
||||
}
|
||||
|
||||
{
|
||||
label: '&Edit'
|
||||
localisedLabel: 'core.menu.edit.self' # accelerator E
|
||||
submenu: [
|
||||
{ label: '&Undo', command: 'core:undo' }
|
||||
{ label: '&Redo', command: 'core:redo' }
|
||||
{ localisedLabel: 'core.menu.edit.undo', command: 'core:undo' } # accelerator U
|
||||
{ localisedLabel: 'core.menu.edit.redo', command: 'core:redo' } # accelerator R
|
||||
{ type: 'separator' }
|
||||
{ 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' }
|
||||
{ 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
|
||||
{ type: 'separator' }
|
||||
{ label: '&Toggle Comments', command: 'editor:toggle-line-comments' }
|
||||
{ localisedLabel: 'core.menu.edit.toggle-comments', command: 'editor:toggle-line-comments' } # accelerator T
|
||||
{
|
||||
label: 'Lines',
|
||||
localisedLabel: 'core.menu.edit.lines.self'
|
||||
submenu: [
|
||||
{ label: '&Indent', command: 'editor:indent-selected-rows' }
|
||||
{ label: '&Outdent', command: 'editor:outdent-selected-rows' }
|
||||
{ label: '&Auto Indent', command: 'editor:auto-indent' }
|
||||
{ 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
|
||||
{ type: 'separator' }
|
||||
{ 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.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: 'Columns',
|
||||
localisedLabel: 'core.menu.edit.columns.self'
|
||||
submenu: [
|
||||
{ label: 'Move Selection &Left', command: 'editor:move-selection-left' }
|
||||
{ label: 'Move Selection &Right', command: 'editor:move-selection-right' }
|
||||
{ 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: 'Text',
|
||||
localisedLabel: 'core.menu.edit.text.self'
|
||||
submenu: [
|
||||
{ label: '&Upper Case', command: 'editor:upper-case' }
|
||||
{ label: '&Lower Case', command: 'editor:lower-case' }
|
||||
{ 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
|
||||
{ type: 'separator' }
|
||||
{ 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' }
|
||||
{ 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
|
||||
{ type: 'separator' }
|
||||
{ label: '&Transpose', command: 'editor:transpose' }
|
||||
{ localisedLabel: 'core.menu.edit.text.transpose', command: 'editor:transpose' } # accelerator T
|
||||
]
|
||||
}
|
||||
{
|
||||
label: 'Folding',
|
||||
localisedLabel: 'core.menu.edit.folding.self'
|
||||
submenu: [
|
||||
{ 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' }
|
||||
{ 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
|
||||
{ type: 'separator' }
|
||||
{ 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.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' }
|
||||
]
|
||||
}
|
||||
{ type: 'separator' }
|
||||
{ label: '&Preferences', command: 'application:show-settings' }
|
||||
{ localisedLabel: 'core.menu.pulsar.preferences', command: 'application:show-settings' } # accelerator P
|
||||
{ type: 'separator' }
|
||||
{ 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' }
|
||||
{ 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' }
|
||||
{ type: 'separator' }
|
||||
]
|
||||
}
|
||||
|
||||
{
|
||||
label: '&View'
|
||||
localisedLabel: 'core.menu.view.self' # accelerator V
|
||||
submenu: [
|
||||
{ label: 'Toggle &Full Screen', command: 'window:toggle-full-screen' }
|
||||
{ label: 'Toggle Menu Bar', command: 'window:toggle-menu-bar' }
|
||||
{ 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: 'Panes'
|
||||
localisedLabel: 'core.menu.view.panes.self'
|
||||
submenu: [
|
||||
{ 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' }
|
||||
{ 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' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Focus Next Pane', command: 'window:focus-next-pane' }
|
||||
{ label: 'Focus Previous Pane', command: 'window:focus-previous-pane' }
|
||||
{ localisedLabel: 'core.menu.view.panes.focus-next', command: 'window:focus-next-pane' }
|
||||
{ localisedLabel: 'core.menu.view.panes.focus-previous', command: 'window:focus-previous-pane' }
|
||||
{ type: 'separator' }
|
||||
{ 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' }
|
||||
{ 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' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Close Pane', command: 'pane:close' }
|
||||
{ localisedLabel: 'core.menu.view.panes.close', command: 'pane:close' }
|
||||
]
|
||||
}
|
||||
{
|
||||
label: 'Developer'
|
||||
localisedLabel: 'core.menu.view.developer.self'
|
||||
submenu: [
|
||||
{ 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' }
|
||||
{ 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
|
||||
]
|
||||
}
|
||||
{ type: 'separator' }
|
||||
{ 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' }
|
||||
{ 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
|
||||
{ type: 'separator' }
|
||||
{ label: 'Toggle Soft &Wrap', command: 'editor:toggle-soft-wrap' }
|
||||
{ localisedLabel: 'core.menu.view.toggle-soft-wrap', command: 'editor:toggle-soft-wrap' } # accelerator W
|
||||
]
|
||||
}
|
||||
|
||||
{
|
||||
label: '&Selection'
|
||||
localisedLabel: 'core.menu.selection.self' # accelerator S
|
||||
submenu: [
|
||||
{ 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'}
|
||||
{ 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
|
||||
{ type: 'separator' }
|
||||
{ label: 'Select to &Top', command: 'core:select-to-top' }
|
||||
{ label: 'Select to Botto&m', command: 'core:select-to-bottom' }
|
||||
{ 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
|
||||
{ type: 'separator' }
|
||||
{ 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.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: 'F&ind'
|
||||
localisedLabel: 'core.menu.find.self' # accelerator I
|
||||
submenu: []
|
||||
}
|
||||
|
||||
{
|
||||
label: '&Packages'
|
||||
localisedLabel: 'core.menu.packages.self' # accelerator P
|
||||
submenu: [
|
||||
{ label: 'Open Package Manager', command: 'settings-view:view-installed-packages' }
|
||||
{ localisedLabel: 'core.menu.packages.open-package-manager', command: 'settings-view:view-installed-packages' }
|
||||
{ type: 'separator' }
|
||||
]
|
||||
}
|
||||
|
||||
{
|
||||
label: '&Help'
|
||||
localisedLabel: 'core.menu.help.self' # accelerator H
|
||||
submenu: [
|
||||
{ label: 'View &Terms of Use', command: 'application:open-terms-of-use' }
|
||||
{ label: 'View &License', command: 'application:open-license' }
|
||||
{ label: "VERSION", enabled: false }
|
||||
{ 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 }
|
||||
{ type: 'separator' }
|
||||
{ label: '&Documentation', command: 'application:open-documentation' }
|
||||
{ label: 'Frequently Asked Questions', command: 'application:open-faq' }
|
||||
{ localisedLabel: 'core.menu.help.docs', command: 'application:open-documentation' } # accelerator D
|
||||
{ localisedLabel: 'core.menu.help.faq', command: 'application:open-faq' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'Community Discussions', command: 'application:open-discussions' }
|
||||
{ label: 'Report Issue', command: 'application:report-issue' }
|
||||
{ label: 'Search Issues', command: 'application:search-issues' }
|
||||
{ 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' }
|
||||
{ type: 'separator' }
|
||||
{ label: 'About Pulsar', command: 'application:about' }
|
||||
{ localisedLabel: 'core.menu.pulsar.about', command: 'application:about' }
|
||||
{ type: 'separator' }
|
||||
]
|
||||
}
|
||||
|
@ -24,6 +24,7 @@
|
||||
"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",
|
||||
@ -77,6 +78,7 @@
|
||||
"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",
|
||||
|
@ -16,7 +16,7 @@ See [RFC 003](https://github.com/atom/atom/blob/master/docs/rfcs/003-consolidate
|
||||
| **autocomplete-atom-api** | [`atom/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-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) |
|
||||
@ -76,7 +76,7 @@ See [RFC 003](https://github.com/atom/atom/blob/master/docs/rfcs/003-consolidate
|
||||
| **language-yaml** | [`./language-yaml`](./language-yaml) | |
|
||||
| **line-ending-selector** | [`./line-ending-selector`](./line-ending-selector) | |
|
||||
| **link** | [`./link`](./link) | |
|
||||
| **markdown-preview** | [`./markdown-preview`][./markdown-preview] | |
|
||||
| **markdown-preview** | [`./markdown-preview`](./markdown-preview) | |
|
||||
| **notifications** | [`atom/notifications`][notifications] | [#18277](https://github.com/atom/atom/issues/18277) |
|
||||
| **one-dark-syntax** | [`./one-dark-syntax`](./one-dark-syntax) | |
|
||||
| **one-dark-ui** | [`./one-dark-ui`](./one-dark-ui) | |
|
||||
@ -90,7 +90,7 @@ See [RFC 003](https://github.com/atom/atom/blob/master/docs/rfcs/003-consolidate
|
||||
| **solarized-light-syntax** | [`./solarized-light-syntax`](./solarized-light-syntax) | |
|
||||
| **spell-check** | [`atom/spell-check`][spell-check] | |
|
||||
| **status-bar** | [`./status-bar`](./status-bar) | |
|
||||
| **styleguide** | [`./styleguide`][./styleguide] | |
|
||||
| **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) |
|
||||
@ -98,7 +98,7 @@ See [RFC 003](https://github.com/atom/atom/blob/master/docs/rfcs/003-consolidate
|
||||
| **update-package-dependencies** | [`./update-package-dependencies`](./update-package-dependencies) | |
|
||||
| **welcome** | [`./welcome`](./welcome) | |
|
||||
| **whitespace** | [`./whitespace`](./whitespace) | |
|
||||
| **wrap-guide** | [`./wrap-guide`][./wrap-guide] | |
|
||||
| **wrap-guide** | [`./wrap-guide`](./wrap-guide) | |
|
||||
|
||||
[autocomplete-atom-api]: https://github.com/pulsar-edit/autocomplete-atom-api
|
||||
[autosave]: https://github.com/pulsar-edit/autosave
|
||||
|
@ -7,4 +7,4 @@ Descriptions are powered by [MDN](https://developer.mozilla.org).
|
||||
|
||||
![html-completions](https://cloud.githubusercontent.com/assets/2766036/25668197/ffd24928-2ff3-11e7-85fc-b327ac2287e6.gif)
|
||||
|
||||
You can update the prebuilt list of tags and attributes names and values by running the `update.js` file at the root of the repository and then checking-in the changed `completions.json` file.
|
||||
You can update the prebuilt list of tags and attributes names and values by running `npm run update` at the root of the package and then checking-in the changed `completions.json` file.
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,113 +0,0 @@
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
const request = require('request')
|
||||
|
||||
const mdnHTMLURL = 'https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes'
|
||||
const mdnJSONAPI = 'https://developer.mozilla.org/en-US/search.json?topic=html&highlight=false'
|
||||
const AttributesURL = 'https://raw.githubusercontent.com/adobe/brackets/master/src/extensions/default/HTMLCodeHints/HtmlAttributes.json'
|
||||
|
||||
const fetch = () => {
|
||||
const attributesPromise = new Promise((resolve) => {
|
||||
request({json: true, url: AttributesURL}, (error, response, attributes) => {
|
||||
if (error) {
|
||||
console.error(error.message)
|
||||
resolve(null)
|
||||
}
|
||||
|
||||
if (response.statusCode !== 200) {
|
||||
console.error(`Request for HtmlAttributes.json failed: ${response.statusCode}`)
|
||||
resolve(null)
|
||||
}
|
||||
|
||||
resolve(attributes)
|
||||
})
|
||||
})
|
||||
|
||||
attributesPromise.then((attributes) => {
|
||||
if (!attributes) return
|
||||
|
||||
const MAX = 10
|
||||
const queue = []
|
||||
for (let attribute in attributes) {
|
||||
// MDN is missing docs for aria attributes and on* event handlers
|
||||
const options = attributes[attribute]
|
||||
if (options.global && !attribute.startsWith('aria') && !attribute.startsWith('on') && (attribute !== 'role')) {
|
||||
queue.push(attribute)
|
||||
}
|
||||
}
|
||||
const running = []
|
||||
const docs = {}
|
||||
|
||||
return new Promise((resolve) => {
|
||||
const checkEnd = () => {
|
||||
if ((queue.length === 0) && (running.length === 0)) resolve(docs)
|
||||
}
|
||||
|
||||
const removeRunning = (attributeName) => {
|
||||
const index = running.indexOf(attributeName)
|
||||
if (index > -1) { running.splice(index, 1) }
|
||||
}
|
||||
|
||||
const runNext = () => {
|
||||
checkEnd()
|
||||
if (queue.length !== 0) {
|
||||
const attributeName = queue.pop()
|
||||
running.push(attributeName)
|
||||
run(attributeName)
|
||||
}
|
||||
}
|
||||
|
||||
var run = (attributeName) => {
|
||||
const url = `${mdnJSONAPI}&q=${attributeName}`
|
||||
request({json: true, url}, (error, response, searchResults) => {
|
||||
if (!error && response.statusCode === 200) {
|
||||
handleRequest(attributeName, searchResults)
|
||||
} else {
|
||||
console.error(`Req failed ${url}; ${response.statusCode}, ${error}`)
|
||||
}
|
||||
removeRunning(attributeName)
|
||||
runNext()
|
||||
})
|
||||
}
|
||||
|
||||
var handleRequest = (attributeName, searchResults) => {
|
||||
if (searchResults.documents) {
|
||||
for (let doc of searchResults.documents) {
|
||||
if (doc.url === `${mdnHTMLURL}/${attributeName}`) {
|
||||
docs[attributeName] = filterExcerpt(attributeName, doc.excerpt)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log(`Could not find documentation for ${attributeName}`)
|
||||
}
|
||||
|
||||
for (let i = 0; i <= MAX; i++) runNext()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
var filterExcerpt = (attributeName, excerpt) => {
|
||||
const beginningPattern = /^the [a-z-]+ global attribute (is )?(\w+)/i
|
||||
excerpt = excerpt.replace(beginningPattern, (match) => {
|
||||
const matches = beginningPattern.exec(match)
|
||||
const firstWord = matches[2]
|
||||
return firstWord[0].toUpperCase() + firstWord.slice(1)
|
||||
})
|
||||
const periodIndex = excerpt.indexOf('.')
|
||||
if (periodIndex > -1) { excerpt = excerpt.slice(0, periodIndex + 1) }
|
||||
return excerpt
|
||||
}
|
||||
|
||||
// Save a file if run from the command line
|
||||
if (require.main === module) {
|
||||
fetch().then((docs) => {
|
||||
if (docs) {
|
||||
fs.writeFileSync(path.join(__dirname, 'global-attribute-docs.json'), `${JSON.stringify(docs, null, ' ')}\n`)
|
||||
} else {
|
||||
console.error('No docs')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = fetch
|
@ -1,120 +0,0 @@
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* DS202: Simplify dynamic range loops
|
||||
* DS207: Consider shorter variations of null checks
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
const request = require('request')
|
||||
|
||||
const mdnHTMLURL = 'https://developer.mozilla.org/en-US/docs/Web/HTML/Element'
|
||||
const mdnJSONAPI = 'https://developer.mozilla.org/en-US/search.json?topic=html&highlight=false'
|
||||
const TagsURL = 'https://raw.githubusercontent.com/adobe/brackets/master/src/extensions/default/HTMLCodeHints/HtmlTags.json'
|
||||
|
||||
const fetch = () => {
|
||||
const tagsPromise = new Promise((resolve) => {
|
||||
request({json: true, url: TagsURL}, (error, response, tags) => {
|
||||
if (error != null) {
|
||||
console.error(error.message)
|
||||
resolve(null)
|
||||
}
|
||||
|
||||
if (response.statusCode !== 200) {
|
||||
console.error(`Request for HtmlTags.json failed: ${response.statusCode}`)
|
||||
resolve(null)
|
||||
}
|
||||
|
||||
resolve(tags)
|
||||
})
|
||||
})
|
||||
|
||||
return tagsPromise.then((tags) => {
|
||||
if (!tags) return
|
||||
|
||||
const MAX = 10
|
||||
const queue = Object.keys(tags)
|
||||
const running = []
|
||||
const docs = {}
|
||||
|
||||
return new Promise((resolve) => {
|
||||
const checkEnd = () => {
|
||||
if ((queue.length === 0) && (running.length === 0)) resolve(docs)
|
||||
}
|
||||
|
||||
const removeRunning = (tagName) => {
|
||||
const index = running.indexOf(tagName)
|
||||
if (index > -1) { return running.splice(index, 1) }
|
||||
}
|
||||
|
||||
const runNext = () => {
|
||||
checkEnd()
|
||||
if (queue.length !== 0) {
|
||||
const tagName = queue.pop()
|
||||
running.push(tagName)
|
||||
run(tagName)
|
||||
}
|
||||
}
|
||||
|
||||
var run = (tagName) => {
|
||||
const url = `${mdnJSONAPI}&q=${tagName}`
|
||||
request({json: true, url}, (error, response, searchResults) => {
|
||||
if ((error == null) && (response.statusCode === 200)) {
|
||||
handleRequest(tagName, searchResults)
|
||||
} else {
|
||||
console.error(`Req failed ${url}; ${response.statusCode}, ${error}`)
|
||||
}
|
||||
removeRunning(tagName)
|
||||
runNext()
|
||||
})
|
||||
}
|
||||
|
||||
var handleRequest = (tagName, searchResults) => {
|
||||
if (searchResults.documents != null) {
|
||||
for (let doc of searchResults.documents) {
|
||||
// MDN groups h1 through h6 under a single "Heading Elements" page
|
||||
if ((doc.url === `${mdnHTMLURL}/${tagName}`) || (/^h\d$/.test(tagName) && (doc.url === `${mdnHTMLURL}/Heading_Elements`))) {
|
||||
if (doc.tags.includes('Obsolete')) {
|
||||
docs[tagName] = `The ${tagName} element is obsolete. Avoid using it and update existing code if possible.`
|
||||
} else if (doc.tags.includes('Deprecated')) {
|
||||
docs[tagName] = `The ${tagName} element is deprecated. Avoid using it and update existing code if possible.`
|
||||
} else {
|
||||
docs[tagName] = filterExcerpt(tagName, doc.excerpt)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log(`Could not find documentation for ${tagName}`)
|
||||
}
|
||||
|
||||
for (let i = 0; i <= MAX; i++) { runNext() }
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
var filterExcerpt = (tagName, excerpt) => {
|
||||
const beginningPattern = /^the html [a-z-]+ element (\([^)]+\) )?(is )?(\w+)/i
|
||||
excerpt = excerpt.replace(beginningPattern, (match) => {
|
||||
const matches = beginningPattern.exec(match)
|
||||
const firstWord = matches[3]
|
||||
return firstWord[0].toUpperCase() + firstWord.slice(1)
|
||||
})
|
||||
const periodIndex = excerpt.indexOf('.')
|
||||
if (periodIndex > -1) { excerpt = excerpt.slice(0, periodIndex + 1) }
|
||||
return excerpt
|
||||
}
|
||||
|
||||
// Save a file if run from the command line
|
||||
if (require.main === module) {
|
||||
fetch().then((docs) => {
|
||||
if (docs != null) {
|
||||
fs.writeFileSync(path.join(__dirname, 'tag-docs.json'), `${JSON.stringify(docs, null, ' ')}\n`)
|
||||
} else {
|
||||
console.error('No docs')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = fetch
|
@ -109,7 +109,7 @@ function getTagAttributes (tag) {
|
||||
}
|
||||
|
||||
function getLocalAttributeDocsURL (attribute, tag) {
|
||||
return `${getTagDocsURL(tag)}#attr-${attribute}`
|
||||
return `${getTagDocsURL(tag)}#attributes`
|
||||
}
|
||||
|
||||
function getGlobalAttributeDocsURL (attribute) {
|
||||
|
23961
packages/autocomplete-html/package-lock.json
generated
23961
packages/autocomplete-html/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -8,6 +8,10 @@
|
||||
"engines": {
|
||||
"atom": ">=0.174.0 <2.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"update": "node ./update/update.js",
|
||||
"update:test": "jest ./update/update.test.js"
|
||||
},
|
||||
"providedServices": {
|
||||
"autocomplete.provider": {
|
||||
"versions": {
|
||||
@ -16,6 +20,12 @@
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"request": "^2.53.0"
|
||||
"@webref/elements": "^2.1.0",
|
||||
"chrome-devtools-frontend": "^1.0.1070764",
|
||||
"content": "github:mdn/content",
|
||||
"esm": "^3.2.25",
|
||||
"jest": "^29.4.3",
|
||||
"joi": "^17.8.3",
|
||||
"ts-import": "^2.0.40"
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,26 @@ describe('HTML autocompletions', () => {
|
||||
return provider.getSuggestions({editor, bufferPosition, scopeDescriptor, prefix})
|
||||
}
|
||||
|
||||
function isValueInCompletions(value, array, attribute) {
|
||||
attribute ??= 'text'
|
||||
let result = [];
|
||||
for (const i of array) {
|
||||
result.push(i[attribute]);
|
||||
}
|
||||
return result.includes(value);
|
||||
}
|
||||
|
||||
function getValueInCompletionsIndex(value, array, attribute) {
|
||||
attribute ??= 'text';
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
if (array[i][attribute] === value) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
// We never did find the value in our array
|
||||
return -1;
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
waitsForPromise(() => atom.packages.activatePackage('autocomplete-html'))
|
||||
waitsForPromise(() => atom.packages.activatePackage('language-html'))
|
||||
@ -59,9 +79,9 @@ describe('HTML autocompletions', () => {
|
||||
editor.setCursorBufferPosition([0, 1])
|
||||
|
||||
const completions = getCompletions()
|
||||
expect(completions.length).toBe(113)
|
||||
expect(completions[0].description).toContain('Creates a hyperlink to other web pages')
|
||||
expect(completions[0].descriptionMoreURL.endsWith('/HTML/Element/a')).toBe(true)
|
||||
expect(completions.length).toBeGreaterThan(113) // Fun Fact last check this was 232
|
||||
expect(completions[0].description.length).toBeGreaterThan(0)
|
||||
expect(completions[0].descriptionMoreURL.length).toBeGreaterThan(0)
|
||||
|
||||
for (let completion of completions) {
|
||||
expect(completion.text.length).toBeGreaterThan(0)
|
||||
@ -75,36 +95,35 @@ describe('HTML autocompletions', () => {
|
||||
editor.setCursorBufferPosition([0, 2])
|
||||
|
||||
let completions = getCompletions()
|
||||
expect(completions.length).toBe(9)
|
||||
expect(completions.length).toBeGreaterThan(9) // Fun fact last check was 14
|
||||
|
||||
expect(completions[0].text).toBe('datalist')
|
||||
expect(isValueInCompletions('datalist', completions)).toBe(true)
|
||||
expect(completions[0].type).toBe('tag')
|
||||
expect(completions[0].descriptionMoreURL.endsWith('/HTML/Element/datalist')).toBe(true)
|
||||
expect(completions[1].text).toBe('dd')
|
||||
expect(completions[2].text).toBe('del')
|
||||
expect(completions[3].text).toBe('details')
|
||||
expect(completions[4].text).toBe('dfn')
|
||||
expect(completions[5].text).toBe('dialog')
|
||||
expect(completions[6].text).toBe('div')
|
||||
expect(completions[7].text).toBe('dl')
|
||||
expect(completions[8].text).toBe('dt')
|
||||
expect(isValueInCompletions('dd', completions)).toBe(true)
|
||||
expect(isValueInCompletions('del', completions)).toBe(true)
|
||||
expect(isValueInCompletions('details', completions)).toBe(true)
|
||||
expect(isValueInCompletions('dfn', completions)).toBe(true)
|
||||
expect(isValueInCompletions('dialog', completions)).toBe(true)
|
||||
expect(isValueInCompletions('div', completions)).toBe(true)
|
||||
expect(isValueInCompletions('dl', completions)).toBe(true)
|
||||
expect(isValueInCompletions('dt', completions)).toBe(true)
|
||||
|
||||
editor.setText('<D')
|
||||
editor.setCursorBufferPosition([0, 2])
|
||||
|
||||
completions = getCompletions()
|
||||
expect(completions.length).toBe(9)
|
||||
expect(completions.length).toBeGreaterThan(9) // Fun fact last check was 14
|
||||
|
||||
expect(completions[0].text).toBe('datalist')
|
||||
expect(isValueInCompletions('datalist', completions)).toBe(true)
|
||||
expect(completions[0].type).toBe('tag')
|
||||
expect(completions[1].text).toBe('dd')
|
||||
expect(completions[2].text).toBe('del')
|
||||
expect(completions[3].text).toBe('details')
|
||||
expect(completions[4].text).toBe('dfn')
|
||||
expect(completions[5].text).toBe('dialog')
|
||||
expect(completions[6].text).toBe('div')
|
||||
expect(completions[7].text).toBe('dl')
|
||||
expect(completions[8].text).toBe('dt')
|
||||
expect(isValueInCompletions('dd', completions)).toBe(true)
|
||||
expect(isValueInCompletions('del', completions)).toBe(true)
|
||||
expect(isValueInCompletions('details', completions)).toBe(true)
|
||||
expect(isValueInCompletions('dfn', completions)).toBe(true)
|
||||
expect(isValueInCompletions('dialog', completions)).toBe(true)
|
||||
expect(isValueInCompletions('div', completions)).toBe(true)
|
||||
expect(isValueInCompletions('dl', completions)).toBe(true)
|
||||
expect(isValueInCompletions('dt', completions)).toBe(true)
|
||||
})
|
||||
|
||||
it("does not autocomplete tag names if there's a space after the <", () => {
|
||||
@ -122,15 +141,16 @@ describe('HTML autocompletions', () => {
|
||||
})
|
||||
|
||||
it('does not provide a descriptionMoreURL if the tag does not have a unique description', () => {
|
||||
// ilayer does not have an associated MDN page as of April 27, 2017
|
||||
// isindex does not have an associated MDN page as of March 25, 2023
|
||||
editor.setText('<i')
|
||||
editor.setCursorBufferPosition([0, 2])
|
||||
|
||||
const completions = getCompletions()
|
||||
const loc = getValueInCompletionsIndex('isindex', completions)
|
||||
|
||||
expect(completions[2].text).toBe('ilayer')
|
||||
expect(completions[2].description).toBe('HTML <ilayer> tag')
|
||||
expect(completions[2].descriptionMoreURL).toBeNull()
|
||||
expect(isValueInCompletions('isindex', completions)).toBe(true)
|
||||
expect(completions[loc].description).toBe("HTML <isindex> tag")
|
||||
expect(completions[loc].descriptionMoreURL).toBeNull()
|
||||
})
|
||||
|
||||
it('autocompletes attribute names without a prefix', () => {
|
||||
@ -138,9 +158,9 @@ describe('HTML autocompletions', () => {
|
||||
editor.setCursorBufferPosition([0, 5])
|
||||
|
||||
let completions = getCompletions()
|
||||
expect(completions.length).toBe(86)
|
||||
expect(completions[0].description).toContain('Provides a hint for generating a keyboard shortcut')
|
||||
expect(completions[0].descriptionMoreURL.endsWith('/HTML/Global_attributes/accesskey')).toBe(true)
|
||||
expect(completions.length).toBeGreaterThan(86) // Fun fact last check this was 264
|
||||
expect(completions[0].description.length).toBeGreaterThan(0)
|
||||
expect(completions[0].descriptionMoreURL.length).toBeGreaterThan(0)
|
||||
|
||||
for (var completion of completions) {
|
||||
expect(completion.snippet.length).toBeGreaterThan(0)
|
||||
@ -153,9 +173,9 @@ describe('HTML autocompletions', () => {
|
||||
editor.setCursorBufferPosition([0, 9])
|
||||
|
||||
completions = getCompletions()
|
||||
expect(completions.length).toBe(98)
|
||||
expect(completions.length).toBeGreaterThan(98) // Last check 274
|
||||
expect(completions[0].rightLabel).toBe('<marquee>')
|
||||
expect(completions[0].descriptionMoreURL.endsWith('/HTML/Element/marquee#attr-align')).toBe(true)
|
||||
expect(completions[0].descriptionMoreURL.endsWith('/HTML/Element/marquee#attributes')).toBe(true)
|
||||
|
||||
for (completion of completions) {
|
||||
expect(completion.snippet.length).toBeGreaterThan(0)
|
||||
@ -186,57 +206,59 @@ describe('HTML autocompletions', () => {
|
||||
editor.setCursorBufferPosition([0, 6])
|
||||
|
||||
let completions = getCompletions()
|
||||
expect(completions.length).toBe(3)
|
||||
expect(completions.length).toBeGreaterThan(3) // Last check 9
|
||||
|
||||
expect(completions[0].snippet).toBe('class="$1"$0')
|
||||
expect(completions[0].displayText).toBe('class')
|
||||
expect(completions[0].type).toBe('attribute')
|
||||
expect(completions[1].displayText).toBe('contenteditable')
|
||||
expect(completions[2].displayText).toBe('contextmenu')
|
||||
let loc = getValueInCompletionsIndex('class', completions, 'displayText')
|
||||
|
||||
expect(completions[loc].snippet).toBe('class="$1"$0')
|
||||
expect(completions[loc].displayText).toBe('class')
|
||||
expect(completions[loc].type).toBe('attribute')
|
||||
expect(isValueInCompletions('contenteditable', completions, 'displayText'))
|
||||
expect(isValueInCompletions('contextmenu', completions, 'displayText'))
|
||||
|
||||
editor.setText('<div C')
|
||||
editor.setCursorBufferPosition([0, 6])
|
||||
|
||||
completions = getCompletions()
|
||||
expect(completions.length).toBe(3)
|
||||
expect(completions.length).toBeGreaterThan(3) // Last check 9
|
||||
|
||||
expect(completions[0].displayText).toBe('class')
|
||||
expect(completions[1].displayText).toBe('contenteditable')
|
||||
expect(completions[2].displayText).toBe('contextmenu')
|
||||
expect(isValueInCompletions('class', completions, 'displayText'))
|
||||
expect(isValueInCompletions('contenteditable', completions, 'displayText'))
|
||||
expect(isValueInCompletions('contextmenu', completions, 'displayText'))
|
||||
|
||||
editor.setText('<div c>')
|
||||
editor.setCursorBufferPosition([0, 6])
|
||||
|
||||
completions = getCompletions()
|
||||
expect(completions.length).toBe(3)
|
||||
expect(completions.length).toBeGreaterThan(3)
|
||||
|
||||
expect(completions[0].displayText).toBe('class')
|
||||
expect(completions[1].displayText).toBe('contenteditable')
|
||||
expect(completions[2].displayText).toBe('contextmenu')
|
||||
expect(isValueInCompletions('class', completions, 'displayText'))
|
||||
expect(isValueInCompletions('contenteditable', completions, 'displayText'))
|
||||
expect(isValueInCompletions('contextmenu', completions, 'displayText'))
|
||||
|
||||
editor.setText('<div c></div>')
|
||||
editor.setCursorBufferPosition([0, 6])
|
||||
|
||||
completions = getCompletions()
|
||||
expect(completions.length).toBe(3)
|
||||
expect(completions.length).toBeGreaterThan(3)
|
||||
|
||||
expect(completions[0].displayText).toBe('class')
|
||||
expect(completions[1].displayText).toBe('contenteditable')
|
||||
expect(completions[2].displayText).toBe('contextmenu')
|
||||
expect(isValueInCompletions('class', completions, 'displayText'))
|
||||
expect(isValueInCompletions('contenteditable', completions, 'displayText'))
|
||||
expect(isValueInCompletions('contextmenu', completions, 'displayText'))
|
||||
|
||||
editor.setText('<marquee di')
|
||||
editor.setCursorBufferPosition([0, 12])
|
||||
|
||||
completions = getCompletions()
|
||||
expect(completions[0].displayText).toBe('direction')
|
||||
expect(completions[1].displayText).toBe('dir')
|
||||
expect(isValueInCompletions('direction', completions, 'displayText'))
|
||||
expect(isValueInCompletions('dir', completions, 'displayText'))
|
||||
|
||||
editor.setText('<marquee dI')
|
||||
editor.setCursorBufferPosition([0, 12])
|
||||
|
||||
completions = getCompletions()
|
||||
expect(completions[0].displayText).toBe('direction')
|
||||
expect(completions[1].displayText).toBe('dir')
|
||||
expect(isValueInCompletions('direction', completions, 'displayText'))
|
||||
expect(isValueInCompletions('dir', completions, 'displayText'))
|
||||
})
|
||||
|
||||
it('autocompletes attribute names without a prefix surrounded by whitespace', () => {
|
||||
@ -245,7 +267,7 @@ describe('HTML autocompletions', () => {
|
||||
|
||||
const completions = getCompletions()
|
||||
for (let completion of completions) { expect(completion.type).toBe('attribute') }
|
||||
expect(completions[0].displayText).toBe('autofocus')
|
||||
expect(isValueInCompletions('autofocus', completions, 'displayText'))
|
||||
})
|
||||
|
||||
it('autocompletes attribute names with a prefix surrounded by whitespace', () => {
|
||||
@ -254,7 +276,7 @@ describe('HTML autocompletions', () => {
|
||||
|
||||
const completions = getCompletions()
|
||||
for (let completion of completions) { expect(completion.type).toBe('attribute') }
|
||||
expect(completions[0].displayText).toBe('onabort')
|
||||
expect(isValueInCompletions('onabort', completions, 'displayText'))
|
||||
})
|
||||
|
||||
it("respects the 'flag' type when autocompleting attribute names", () => {
|
||||
@ -262,7 +284,7 @@ describe('HTML autocompletions', () => {
|
||||
editor.setCursorBufferPosition([0, 8])
|
||||
|
||||
const completions = getCompletions()
|
||||
expect(completions[0].snippet).toBe('autofocus')
|
||||
expect(isValueInCompletions('autofocus', completions, 'snippet'))
|
||||
})
|
||||
|
||||
it('does not autocomplete attribute names in between an attribute name and value', () => {
|
||||
@ -308,7 +330,7 @@ describe('HTML autocompletions', () => {
|
||||
editor.setCursorBufferPosition([0, 6])
|
||||
|
||||
const completions = getCompletions()
|
||||
expect(completions[0].displayText).toBe('onafterprint')
|
||||
expect(isValueInCompletions('onafterprint', completions, 'displayText'))
|
||||
})
|
||||
|
||||
it('does not provide a descriptionMoreURL if the attribute does not have a unique description', () => {
|
||||
@ -317,9 +339,11 @@ describe('HTML autocompletions', () => {
|
||||
|
||||
const completions = getCompletions()
|
||||
|
||||
expect(completions[0].displayText).toBe('onabort')
|
||||
expect(completions[0].description).toBe('Global onabort attribute')
|
||||
expect(completions[0].descriptionMoreURL).toBeNull()
|
||||
const loc = getValueInCompletionsIndex('onabort', completions, 'displayText')
|
||||
|
||||
expect(completions[loc].displayText).toBe('onabort')
|
||||
expect(completions[loc].description).toBe('Global onabort attribute')
|
||||
expect(completions[loc].descriptionMoreURL).toBeNull()
|
||||
})
|
||||
|
||||
it('autocompletes attribute values without a prefix', () => {
|
||||
@ -332,7 +356,7 @@ describe('HTML autocompletions', () => {
|
||||
expect(completions[0].text).toBe('scroll')
|
||||
expect(completions[0].type).toBe('value')
|
||||
expect(completions[0].description.length).toBeGreaterThan(0)
|
||||
expect(completions[0].descriptionMoreURL.endsWith('/HTML/Element/marquee#attr-behavior')).toBe(true)
|
||||
expect(completions[0].descriptionMoreURL.endsWith('/HTML/Element/marquee#attributes')).toBe(true)
|
||||
expect(completions[1].text).toBe('slide')
|
||||
expect(completions[2].text).toBe('alternate')
|
||||
|
||||
@ -419,7 +443,7 @@ describe('HTML autocompletions', () => {
|
||||
expect(completions[0].text).toBe('button')
|
||||
expect(completions[0].type).toBe('value')
|
||||
expect(completions[0].description.length).toBeGreaterThan(0)
|
||||
expect(completions[0].descriptionMoreURL.endsWith('/HTML/Element/button#attr-type')).toBe(true)
|
||||
expect(completions[0].descriptionMoreURL.endsWith('/HTML/Element/button#attributes')).toBe(true)
|
||||
expect(completions[1].text).toBe('reset')
|
||||
expect(completions[2].text).toBe('submit')
|
||||
|
||||
@ -433,7 +457,7 @@ describe('HTML autocompletions', () => {
|
||||
expect(completions[0].text).toBe('alternate')
|
||||
expect(completions[0].type).toBe('value')
|
||||
expect(completions[0].description.length).toBeGreaterThan(0)
|
||||
expect(completions[0].descriptionMoreURL.endsWith('/HTML/Element/link#attr-rel')).toBe(true)
|
||||
expect(completions[0].descriptionMoreURL.endsWith('/HTML/Element/link#attributes')).toBe(true)
|
||||
})
|
||||
|
||||
it("provides 'true' and 'false' suggestions when autocompleting boolean attributes", () => {
|
||||
|
@ -1,73 +0,0 @@
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
const request = require('request')
|
||||
const fetchTagDescriptions = require('./fetch-tag-docs')
|
||||
const fetchGlobalAttributeDescriptions = require('./fetch-global-attribute-docs')
|
||||
|
||||
const TagsURL = 'https://raw.githubusercontent.com/adobe/brackets/master/src/extensions/default/HTMLCodeHints/HtmlTags.json'
|
||||
const AttributesURL = 'https://raw.githubusercontent.com/adobe/brackets/master/src/extensions/default/HTMLCodeHints/HtmlAttributes.json'
|
||||
|
||||
const tagsPromise = new Promise((resolve) => {
|
||||
request({json: true, url: TagsURL}, (error, response, tags) => {
|
||||
if (error != null) {
|
||||
console.error(error.message)
|
||||
resolve(null)
|
||||
}
|
||||
|
||||
if (response.statusCode !== 200) {
|
||||
console.error(`Request for HtmlTags.json failed: ${response.statusCode}`)
|
||||
resolve(null)
|
||||
}
|
||||
|
||||
for (let tag in tags) {
|
||||
const options = tags[tag]
|
||||
if ((options.attributes != null ? options.attributes.length : undefined) === 0) { delete options.attributes }
|
||||
}
|
||||
|
||||
resolve(tags)
|
||||
})
|
||||
})
|
||||
|
||||
const tagDescriptionsPromise = fetchTagDescriptions()
|
||||
|
||||
const attributesPromise = new Promise((resolve) => {
|
||||
return request({json: true, url: AttributesURL}, (error, response, attributes) => {
|
||||
if (error != null) {
|
||||
console.error(error.message)
|
||||
resolve(null)
|
||||
}
|
||||
|
||||
if (response.statusCode !== 200) {
|
||||
console.error(`Request for HtmlAttributes.json failed: ${response.statusCode}`)
|
||||
resolve(null)
|
||||
}
|
||||
|
||||
for (let attribute in attributes) {
|
||||
const options = attributes[attribute]
|
||||
if ((options.attribOption != null ? options.attribOption.length : undefined) === 0) { delete options.attribOption }
|
||||
}
|
||||
|
||||
resolve(attributes)
|
||||
})
|
||||
})
|
||||
|
||||
const globalAttributeDescriptionsPromise = fetchGlobalAttributeDescriptions()
|
||||
|
||||
Promise.all([tagsPromise, tagDescriptionsPromise, attributesPromise, globalAttributeDescriptionsPromise]).then((values) => {
|
||||
const tags = values[0]
|
||||
const tagDescriptions = values[1]
|
||||
const attributes = values[2]
|
||||
const attributeDescriptions = values[3]
|
||||
|
||||
for (let tag in tags) {
|
||||
tags[tag].description = tagDescriptions[tag]
|
||||
}
|
||||
|
||||
for (let attribute in attributes) {
|
||||
const options = attributes[attribute]
|
||||
if (options.global) { attributes[attribute].description = attributeDescriptions[attribute] }
|
||||
}
|
||||
|
||||
const completions = {tags, attributes}
|
||||
fs.writeFileSync(path.join(__dirname, 'completions.json'), `${JSON.stringify(completions, null, ' ')}\n`)
|
||||
})
|
44
packages/autocomplete-html/update/chromium-elements-shim.js
Normal file
44
packages/autocomplete-html/update/chromium-elements-shim.js
Normal file
@ -0,0 +1,44 @@
|
||||
require = require("esm")(module)
|
||||
module.exports = require("./chromium-elements-shim.mjs")
|
||||
|
||||
/**
|
||||
Used to aid in the automatic updating of our `completions.json`
|
||||
|
||||
So while this, coupled with `chromium-elements-shim.mjs` is a rather ugly solution
|
||||
heres why this exists:
|
||||
|
||||
Essentially, we need the values declared within `DOMPinnedProperties.ts`.
|
||||
Those values will be essential in creating our final `completions.json`
|
||||
|
||||
But the problems with using this file in the regular JavaScript the `update.js`
|
||||
file is created in are three fold:
|
||||
|
||||
1) This file, obviously, is TypeScript
|
||||
2) This file is an ECMAScript Module
|
||||
3) While this is a TypeScript file there's no `./dist` folder or compiled version
|
||||
readily available, instead relying on custom built tooling to transpile.
|
||||
|
||||
So while this solution is ugly, essentially we make it so we can simply
|
||||
`require()` this file within our JavaScript.
|
||||
|
||||
We literally use `const chromiumElementsShim = require("./chromium-elements-shim.js");`
|
||||
Then just have to call `chromiumElementsShim.bootstrap()` to transpile the file.
|
||||
|
||||
We require this file, which then loads `esm` which allows us to import a ESM
|
||||
module in a CommonJS module by making a small shim. Exporting it as CommonJS.
|
||||
|
||||
But the file is still TypeScript. So we use `ts-import` in `chromium-elements-shim.mjs`
|
||||
to read the file from disk, and transpile it, finally exporting the async function
|
||||
that actually does the transpiling.
|
||||
|
||||
So from here we import that module, and shim it.
|
||||
|
||||
A word of caution, we are specifically using the older version of `ts-import`
|
||||
because this was the version that was used before the developer upgraded the Node
|
||||
version supported. Which beyond this they use `node:fs` to import the FS Module,
|
||||
which isn't supported on the version of Pulsar we are currently using.
|
||||
|
||||
So once we upgrade our version of NodeJS we will also be able to upgrade `ts-import`
|
||||
to it's latest version. That will also mean we have to rewrite `chromium-elements-shim.mjs`
|
||||
as the API has completely changed since the specific version we are using.
|
||||
*/
|
11
packages/autocomplete-html/update/chromium-elements-shim.mjs
Normal file
11
packages/autocomplete-html/update/chromium-elements-shim.mjs
Normal file
@ -0,0 +1,11 @@
|
||||
import * as tsImport from 'ts-import';
|
||||
|
||||
export const bootstrap = async () => {
|
||||
const filePath = "./node_modules/chrome-devtools-frontend/front_end/models/javascript_metadata/DOMPinnedProperties.ts";
|
||||
const compiled = await tsImport.default.tsImport.compile(filePath);
|
||||
|
||||
return compiled;
|
||||
};
|
||||
|
||||
// Used to aid in the automated process of updating `completions.json`
|
||||
// More information in `chromium-elements-shim.js`
|
1328
packages/autocomplete-html/update/curated-attributes.json
Normal file
1328
packages/autocomplete-html/update/curated-attributes.json
Normal file
File diff suppressed because it is too large
Load Diff
267
packages/autocomplete-html/update/update.js
Normal file
267
packages/autocomplete-html/update/update.js
Normal file
@ -0,0 +1,267 @@
|
||||
/**
|
||||
This file will manage the updating of `autocomplete-html` `completions.json`
|
||||
We will partially utilize `@webref/elements` `.listAll()` function that returns
|
||||
a full list of HTML Elements along with a defined `interface`.
|
||||
To use this `interface` in any meaningful way, we will utilize the dataset
|
||||
of Attributes that apply to each `interface` from Chromiums DevTools resource
|
||||
`https://github.com/ChromeDevTools/devtools-frontend`.
|
||||
Finally from here we will utilize `https://github.com/mdn/content` to parse
|
||||
the Markdown docs of MDN's website to retreive descriptions for each element.
|
||||
|
||||
Now for a summary of our `completions.json` file we aim to generate.
|
||||
There are two top level elements, `tags` and `attributes`, both objects.
|
||||
Within `tags` we expect the following:
|
||||
"tags": {
|
||||
"a": {
|
||||
"attributes": [ "href", "hreflang", "media", "rel", "target", "type" ],
|
||||
"description": "....."
|
||||
}
|
||||
};
|
||||
|
||||
When an entry contains no `attributes` there is no empty array, the element
|
||||
simply doesn't exist.
|
||||
|
||||
The `attributes` object contains keys of different elements that themselves
|
||||
are objects that can contain several valid keys.
|
||||
- global: Seems to be used exclusively for Global Attributes. Is a boolean
|
||||
which when false, the key does not appear.
|
||||
- type: A ?type? for the attribute. It's meaning is not immediately known.
|
||||
Nor a way to reliabley programatically collect it. Some discovered values:
|
||||
* cssStyle: Exclusively used for `class` attribute
|
||||
* boolean: Attributes that only accept `true` or `false`
|
||||
* flag: For attributes that don't require or accept values. eg autoplay
|
||||
* cssId: Exclusively used for the `id` attribute
|
||||
* color: Exclusively used for the `bgcolor` attribute
|
||||
* style: Exclusively used for the `style` attribute
|
||||
- description: A text description of the attribute
|
||||
- attribOption: A string array of valid values that can exist within the attribute.
|
||||
Such as the case with `rel` where only so many valid options exist.
|
||||
|
||||
Although with our data sources mentioned above, we are able to collect nearly
|
||||
all the data needed. Except the `type` that is defined within our
|
||||
`completions.json` as well as the `attribOption` within our completions.
|
||||
|
||||
Studying these closer reveals that all attributes listing with our `completions.json`
|
||||
do not appear elsewhere, and are nearly all global attributes.
|
||||
|
||||
In this case since there is no sane way to collect this data, we will leave this
|
||||
list as a manually maintained section of our `completions.json`.
|
||||
This does mean that `curated-attributes.json` is a static document that
|
||||
will require manual updating in the future. Or most ideally, will find a way
|
||||
to automatically generate the needed data.
|
||||
*/
|
||||
|
||||
const chromiumElementsShim = require("./chromium-elements-shim.js");
|
||||
const curatedAttributes = require("./curated-attributes.json");
|
||||
const validate = require("./validate.js");
|
||||
const elements = require("@webref/elements");
|
||||
const fs = require("fs");
|
||||
|
||||
let GLOBAL_ATTRIBUTES = [];
|
||||
|
||||
async function update() {
|
||||
const chromiumElements = await chromiumElementsShim.bootstrap();
|
||||
const htmlElementsRaw = await elements.listAll();
|
||||
|
||||
// Lets then validate our current data
|
||||
if (!validate.htmlElementsRaw(htmlElementsRaw)) {
|
||||
console.log(validate.htmlElementsRaw(htmlElementsRaw));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Then validate the devtools data
|
||||
if (!validate.devToolsDom(chromiumElements.DOMPinnedProperties)) {
|
||||
console.log(validate.devToolsDom(chromiumElements.DOMPinnedProperties));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const fullArrayHtmlElements = buildHtmlElementsArray(htmlElementsRaw);
|
||||
|
||||
const tagsWithAttrs = matchElementInterface(fullArrayHtmlElements, chromiumElements.DOMPinnedProperties);
|
||||
// tagsWithAttrs gives us an already built object for tags. Including their description.
|
||||
|
||||
// Like mentioned in the docs above, instead of manually curate and organize
|
||||
// several aspects of the attributes portion of the file
|
||||
// we will simply just insert the manually curated file itself, allowing for any
|
||||
// change to occur to it later on as needed, as no good data source specifies
|
||||
// what is needed to create it.
|
||||
|
||||
const completion = {
|
||||
tags: tagsWithAttrs,
|
||||
attributes: curatedAttributes
|
||||
};
|
||||
|
||||
// Now to write our file
|
||||
fs.writeFileSync("./completions.json", JSON.stringify(completion, null, 2));
|
||||
|
||||
// Now to check if our attributes contains all of the global values that we saw.
|
||||
const missingGlobals = confirmGlobals(curatedAttributes, GLOBAL_ATTRIBUTES);
|
||||
|
||||
if (missingGlobals.length > 0) {
|
||||
console.log(missingGlobals);
|
||||
console.log("Above are the globals found during updating that do not exist in Curated Attributes.");
|
||||
console.log(`Total Missing Global Attributes: ${missingGlobals.length}`);
|
||||
}
|
||||
|
||||
console.log("Updated all `autocomplete-html` completions.");
|
||||
}
|
||||
|
||||
function confirmGlobals(have, want) {
|
||||
// have is an object, meanwhile want's is an array
|
||||
let result = [];
|
||||
|
||||
for (const w of want) {
|
||||
if (typeof have[w] !== "object") {
|
||||
result.push(w);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function buildHtmlElementsArray(elements) {
|
||||
let elementArray = [];
|
||||
|
||||
for (const spec in elements) {
|
||||
if (Array.isArray(elements[spec].elements)) {
|
||||
for (const ele of elements[spec].elements) {
|
||||
elementArray.push(ele);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return elementArray;
|
||||
}
|
||||
|
||||
function matchElementInterface(elements, domProperties) {
|
||||
let outElements = {};
|
||||
|
||||
for (const ele of elements) {
|
||||
let tags = resolveElementInterfaceAttrs(ele, domProperties);
|
||||
|
||||
outElements[ele.name] = {};
|
||||
|
||||
if (tags.length > 0) {
|
||||
outElements[ele.name].attributes = tags;
|
||||
}
|
||||
|
||||
let desc = getElementDescription(ele.name);
|
||||
outElements[ele.name].description = desc;
|
||||
|
||||
}
|
||||
|
||||
return outElements;
|
||||
}
|
||||
|
||||
function resolveElementInterfaceAttrs(element, domProperties) {
|
||||
let attrs = [];
|
||||
let interfaceArray = [];
|
||||
|
||||
if (typeof element.interface === "string") {
|
||||
interfaceArray.push(element.interface);
|
||||
}
|
||||
|
||||
// Now to loop through every interface and resolve the tags within.
|
||||
while (interfaceArray.length > 0) {
|
||||
let inter = interfaceArray[0];
|
||||
|
||||
if (domProperties[inter]) {
|
||||
// First add all immediate props
|
||||
for (const prop in domProperties[inter].props) {
|
||||
if (!domProperties[inter].props[prop].global && inter !== "GlobalEventHandlers") {
|
||||
// Seems that according to the previous completions.json
|
||||
// We don't want to include any global values in our individual attributes
|
||||
attrs.push(prop);
|
||||
} else {
|
||||
// We don't want global attributes on our actual completions. But do want them tracked
|
||||
if (!GLOBAL_ATTRIBUTES.includes(prop)) {
|
||||
GLOBAL_ATTRIBUTES.push(prop);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Now resolve any additional interfaces, by adding them to our existing array
|
||||
if (typeof domProperties[inter].inheritance === "string") {
|
||||
interfaceArray.push(domProperties[inter].inheritance);
|
||||
}
|
||||
if (Array.isArray(domProperties[inter].includes)) {
|
||||
interfaceArray = interfaceArray.concat(domProperties[inter].includes);
|
||||
}
|
||||
}
|
||||
// Now we have done everything needed for this one interface to be resolved,
|
||||
// so we can just remove the first element of the array and let the while
|
||||
// loop continue
|
||||
interfaceArray.shift();
|
||||
}
|
||||
|
||||
// Return our final list of attributes
|
||||
return attrs;
|
||||
}
|
||||
|
||||
function getElementDescription(element) {
|
||||
// We will gather a description by checking if there's a document written
|
||||
// on MDN for our Element and then extract a summary from there.
|
||||
|
||||
// Some elements are similar enough they exist in a single folder of a different
|
||||
// name. So we will manually intervein in those cases.
|
||||
|
||||
if (element.match(/^h[1-6]$/)) {
|
||||
element = 'heading_elements';
|
||||
}
|
||||
|
||||
let file;
|
||||
|
||||
// First lets find the file, but when not initially found, we will continue
|
||||
// to search valid locations. Since MDN has content of valid tags seperated
|
||||
// by essentially the spec they exist in.
|
||||
const filePath = [ "html", "svg", "mathml" ].map(path =>
|
||||
`./node_modules/content/files/en-us/web/${path}/element/${element}/index.md`
|
||||
).find(f => fs.existsSync(f));
|
||||
|
||||
if (filePath) {
|
||||
file = fs.readFileSync(filePath, { encoding: "utf8" });
|
||||
}
|
||||
|
||||
if (typeof file === "string") {
|
||||
// Now lets parse the file, as long as it was assigned at some point in
|
||||
// the above checks.
|
||||
|
||||
// This logic is largely borrowed from `autocomplete-css` update.js Which does the same thing.
|
||||
let breaks = file.split("---");
|
||||
|
||||
// The first two breaks should be the yaml metadata block
|
||||
let data = breaks[2].replace(/\{\{\S+\}\}\{\{\S+\}\}/gm, "")
|
||||
.replace(/\{\{HTMLSidebar\}\}/gm, "") // Used when within `web/html`
|
||||
.replace(/\{\{SVGRef\}\}/gm, "") // used when within `web/svg`
|
||||
.replace(/\{\{MathMLRef\}\}/gm, ""); // used when within `web/mathml`
|
||||
|
||||
let summaryRaw = data.split("\n");
|
||||
// In case the first few lines is an empty line break
|
||||
for (let i = 0; i < summaryRaw.length; i++) {
|
||||
if (summaryRaw[i].length > 1) {
|
||||
return sanitizeDescription(summaryRaw[i]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No proper file was ever found. Return an empty description
|
||||
return "";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function sanitizeDescription(input) {
|
||||
return input
|
||||
.replace(/\{\{\S+\("(\S+)"\)\}\}/g, '$1')
|
||||
// ^ Parses special MDN Markdown Links.
|
||||
// eg. {{htmlattrxref("title")}} => title
|
||||
// Where we still want to keep the text within
|
||||
.replace(/[\*\`\{\}\"]/g, "") // Removes special Markdown based characters
|
||||
.replace(/\[([A-Za-z0-9-_* ]+)\]\(\S+\)/g, '$1');
|
||||
// ^ Parses Markdown links, extracting only the linked text
|
||||
// eg. [HTML](/en-US/docs/Web/HTML) => HTML
|
||||
}
|
||||
|
||||
update();
|
||||
|
||||
module.exports = {
|
||||
sanitizeDescription,
|
||||
};
|
34
packages/autocomplete-html/update/update.test.js
Normal file
34
packages/autocomplete-html/update/update.test.js
Normal file
@ -0,0 +1,34 @@
|
||||
/**
|
||||
This file aims to run some short simple tests against `update.js`. Focusing
|
||||
mainly on the Regex used within `sanitizeDescription()`
|
||||
*/
|
||||
|
||||
const update = require("./update.js");
|
||||
|
||||
describe("Parses Descriptions Properly from Markdown", () => {
|
||||
|
||||
test("Extracts Markdown Links text", () => {
|
||||
const text = "Here is my very important [link](https://github.com/pulsar-edit/pulsar)!";
|
||||
|
||||
const out = update.sanitizeDescription(text);
|
||||
|
||||
expect(out).toBe("Here is my very important link!");
|
||||
});
|
||||
|
||||
test("Removes some Markdown characters", () => {
|
||||
const text = "Some *Bolded* text, some **italic** text";
|
||||
|
||||
const out = update.sanitizeDescription(text);
|
||||
|
||||
expect(out).toBe("Some Bolded text, some italic text");
|
||||
});
|
||||
|
||||
test("Extracts Text from MDNs special Links", () => {
|
||||
const text = 'What about {{htmlattrxref("this?")}}';
|
||||
|
||||
const out = update.sanitizeDescription(text);
|
||||
|
||||
expect(out).toBe("What about this?");
|
||||
});
|
||||
|
||||
});
|
77
packages/autocomplete-html/update/validate.js
Normal file
77
packages/autocomplete-html/update/validate.js
Normal file
@ -0,0 +1,77 @@
|
||||
const Joi = require("joi");
|
||||
|
||||
function htmlElementsRaw(obj) {
|
||||
// Function to validate structure of the `@webref/elements` `.listAll()`
|
||||
|
||||
const innerSchema = Joi.object({
|
||||
"spec": Joi.object({ // Details on the spec
|
||||
"title": Joi.string().required(), // The title of the spec
|
||||
"url": Joi.string().required(), // URL to the definition of the spec
|
||||
}).required(),
|
||||
"elements": Joi.array().items( // the elements object is an array of objects
|
||||
Joi.object({
|
||||
"name": Joi.string().required(), // The name of the element
|
||||
"interface": Joi.string().optional(), // The optional interface of the element
|
||||
// ^ The interface name will match one from DevTools data.
|
||||
"obsolete": Joi.boolean().truthy().optional()
|
||||
// ^ The optional and uncommon boolean to indicate an element is absolete
|
||||
}).required()
|
||||
).required()
|
||||
});
|
||||
|
||||
let valid = true;
|
||||
let error = [];
|
||||
|
||||
for (const ele in obj) {
|
||||
// The first key is the name of the spec the element is apart of.
|
||||
let validation = innerSchema.validate(obj[ele]);
|
||||
|
||||
if (validation.error) {
|
||||
error.push(validation.error);
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!valid) {
|
||||
console.log(error);
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function devToolsDom(obj) {
|
||||
// Validates the structure of the data returned from Chromiums DevTools
|
||||
// DOMPinnedProperties
|
||||
// Read More: https://github.com/ChromeDevTools/devtools-frontend/blob/main/front_end/models/javascript_metadata/DOMPinnedProperties.ts
|
||||
const innerSchema = Joi.object({
|
||||
"inheritance": Joi.string().optional(), // An interface to inherit
|
||||
"includes": Joi.array().items(Joi.string()), // An array of interfaces included with our current interface
|
||||
"props": Joi.object(), // An object list of properties. Where the key is the property name
|
||||
}).unknown();
|
||||
|
||||
let valid = true;
|
||||
let error = [];
|
||||
|
||||
for (const inter in obj) {
|
||||
// The first key is the name of an interface
|
||||
let validation = innerSchema.validate(obj[inter]);
|
||||
|
||||
if (validation.error) {
|
||||
error.push(validation.error);
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!valid) {
|
||||
console.log(error);
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
htmlElementsRaw,
|
||||
devToolsDom,
|
||||
};
|
@ -51,8 +51,12 @@ export default class PackageSnippetsView {
|
||||
<input id='toggleSnippets' className='input-checkbox' type='checkbox' ref='snippetToggle' />
|
||||
<div className='setting-title'>Enable</div>
|
||||
</label>
|
||||
<div className='setting-description'>
|
||||
{'Disable this if you want to prevent this package’s snippets from appearing as suggestions or if you want to customize them in your snippets file.'}
|
||||
<div className='setting-description' ref='snippetSettingDescription'>
|
||||
<p>Disable this if you want to prevent this package’s snippets from appearing as suggestions or if you want to customize them in your snippets file.</p>
|
||||
|
||||
<p>To <strong>disable</strong> most snippets and <strong>enable</strong> just a few, use the <kbd>Copy</kbd> button on any snippet you want to enable, then paste the result into your own snippets file.</p>
|
||||
|
||||
<p>To <strong>enable</strong> most snippets and <strong>disable</strong> just a few, use the <kbd>Copy</kbd> button on any snippet you want to disable, paste the result into your own snippets file, and change the body to <code>null</code>.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -60,6 +64,7 @@ export default class PackageSnippetsView {
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Trigger</th>
|
||||
<th ref="headingCommand">Command</th>
|
||||
<th>Name</th>
|
||||
<th>Scope</th>
|
||||
<th>Body</th>
|
||||
@ -73,13 +78,12 @@ export default class PackageSnippetsView {
|
||||
|
||||
getSnippetProperties () {
|
||||
const packageProperties = {}
|
||||
for (const {name, properties, selectorString} of this.snippetsProvider.getSnippets()) {
|
||||
for (const {name, properties} of this.snippetsProvider.getSnippets()) {
|
||||
if (name && name.indexOf && name.indexOf(this.packagePath) === 0) {
|
||||
const object = properties.snippets != null ? properties.snippets : {}
|
||||
for (let key in object) {
|
||||
const snippet = object[key]
|
||||
if (snippet != null) {
|
||||
snippet.selectorString = selectorString
|
||||
if (packageProperties[key] == null) {
|
||||
packageProperties[key] = snippet
|
||||
}
|
||||
@ -116,13 +120,15 @@ export default class PackageSnippetsView {
|
||||
this.getSnippets((snippets) => {
|
||||
this.refs.snippets.innerHTML = ''
|
||||
|
||||
let anyWithCommand = snippets.some(s => ('command' in s))
|
||||
|
||||
if (snippetsDisabled) {
|
||||
this.refs.snippets.classList.add('text-subtle')
|
||||
} else {
|
||||
this.refs.snippets.classList.remove('text-subtle')
|
||||
}
|
||||
|
||||
for (let {body, bodyText, name, prefix, selectorString} of snippets) {
|
||||
for (let {body, bodyText, command, name, packageName, prefix, selector} of snippets) {
|
||||
if (name == null) {
|
||||
name = ''
|
||||
}
|
||||
@ -135,8 +141,13 @@ export default class PackageSnippetsView {
|
||||
body = bodyText || ''
|
||||
}
|
||||
|
||||
if (selectorString == null) {
|
||||
selectorString = ''
|
||||
if (selector == null) {
|
||||
selector = ''
|
||||
}
|
||||
|
||||
let commandName = ''
|
||||
if (packageName && command) {
|
||||
commandName = `${packageName}:${command}`
|
||||
}
|
||||
|
||||
const row = document.createElement('tr')
|
||||
@ -146,13 +157,18 @@ export default class PackageSnippetsView {
|
||||
prefixTd.textContent = prefix
|
||||
row.appendChild(prefixTd)
|
||||
|
||||
const commandTd = document.createElement('td')
|
||||
commandTd.textContent = commandName
|
||||
row.appendChild(commandTd)
|
||||
commandTd.style.display = anyWithCommand ? '' : 'none'
|
||||
|
||||
const nameTd = document.createElement('td')
|
||||
nameTd.textContent = name
|
||||
row.appendChild(nameTd)
|
||||
|
||||
const scopeTd = document.createElement('td')
|
||||
scopeTd.classList.add('snippet-scope-name')
|
||||
scopeTd.textContent = selectorString
|
||||
scopeTd.textContent = selector
|
||||
row.appendChild(scopeTd)
|
||||
|
||||
const bodyTd = document.createElement('td')
|
||||
@ -160,7 +176,7 @@ export default class PackageSnippetsView {
|
||||
row.appendChild(bodyTd)
|
||||
|
||||
this.refs.snippets.appendChild(row)
|
||||
this.createButtonsForSnippetRow(bodyTd, {body, prefix, scope: selectorString, name})
|
||||
this.createButtonsForSnippetRow(bodyTd, {body, prefix, scope: selector, name, command})
|
||||
}
|
||||
|
||||
if (this.refs.snippets.children.length > 0) {
|
||||
@ -168,10 +184,14 @@ export default class PackageSnippetsView {
|
||||
} else {
|
||||
this.element.style.display = 'none'
|
||||
}
|
||||
|
||||
// The “Command” column should only be shown if at least one snippet is
|
||||
// mapped to a command name.
|
||||
this.refs.headingCommand.style.display = anyWithCommand ? '' : 'none'
|
||||
})
|
||||
}
|
||||
|
||||
createButtonsForSnippetRow (td, {scope, body, name, prefix}) {
|
||||
createButtonsForSnippetRow (td, {scope, body, name, prefix, command}) {
|
||||
let buttonContainer = document.createElement('div')
|
||||
buttonContainer.classList.add('btn-group', 'btn-group-xs')
|
||||
|
||||
@ -198,7 +218,7 @@ export default class PackageSnippetsView {
|
||||
|
||||
copyButton.addEventListener('click', (event) => {
|
||||
event.preventDefault()
|
||||
return this.writeSnippetToClipboard({scope, body, name, prefix})
|
||||
return this.writeSnippetToClipboard({scope, body, name, prefix, command})
|
||||
})
|
||||
|
||||
buttonContainer.appendChild(viewButton)
|
||||
@ -207,24 +227,39 @@ export default class PackageSnippetsView {
|
||||
td.appendChild(buttonContainer)
|
||||
}
|
||||
|
||||
writeSnippetToClipboard ({scope, body, name, prefix}) {
|
||||
writeSnippetToClipboard ({scope, body, name, prefix, command}) {
|
||||
let content
|
||||
const extension = path.extname(this.snippetsProvider.getUserSnippetsPath())
|
||||
body = body.replace(/\n/g, '\\n').replace(/\t/g, '\\t')
|
||||
// Either `prefix` or `command` will be present, or else both. Only copy
|
||||
// the values that are present.
|
||||
let triggers = []
|
||||
if (extension === '.cson') {
|
||||
if (prefix) {
|
||||
triggers.push(` 'prefix': '${prefix}'`)
|
||||
}
|
||||
if (command) {
|
||||
triggers.push(` 'command': '${command}'`)
|
||||
}
|
||||
body = body.replace(/'/g, `\\'`)
|
||||
content = `
|
||||
'${scope}':
|
||||
'${name}':
|
||||
'prefix': '${prefix}'
|
||||
${triggers.join('\n')}
|
||||
'body': '${body}'
|
||||
`
|
||||
} else {
|
||||
if (prefix) {
|
||||
triggers.push(` "prefix": "${prefix}"`)
|
||||
}
|
||||
if (command) {
|
||||
triggers.push(` "command": "${command}"`)
|
||||
}
|
||||
body = body.replace(/"/g, `\\"`)
|
||||
content = `
|
||||
"${scope}": {
|
||||
"${name}": {
|
||||
"prefix": "${prefix}",
|
||||
${triggers.join(',\n')}
|
||||
"body": "${body}"
|
||||
}
|
||||
}
|
||||
|
14
packages/settings-view/spec/.eslintrc.js
Normal file
14
packages/settings-view/spec/.eslintrc.js
Normal file
@ -0,0 +1,14 @@
|
||||
module.exports = {
|
||||
env: {
|
||||
es2021: true,
|
||||
jasmine: true,
|
||||
node: true
|
||||
},
|
||||
globals: {
|
||||
waitsForPromise: true
|
||||
},
|
||||
rules: {
|
||||
"node/no-missing-require": "off",
|
||||
"semi": ["error", "always"]
|
||||
}
|
||||
};
|
@ -2,6 +2,7 @@
|
||||
".source.b": {
|
||||
"BAR": {
|
||||
"prefix": "b",
|
||||
"command": "sample-command",
|
||||
"body": "bar?\nline two"
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
".source.a": {
|
||||
".source.a, .source.aa": {
|
||||
"FOO": {
|
||||
"prefix": "f",
|
||||
"body": "foo!"
|
||||
|
@ -1,338 +0,0 @@
|
||||
path = require 'path'
|
||||
PackageDetailView = require '../lib/package-detail-view'
|
||||
PackageManager = require '../lib/package-manager'
|
||||
SettingsView = require '../lib/settings-view'
|
||||
PackageKeymapView = require '../lib/package-keymap-view'
|
||||
PackageSnippetsView = require '../lib/package-snippets-view'
|
||||
_ = require 'underscore-plus'
|
||||
SnippetsProvider =
|
||||
getSnippets: -> atom.config.scopedSettingsStore.propertySets
|
||||
|
||||
describe "InstalledPackageView", ->
|
||||
beforeEach ->
|
||||
spyOn(PackageManager.prototype, 'loadCompatiblePackageVersion').andCallFake ->
|
||||
|
||||
it "displays the grammars registered by the package", ->
|
||||
settingsPanels = null
|
||||
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage(path.join(__dirname, 'fixtures', 'language-test'))
|
||||
|
||||
runs ->
|
||||
pack = atom.packages.getActivePackage('language-test')
|
||||
view = new PackageDetailView(pack, new SettingsView(), new PackageManager(), SnippetsProvider)
|
||||
settingsPanels = view.element.querySelectorAll('.package-grammars .settings-panel')
|
||||
|
||||
waitsFor ->
|
||||
children = Array.from(settingsPanels).map((s) -> s.children.length)
|
||||
childrenCount = children.reduce(((a, b) -> a + b), 0)
|
||||
childrenCount is 2
|
||||
|
||||
expect(settingsPanels[0].querySelector('.grammar-scope').textContent).toBe 'Scope: source.a'
|
||||
expect(settingsPanels[0].querySelector('.grammar-filetypes').textContent).toBe 'File Types: .a, .aa, a'
|
||||
|
||||
expect(settingsPanels[1].querySelector('.grammar-scope').textContent).toBe 'Scope: source.b'
|
||||
expect(settingsPanels[1].querySelector('.grammar-filetypes').textContent).toBe 'File Types: '
|
||||
|
||||
expect(settingsPanels[2]).toBeUndefined()
|
||||
|
||||
it "displays the snippets registered by the package", ->
|
||||
snippetsTable = null
|
||||
snippetsModule = null
|
||||
|
||||
# Relies on behavior not present in the snippets package before 1.33.
|
||||
# TODO: These tests should always run once 1.33 is released.
|
||||
shouldRunScopeTest = parseFloat(atom.getVersion()) >= 1.33
|
||||
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage(path.join(__dirname, 'fixtures', 'language-test'))
|
||||
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage('snippets').then (p) ->
|
||||
snippetsModule = p.mainModule
|
||||
return unless snippetsModule.provideSnippets().getUnparsedSnippets?
|
||||
|
||||
SnippetsProvider =
|
||||
getSnippets: -> snippetsModule.provideSnippets().getUnparsedSnippets()
|
||||
|
||||
waitsFor 'snippets to load', -> snippetsModule.provideSnippets().bundledSnippetsLoaded()
|
||||
|
||||
runs ->
|
||||
pack = atom.packages.getActivePackage('language-test')
|
||||
view = new PackageDetailView(pack, new SettingsView(), new PackageManager(), SnippetsProvider)
|
||||
snippetsTable = view.element.querySelector('.package-snippets-table tbody')
|
||||
|
||||
waitsFor 'snippets table children to contain 2 items', ->
|
||||
snippetsTable.children.length >= 2
|
||||
|
||||
runs ->
|
||||
expect(snippetsTable.querySelector('tr:nth-child(1) td:nth-child(1)').textContent).toBe 'b'
|
||||
expect(snippetsTable.querySelector('tr:nth-child(1) td:nth-child(2)').textContent).toBe 'BAR'
|
||||
expect(snippetsTable.querySelector('tr:nth-child(1) td.snippet-scope-name').textContent).toBe '.b.source' if shouldRunScopeTest
|
||||
|
||||
expect(snippetsTable.querySelector('tr:nth-child(2) td:nth-child(1)').textContent).toBe 'f'
|
||||
expect(snippetsTable.querySelector('tr:nth-child(2) td:nth-child(2)').textContent).toBe 'FOO'
|
||||
expect(snippetsTable.querySelector('tr:nth-child(2) td.snippet-scope-name').textContent).toBe '.a.source' if shouldRunScopeTest
|
||||
|
||||
describe "when a snippet body is viewed", ->
|
||||
it "shows a tooltip", ->
|
||||
tooltipCalls = []
|
||||
view = null
|
||||
snippetsTable = null
|
||||
snippetsModule = null
|
||||
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage(path.join(__dirname, 'fixtures', 'language-test'))
|
||||
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage('snippets').then (p) ->
|
||||
snippetsModule = p.mainModule
|
||||
return unless snippetsModule.provideSnippets().getUnparsedSnippets?
|
||||
|
||||
SnippetsProvider =
|
||||
getSnippets: -> snippetsModule.provideSnippets().getUnparsedSnippets()
|
||||
|
||||
waitsFor 'snippets to load', -> snippetsModule.provideSnippets().bundledSnippetsLoaded()
|
||||
|
||||
runs ->
|
||||
pack = atom.packages.getActivePackage('language-test')
|
||||
view = new PackageDetailView(pack, new SettingsView(), new PackageManager(), SnippetsProvider)
|
||||
snippetsTable = view.element.querySelector('.package-snippets-table tbody')
|
||||
|
||||
waitsFor 'snippets table children to contain 2 items', ->
|
||||
snippetsTable.children.length >= 2
|
||||
|
||||
runs ->
|
||||
expect(view.element.ownerDocument.querySelector('.snippet-body-tooltip')).not.toExist()
|
||||
|
||||
view.element.querySelector('.package-snippets-table tbody tr:nth-child(1) td.snippet-body .snippet-view-btn').click()
|
||||
expect(view.element.ownerDocument.querySelector('.snippet-body-tooltip')).toExist()
|
||||
|
||||
|
||||
# Relies on behavior not present in the snippets package before 1.33.
|
||||
# TODO: These tests should always run once 1.33 is released.
|
||||
if parseFloat(atom.getVersion()) >= 1.33
|
||||
describe "when a snippet is copied", ->
|
||||
[pack, card] = []
|
||||
snippetsTable = null
|
||||
snippetsModule = null
|
||||
|
||||
beforeEach ->
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage(path.join(__dirname, 'fixtures', 'language-test'))
|
||||
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage('snippets').then (p) ->
|
||||
snippetsModule = p.mainModule
|
||||
return unless snippetsModule.provideSnippets().getUnparsedSnippets?
|
||||
|
||||
SnippetsProvider =
|
||||
getSnippets: -> snippetsModule.provideSnippets().getUnparsedSnippets()
|
||||
getUserSnippetsPath: snippetsModule.getUserSnippetsPath()
|
||||
|
||||
waitsFor 'snippets to load', -> snippetsModule.provideSnippets().bundledSnippetsLoaded()
|
||||
|
||||
runs ->
|
||||
pack = atom.packages.getActivePackage('language-test')
|
||||
card = new PackageSnippetsView(pack, SnippetsProvider)
|
||||
snippetsTable = card.element.querySelector('.package-snippets-table tbody')
|
||||
|
||||
waitsFor 'snippets table children to contain 2 items', ->
|
||||
snippetsTable.children.length >= 2
|
||||
|
||||
describe "when the snippets file ends in .cson", ->
|
||||
it "writes a CSON snippet to the clipboard", ->
|
||||
spyOn(SnippetsProvider, 'getUserSnippetsPath').andReturn('snippets.cson')
|
||||
card.element.querySelector('.package-snippets-table tbody tr:nth-child(1) td.snippet-body .snippet-copy-btn').click()
|
||||
expect(atom.clipboard.read()).toBe """
|
||||
\n'.b.source':
|
||||
'BAR':
|
||||
'prefix': 'b'
|
||||
'body': 'bar?\\nline two'\n
|
||||
"""
|
||||
|
||||
describe "when the snippets file ends in .json", ->
|
||||
it "writes a JSON snippet to the clipboard", ->
|
||||
spyOn(SnippetsProvider, 'getUserSnippetsPath').andReturn('snippets.json')
|
||||
card.element.querySelector('.package-snippets-table tbody tr:nth-child(1) td.snippet-body .btn:nth-child(2)').click()
|
||||
expect(atom.clipboard.read()).toBe """
|
||||
\n ".b.source": {
|
||||
"BAR": {
|
||||
"prefix": "b",
|
||||
"body": "bar?\\nline two"
|
||||
}
|
||||
}\n
|
||||
"""
|
||||
|
||||
describe "when the snippets toggle is clicked", ->
|
||||
it "sets the packagesWithSnippetsDisabled config to include the package name", ->
|
||||
[pack, card] = []
|
||||
snippetsModule = []
|
||||
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage(path.join(__dirname, 'fixtures', 'language-test'))
|
||||
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage('snippets').then (p) ->
|
||||
snippetsModule = p.mainModule
|
||||
return unless snippetsModule.provideSnippets().getUnparsedSnippets?
|
||||
|
||||
SnippetsProvider =
|
||||
getSnippets: -> snippetsModule.provideSnippets().getUnparsedSnippets()
|
||||
|
||||
waitsFor 'snippets to load', -> snippetsModule.provideSnippets().bundledSnippetsLoaded()
|
||||
|
||||
runs ->
|
||||
pack = atom.packages.getActivePackage('language-test')
|
||||
card = new PackageSnippetsView(pack, SnippetsProvider)
|
||||
jasmine.attachToDOM(card.element)
|
||||
|
||||
card.refs.snippetToggle.click()
|
||||
expect(card.refs.snippetToggle.checked).toBe false
|
||||
expect(_.include(atom.config.get('core.packagesWithSnippetsDisabled') ? [], 'language-test')).toBe true
|
||||
|
||||
waitsFor 'snippets table to update', ->
|
||||
card.refs.snippets.classList.contains('text-subtle')
|
||||
|
||||
runs ->
|
||||
card.refs.snippetToggle.click()
|
||||
expect(card.refs.snippetToggle.checked).toBe true
|
||||
expect(_.include(atom.config.get('core.packagesWithSnippetsDisabled') ? [], 'language-test')).toBe false
|
||||
|
||||
waitsFor 'snippets table to update', ->
|
||||
not card.refs.snippets.classList.contains('text-subtle')
|
||||
|
||||
it "does not display keybindings from other platforms", ->
|
||||
keybindingsTable = null
|
||||
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage(path.join(__dirname, 'fixtures', 'language-test'))
|
||||
|
||||
runs ->
|
||||
pack = atom.packages.getActivePackage('language-test')
|
||||
view = new PackageDetailView(pack, new SettingsView(), new PackageManager(), SnippetsProvider)
|
||||
keybindingsTable = view.element.querySelector('.package-keymap-table tbody')
|
||||
expect(keybindingsTable.children.length).toBe 1
|
||||
|
||||
describe "when the keybindings toggle is clicked", ->
|
||||
it "sets the packagesWithKeymapsDisabled config to include the package name", ->
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage(path.join(__dirname, 'fixtures', 'language-test'))
|
||||
|
||||
runs ->
|
||||
pack = atom.packages.getActivePackage('language-test')
|
||||
card = new PackageKeymapView(pack)
|
||||
jasmine.attachToDOM(card.element)
|
||||
|
||||
card.refs.keybindingToggle.click()
|
||||
expect(card.refs.keybindingToggle.checked).toBe false
|
||||
expect(_.include(atom.config.get('core.packagesWithKeymapsDisabled') ? [], 'language-test')).toBe true
|
||||
|
||||
if atom.keymaps.build?
|
||||
keybindingRows = card.element.querySelectorAll('.package-keymap-table tbody.text-subtle tr')
|
||||
expect(keybindingRows.length).toBe 1
|
||||
|
||||
card.refs.keybindingToggle.click()
|
||||
expect(card.refs.keybindingToggle.checked).toBe true
|
||||
expect(_.include(atom.config.get('core.packagesWithKeymapsDisabled') ? [], 'language-test')).toBe false
|
||||
|
||||
if atom.keymaps.build?
|
||||
keybindingRows = card.element.querySelectorAll('.package-keymap-table tbody tr')
|
||||
expect(keybindingRows.length).toBe 1
|
||||
|
||||
describe "when a keybinding is copied", ->
|
||||
[pack, card] = []
|
||||
|
||||
beforeEach ->
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage(path.join(__dirname, 'fixtures', 'language-test'))
|
||||
|
||||
runs ->
|
||||
pack = atom.packages.getActivePackage('language-test')
|
||||
card = new PackageKeymapView(pack)
|
||||
|
||||
describe "when the keybinding file ends in .cson", ->
|
||||
it "writes a CSON snippet to the clipboard", ->
|
||||
spyOn(atom.keymaps, 'getUserKeymapPath').andReturn 'keymap.cson'
|
||||
card.element.querySelector('.copy-icon').click()
|
||||
expect(atom.clipboard.read()).toBe """
|
||||
'test':
|
||||
'cmd-g': 'language-test:run'
|
||||
"""
|
||||
|
||||
describe "when the keybinding file ends in .json", ->
|
||||
it "writes a JSON snippet to the clipboard", ->
|
||||
spyOn(atom.keymaps, 'getUserKeymapPath').andReturn 'keymap.json'
|
||||
card.element.querySelector('.copy-icon').click()
|
||||
expect(atom.clipboard.read()).toBe """
|
||||
"test": {
|
||||
"cmd-g": "language-test:run"
|
||||
}
|
||||
"""
|
||||
|
||||
describe "when the package is active", ->
|
||||
it "displays the correct enablement state", ->
|
||||
packageCard = null
|
||||
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage('status-bar')
|
||||
|
||||
runs ->
|
||||
expect(atom.packages.isPackageActive('status-bar')).toBe(true)
|
||||
pack = atom.packages.getLoadedPackage('status-bar')
|
||||
view = new PackageDetailView(pack, new SettingsView(), new PackageManager(), SnippetsProvider)
|
||||
packageCard = view.element.querySelector('.package-card')
|
||||
|
||||
runs ->
|
||||
# Trigger observeDisabledPackages() here
|
||||
# because it is not default in specs
|
||||
atom.packages.observeDisabledPackages()
|
||||
atom.packages.disablePackage('status-bar')
|
||||
expect(atom.packages.isPackageDisabled('status-bar')).toBe(true)
|
||||
expect(packageCard.classList.contains('disabled')).toBe(true)
|
||||
|
||||
describe "when the package is not active", ->
|
||||
it "displays the correct enablement state", ->
|
||||
atom.packages.loadPackage('status-bar')
|
||||
expect(atom.packages.isPackageActive('status-bar')).toBe(false)
|
||||
pack = atom.packages.getLoadedPackage('status-bar')
|
||||
view = new PackageDetailView(pack, new SettingsView(), new PackageManager(), SnippetsProvider)
|
||||
packageCard = view.element.querySelector('.package-card')
|
||||
|
||||
# Trigger observeDisabledPackages() here
|
||||
# because it is not default in specs
|
||||
atom.packages.observeDisabledPackages()
|
||||
atom.packages.disablePackage('status-bar')
|
||||
expect(atom.packages.isPackageDisabled('status-bar')).toBe(true)
|
||||
expect(packageCard.classList.contains('disabled')).toBe(true)
|
||||
|
||||
it "still loads the config schema for the package", ->
|
||||
atom.packages.loadPackage(path.join(__dirname, 'fixtures', 'package-with-config'))
|
||||
|
||||
waitsFor ->
|
||||
atom.packages.isPackageLoaded('package-with-config') is true
|
||||
|
||||
runs ->
|
||||
expect(atom.config.get('package-with-config.setting')).toBe undefined
|
||||
|
||||
pack = atom.packages.getLoadedPackage('package-with-config')
|
||||
new PackageDetailView(pack, new SettingsView(), new PackageManager(), SnippetsProvider)
|
||||
|
||||
expect(atom.config.get('package-with-config.setting')).toBe 'something'
|
||||
|
||||
describe "when the package was not installed from atom.io", ->
|
||||
normalizePackageDataReadmeError = 'ERROR: No README data found!'
|
||||
|
||||
it "still displays the Readme", ->
|
||||
atom.packages.loadPackage(path.join(__dirname, 'fixtures', 'package-with-readme'))
|
||||
|
||||
waitsFor ->
|
||||
atom.packages.isPackageLoaded('package-with-readme') is true
|
||||
|
||||
runs ->
|
||||
pack = atom.packages.getLoadedPackage('package-with-readme')
|
||||
expect(pack.metadata.readme).toBe normalizePackageDataReadmeError
|
||||
|
||||
view = new PackageDetailView(pack, new SettingsView(), new PackageManager(), SnippetsProvider)
|
||||
expect(view.refs.sections.querySelector('.package-readme').textContent).not.toBe normalizePackageDataReadmeError
|
||||
expect(view.refs.sections.querySelector('.package-readme').textContent.trim()).toContain 'I am a Readme!'
|
517
packages/settings-view/spec/installed-package-view-spec.js
Normal file
517
packages/settings-view/spec/installed-package-view-spec.js
Normal file
@ -0,0 +1,517 @@
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS101: Remove unnecessary use of Array.from
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* DS104: Avoid inline assignments
|
||||
* DS207: Consider shorter variations of null checks
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
|
||||
|
||||
const path = require('path');
|
||||
const PackageDetailView = require('../lib/package-detail-view');
|
||||
const PackageManager = require('../lib/package-manager');
|
||||
const SettingsView = require('../lib/settings-view');
|
||||
const PackageKeymapView = require('../lib/package-keymap-view');
|
||||
const PackageSnippetsView = require('../lib/package-snippets-view');
|
||||
const _ = require('underscore-plus');
|
||||
|
||||
let SnippetsProvider = {
|
||||
getSnippets() {
|
||||
return atom.config.scopedSettingsStore.propertySets;
|
||||
}
|
||||
};
|
||||
|
||||
describe("InstalledPackageView", function() {
|
||||
beforeEach(() => {
|
||||
spyOn(PackageManager.prototype, 'loadCompatiblePackageVersion')
|
||||
.andCallFake(() => {});
|
||||
});
|
||||
|
||||
it("displays the grammars registered by the package", () => {
|
||||
let settingsPanels = null;
|
||||
|
||||
waitsForPromise(() => {
|
||||
return atom.packages.activatePackage(path.join(__dirname, 'fixtures', 'language-test'));
|
||||
});
|
||||
|
||||
runs(() => {
|
||||
const pack = atom.packages.getActivePackage('language-test');
|
||||
const view = new PackageDetailView(pack, new SettingsView(), new PackageManager(), SnippetsProvider);
|
||||
settingsPanels = view.element.querySelectorAll('.package-grammars .settings-panel');
|
||||
|
||||
waitsFor(() => {
|
||||
const children = Array.from(settingsPanels).map(s => s.children.length);
|
||||
const childrenCount = children.reduce(((a, b) => a + b), 0);
|
||||
return childrenCount === 2;
|
||||
});
|
||||
|
||||
expect(
|
||||
settingsPanels[0].querySelector('.grammar-scope').textContent
|
||||
).toBe('Scope: source.a');
|
||||
expect(
|
||||
settingsPanels[0].querySelector('.grammar-filetypes'
|
||||
).textContent).toBe('File Types: .a, .aa, a');
|
||||
|
||||
expect(
|
||||
settingsPanels[1].querySelector('.grammar-scope').textContent
|
||||
).toBe('Scope: source.b');
|
||||
expect(
|
||||
settingsPanels[1].querySelector('.grammar-filetypes').textContent
|
||||
).toBe('File Types: ');
|
||||
|
||||
expect(settingsPanels[2]).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
it("displays the snippets registered by the package", () => {
|
||||
let snippetsTable = null;
|
||||
let snippetsModule = null;
|
||||
|
||||
// Relies on behavior not present in the snippets package before 1.103.
|
||||
const shouldRunScopeTest = parseFloat(atom.getVersion()) >= 1.103;
|
||||
|
||||
waitsForPromise(() => {
|
||||
return atom.packages.activatePackage(path.join(__dirname, 'fixtures', 'language-test'));
|
||||
});
|
||||
|
||||
waitsForPromise(() => {
|
||||
return atom.packages.activatePackage('snippets').then((p) => {
|
||||
snippetsModule = p.mainModule;
|
||||
if (snippetsModule.provideSnippets().getUnparsedSnippets == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
SnippetsProvider = {
|
||||
getSnippets() {
|
||||
return snippetsModule.provideSnippets().getUnparsedSnippets();
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
waitsFor('snippets to load', () => {
|
||||
return snippetsModule.provideSnippets().bundledSnippetsLoaded();
|
||||
});
|
||||
|
||||
runs(() => {
|
||||
const pack = atom.packages.getActivePackage('language-test');
|
||||
const view = new PackageDetailView(pack, new SettingsView(), new PackageManager(), SnippetsProvider);
|
||||
snippetsTable = view.element.querySelector('.package-snippets-table tbody');
|
||||
});
|
||||
|
||||
waitsFor('snippets table children to contain 2 items', () => {
|
||||
return snippetsTable.children.length >= 2;
|
||||
});
|
||||
|
||||
runs(() => {
|
||||
expect(
|
||||
snippetsTable.querySelector('tr:nth-child(1) td:nth-child(1)').textContent
|
||||
).toBe('b');
|
||||
if (shouldRunScopeTest) {
|
||||
expect(
|
||||
snippetsTable.querySelector('tr:nth-child(1) td:nth-child(2)').textContent
|
||||
).toBe('language-test:sample-command');
|
||||
}
|
||||
expect(
|
||||
snippetsTable.querySelector('tr:nth-child(1) td:nth-child(3)'
|
||||
).textContent).toBe('BAR');
|
||||
|
||||
if (shouldRunScopeTest) {
|
||||
expect(
|
||||
snippetsTable.querySelector('tr:nth-child(1) td.snippet-scope-name').textContent
|
||||
).toBe('.source.b');
|
||||
}
|
||||
|
||||
expect(
|
||||
snippetsTable.querySelector('tr:nth-child(2) td:nth-child(1)').textContent
|
||||
).toBe('f');
|
||||
|
||||
if (shouldRunScopeTest) {
|
||||
expect(
|
||||
snippetsTable.querySelector('tr:nth-child(1) td:nth-child(2)').textContent
|
||||
).toBe('');
|
||||
}
|
||||
expect(
|
||||
snippetsTable.querySelector('tr:nth-child(2) td:nth-child(3)').textContent
|
||||
).toBe('FOO');
|
||||
|
||||
if (shouldRunScopeTest) {
|
||||
expect(
|
||||
snippetsTable.querySelector('tr:nth-child(2) td.snippet-scope-name').textContent
|
||||
).toBe('.source.a, .source.aa');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("when a snippet body is viewed", () =>
|
||||
it("shows a tooltip", () => {
|
||||
let view = null;
|
||||
let snippetsTable = null;
|
||||
let snippetsModule = null;
|
||||
|
||||
waitsForPromise(() => {
|
||||
return atom.packages.activatePackage(path.join(__dirname, 'fixtures', 'language-test'));
|
||||
});
|
||||
|
||||
waitsForPromise(() =>
|
||||
atom.packages.activatePackage('snippets').then((p) => {
|
||||
snippetsModule = p.mainModule;
|
||||
if (snippetsModule.provideSnippets().getUnparsedSnippets == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
SnippetsProvider = {
|
||||
getSnippets() {
|
||||
return snippetsModule.provideSnippets().getUnparsedSnippets();
|
||||
}
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
waitsFor('snippets to load', () => {
|
||||
return snippetsModule.provideSnippets().bundledSnippetsLoaded();
|
||||
});
|
||||
|
||||
runs(() => {
|
||||
const pack = atom.packages.getActivePackage('language-test');
|
||||
view = new PackageDetailView(pack, new SettingsView(), new PackageManager(), SnippetsProvider);
|
||||
snippetsTable = view.element.querySelector('.package-snippets-table tbody');
|
||||
});
|
||||
|
||||
waitsFor('snippets table children to contain 2 items', () => snippetsTable.children.length >= 2);
|
||||
|
||||
runs(() => {
|
||||
expect(
|
||||
view.element.ownerDocument.querySelector('.snippet-body-tooltip')
|
||||
).not.toExist();
|
||||
|
||||
view.element.querySelector(
|
||||
'.package-snippets-table tbody tr:nth-child(1) td.snippet-body .snippet-view-btn'
|
||||
).click();
|
||||
|
||||
expect(
|
||||
view.element.ownerDocument.querySelector('.snippet-body-tooltip')
|
||||
).toExist();
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
// Relies on behavior not present in the snippets package before 1.33.
|
||||
// TODO: These tests should always run once 1.33 is released.
|
||||
if (parseFloat(atom.getVersion()) >= 1.33) {
|
||||
describe("when a snippet is copied", () => {
|
||||
let pack, card;
|
||||
let snippetsTable = null;
|
||||
let snippetsModule = null;
|
||||
|
||||
beforeEach(() => {
|
||||
waitsForPromise(() => {
|
||||
return atom.packages.activatePackage(path.join(__dirname, 'fixtures', 'language-test'));
|
||||
});
|
||||
|
||||
waitsForPromise(() => {
|
||||
return atom.packages.activatePackage('snippets').then((p) => {
|
||||
snippetsModule = p.mainModule;
|
||||
if (snippetsModule.provideSnippets().getUnparsedSnippets == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
SnippetsProvider = {
|
||||
getSnippets() {
|
||||
return snippetsModule.provideSnippets().getUnparsedSnippets();
|
||||
},
|
||||
getUserSnippetsPath: () => snippetsModule.getUserSnippetsPath()
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
waitsFor('snippets to load', () => {
|
||||
return snippetsModule.provideSnippets().bundledSnippetsLoaded();
|
||||
});
|
||||
|
||||
runs(() => {
|
||||
pack = atom.packages.getActivePackage('language-test');
|
||||
card = new PackageSnippetsView(pack, SnippetsProvider);
|
||||
snippetsTable = card.element.querySelector('.package-snippets-table tbody');
|
||||
});
|
||||
|
||||
waitsFor('snippets table children to contain 2 items', () => snippetsTable.children.length >= 2);
|
||||
});
|
||||
|
||||
describe("when the snippets file ends in .cson", () =>
|
||||
it("writes a CSON snippet to the clipboard", () => {
|
||||
spyOn(SnippetsProvider, 'getUserSnippetsPath').andReturn('snippets.cson');
|
||||
card.element.querySelector(
|
||||
'.package-snippets-table tbody tr:nth-child(1) td.snippet-body .snippet-copy-btn'
|
||||
).click();
|
||||
expect(atom.clipboard.read()).toBe(`\
|
||||
\n'.b.source':
|
||||
'BAR':
|
||||
'prefix': 'b'
|
||||
'body': 'bar?\\nline two'\n\
|
||||
`
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
describe("when the snippets file ends in .json", () =>
|
||||
it("writes a JSON snippet to the clipboard", () => {
|
||||
spyOn(SnippetsProvider, 'getUserSnippetsPath').andReturn('snippets.json');
|
||||
card.element.querySelector(
|
||||
'.package-snippets-table tbody tr:nth-child(1) td.snippet-body .btn:nth-child(2)'
|
||||
).click();
|
||||
expect(atom.clipboard.read()).toBe(`\
|
||||
\n ".b.source": {
|
||||
"BAR": {
|
||||
"prefix": "b",
|
||||
"body": "bar?\\nline two"
|
||||
}
|
||||
}\n\
|
||||
`
|
||||
);
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
describe("when the snippets toggle is clicked", () =>
|
||||
it("sets the packagesWithSnippetsDisabled config to include the package name", function() {
|
||||
let pack, card;
|
||||
let snippetsModule = null;
|
||||
|
||||
waitsForPromise(() => {
|
||||
return atom.packages.activatePackage(path.join(__dirname, 'fixtures', 'language-test'));
|
||||
});
|
||||
|
||||
waitsForPromise(() =>
|
||||
atom.packages.activatePackage('snippets').then((p) => {
|
||||
snippetsModule = p.mainModule;
|
||||
if (snippetsModule.provideSnippets().getUnparsedSnippets == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
return SnippetsProvider = {
|
||||
getSnippets() {
|
||||
return snippetsModule.provideSnippets().getUnparsedSnippets();
|
||||
}
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
waitsFor('snippets to load', () => {
|
||||
return snippetsModule.provideSnippets().bundledSnippetsLoaded();
|
||||
});
|
||||
|
||||
runs(() => {
|
||||
pack = atom.packages.getActivePackage('language-test');
|
||||
card = new PackageSnippetsView(pack, SnippetsProvider);
|
||||
jasmine.attachToDOM(card.element);
|
||||
|
||||
card.refs.snippetToggle.click();
|
||||
expect(card.refs.snippetToggle.checked).toBe(false);
|
||||
let disabledSnippetsPackages = atom.config.get('core.packagesWithSnippetsDisabled') || [];
|
||||
expect(
|
||||
_.include(disabledSnippetsPackages, 'language-test')
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
waitsFor('snippets table to update', () => {
|
||||
return card.refs.snippets.classList.contains('text-subtle');
|
||||
});
|
||||
|
||||
runs(() => {
|
||||
card.refs.snippetToggle.click();
|
||||
expect(card.refs.snippetToggle.checked).toBe(true);
|
||||
let disabledSnippetsPackages = atom.config.get('core.packagesWithSnippetsDisabled') || [];
|
||||
expect(
|
||||
_.include(disabledSnippetsPackages, 'language-test')
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
waitsFor('snippets table to update', () => {
|
||||
return !card.refs.snippets.classList.contains('text-subtle');
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
it("does not display keybindings from other platforms", () => {
|
||||
let keybindingsTable = null;
|
||||
|
||||
waitsForPromise(() => {
|
||||
return atom.packages.activatePackage(path.join(__dirname, 'fixtures', 'language-test'));
|
||||
});
|
||||
|
||||
runs(() => {
|
||||
const pack = atom.packages.getActivePackage('language-test');
|
||||
const view = new PackageDetailView(pack, new SettingsView(), new PackageManager(), SnippetsProvider);
|
||||
keybindingsTable = view.element.querySelector('.package-keymap-table tbody');
|
||||
expect(keybindingsTable.children.length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when the keybindings toggle is clicked", () =>
|
||||
it("sets the packagesWithKeymapsDisabled config to include the package name", () => {
|
||||
waitsForPromise(() => {
|
||||
return atom.packages.activatePackage(path.join(__dirname, 'fixtures', 'language-test'));
|
||||
});
|
||||
|
||||
runs(() => {
|
||||
let keybindingRows;
|
||||
const pack = atom.packages.getActivePackage('language-test');
|
||||
const card = new PackageKeymapView(pack);
|
||||
jasmine.attachToDOM(card.element);
|
||||
|
||||
card.refs.keybindingToggle.click();
|
||||
expect(card.refs.keybindingToggle.checked).toBe(false);
|
||||
let disabledKeymapsPackages = atom.config.get('core.packagesWithKeymapsDisabled') || [];
|
||||
expect(
|
||||
_.include(disabledKeymapsPackages, 'language-test')
|
||||
).toBe(true);
|
||||
|
||||
if (atom.keymaps.build) {
|
||||
keybindingRows = card.element.querySelectorAll('.package-keymap-table tbody.text-subtle tr');
|
||||
expect(keybindingRows.length).toBe(1);
|
||||
}
|
||||
|
||||
card.refs.keybindingToggle.click();
|
||||
expect(card.refs.keybindingToggle.checked).toBe(true);
|
||||
disabledKeymapsPackages = atom.config.get('core.packagesWithKeymapsDisabled') || [];
|
||||
|
||||
expect(
|
||||
_.include(disabledKeymapsPackages, 'language-test')
|
||||
).toBe(false);
|
||||
|
||||
if (atom.keymaps.build) {
|
||||
keybindingRows = card.element.querySelectorAll('.package-keymap-table tbody tr');
|
||||
expect(keybindingRows.length).toBe(1);
|
||||
}
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
describe("when a keybinding is copied", () => {
|
||||
let [pack, card] = Array.from([]);
|
||||
|
||||
beforeEach(() => {
|
||||
waitsForPromise(() => {
|
||||
return atom.packages.activatePackage(path.join(__dirname, 'fixtures', 'language-test'));
|
||||
});
|
||||
|
||||
runs(() => {
|
||||
pack = atom.packages.getActivePackage('language-test');
|
||||
card = new PackageKeymapView(pack);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when the keybinding file ends in .cson", () =>
|
||||
it("writes a CSON snippet to the clipboard", () => {
|
||||
spyOn(atom.keymaps, 'getUserKeymapPath').andReturn('keymap.cson');
|
||||
card.element.querySelector('.copy-icon').click();
|
||||
expect(atom.clipboard.read()).toBe(`\
|
||||
'test':
|
||||
'cmd-g': 'language-test:run'\
|
||||
`
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
describe("when the keybinding file ends in .json", () => {
|
||||
it("writes a JSON snippet to the clipboard", () => {
|
||||
spyOn(atom.keymaps, 'getUserKeymapPath').andReturn('keymap.json');
|
||||
card.element.querySelector('.copy-icon').click();
|
||||
expect(atom.clipboard.read()).toBe(`\
|
||||
"test": {
|
||||
"cmd-g": "language-test:run"
|
||||
}\
|
||||
`
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("when the package is active", () =>
|
||||
it("displays the correct enablement state", () => {
|
||||
let packageCard = null;
|
||||
|
||||
waitsForPromise(() => {
|
||||
return atom.packages.activatePackage('status-bar');
|
||||
});
|
||||
|
||||
runs(() => {
|
||||
expect(atom.packages.isPackageActive('status-bar')).toBe(true);
|
||||
const pack = atom.packages.getLoadedPackage('status-bar');
|
||||
const view = new PackageDetailView(pack, new SettingsView(), new PackageManager(), SnippetsProvider);
|
||||
packageCard = view.element.querySelector('.package-card');
|
||||
});
|
||||
|
||||
runs(() => {
|
||||
// Trigger observeDisabledPackages() here
|
||||
// because it is not default in specs
|
||||
atom.packages.observeDisabledPackages();
|
||||
atom.packages.disablePackage('status-bar');
|
||||
expect(atom.packages.isPackageDisabled('status-bar')).toBe(true);
|
||||
expect(packageCard.classList.contains('disabled')).toBe(true);
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
describe("when the package is not active", () => {
|
||||
it("displays the correct enablement state", () => {
|
||||
atom.packages.loadPackage('status-bar');
|
||||
expect(atom.packages.isPackageActive('status-bar')).toBe(false);
|
||||
const pack = atom.packages.getLoadedPackage('status-bar');
|
||||
const view = new PackageDetailView(pack, new SettingsView(), new PackageManager(), SnippetsProvider);
|
||||
const packageCard = view.element.querySelector('.package-card');
|
||||
|
||||
// Trigger observeDisabledPackages() here
|
||||
// because it is not default in specs
|
||||
atom.packages.observeDisabledPackages();
|
||||
atom.packages.disablePackage('status-bar');
|
||||
expect(atom.packages.isPackageDisabled('status-bar')).toBe(true);
|
||||
expect(packageCard.classList.contains('disabled')).toBe(true);
|
||||
});
|
||||
|
||||
it("still loads the config schema for the package", () => {
|
||||
atom.packages.loadPackage(path.join(__dirname, 'fixtures', 'package-with-config'));
|
||||
|
||||
waitsFor(() => atom.packages.isPackageLoaded('package-with-config') === true);
|
||||
|
||||
runs(() => {
|
||||
expect(atom.config.get('package-with-config.setting')).toBe(undefined);
|
||||
|
||||
const pack = atom.packages.getLoadedPackage('package-with-config');
|
||||
new PackageDetailView(pack, new SettingsView(), new PackageManager(), SnippetsProvider);
|
||||
|
||||
expect(atom.config.get('package-with-config.setting')).toBe('something');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("when the package was not installed from atom.io", () => {
|
||||
const normalizePackageDataReadmeError = 'ERROR: No README data found!';
|
||||
|
||||
it("still displays the Readme", () => {
|
||||
atom.packages.loadPackage(path.join(__dirname, 'fixtures', 'package-with-readme'));
|
||||
|
||||
waitsFor(() => {
|
||||
return atom.packages.isPackageLoaded('package-with-readme') === true;
|
||||
});
|
||||
|
||||
runs(() => {
|
||||
const pack = atom.packages.getLoadedPackage('package-with-readme');
|
||||
expect(pack.metadata.readme).toBe(normalizePackageDataReadmeError);
|
||||
|
||||
const view = new PackageDetailView(pack, new SettingsView(), new PackageManager(), SnippetsProvider);
|
||||
expect(
|
||||
view.refs.sections.querySelector('.package-readme').textContent
|
||||
).not.toBe(normalizePackageDataReadmeError);
|
||||
expect(
|
||||
view.refs.sections.querySelector('.package-readme').textContent.trim()
|
||||
).toContain('I am a Readme!');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
3
packages/welcome/i18n/en.json
Normal file
3
packages/welcome/i18n/en.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"title": "Welcome"
|
||||
}
|
@ -2,4 +2,6 @@
|
||||
|
||||
import WelcomePackage from './welcome-package';
|
||||
|
||||
export const t = atom.i18n.getT("welcome");
|
||||
|
||||
export default new WelcomePackage();
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
import etch from 'etch';
|
||||
import path from 'path';
|
||||
import { t } from "./main";
|
||||
|
||||
export default class WelcomeView {
|
||||
constructor(props) {
|
||||
@ -126,7 +127,7 @@ export default class WelcomeView {
|
||||
}
|
||||
|
||||
getTitle() {
|
||||
return 'Welcome';
|
||||
return t("title");
|
||||
}
|
||||
|
||||
isEqual(other) {
|
||||
|
@ -37,8 +37,6 @@ async function modifyMainPackageJson(file, extraMetadata, isRemovePackageScripts
|
||||
/// END Monkey-Patch
|
||||
|
||||
const builder = require("electron-builder")
|
||||
const Platform = builder.Platform
|
||||
|
||||
|
||||
const pngIcon = 'resources/app-icons/beta.png'
|
||||
const icoIcon = 'resources/app-icons/beta.ico'
|
||||
@ -55,6 +53,7 @@ let options = {
|
||||
"package.json",
|
||||
"dot-atom/**/*",
|
||||
"exports/**/*",
|
||||
"i18n/**/*",
|
||||
"resources/**/*",
|
||||
"src/**/*",
|
||||
"static/**/*",
|
||||
@ -219,7 +218,7 @@ let options = {
|
||||
],
|
||||
"target": [
|
||||
{ "target": "nsis" },
|
||||
{ "target": "portable" },
|
||||
{ target: "zip" },
|
||||
],
|
||||
},
|
||||
// Windows NSIS Configuration
|
||||
@ -262,7 +261,6 @@ async function main() {
|
||||
let options = whatToBuild()
|
||||
options.extraMetadata = generateMetadata(JSON.parse(package))
|
||||
builder.build({
|
||||
//targets: Platform.LINUX.createTarget(),
|
||||
config: options
|
||||
}).then((result) => {
|
||||
console.log("Built binaries")
|
||||
|
@ -46,6 +46,7 @@ const TextEditorRegistry = require('./text-editor-registry');
|
||||
const AutoUpdateManager = require('./auto-update-manager');
|
||||
const StartupTime = require('./startup-time');
|
||||
const getReleaseChannel = require('./get-release-channel');
|
||||
const I18n = require("./i18n");
|
||||
const packagejson = require("../package.json");
|
||||
|
||||
const stat = util.promisify(fs.stat);
|
||||
@ -125,6 +126,11 @@ class AtomEnvironment {
|
||||
/** @type {StyleManager} */
|
||||
this.styles = new StyleManager();
|
||||
|
||||
this.i18n = new I18n({
|
||||
notificationManager: this.notifications,
|
||||
config: this.config
|
||||
});
|
||||
|
||||
/** @type {PackageManager} */
|
||||
this.packages = new PackageManager({
|
||||
config: this.config,
|
||||
@ -135,7 +141,8 @@ class AtomEnvironment {
|
||||
grammarRegistry: this.grammars,
|
||||
deserializerManager: this.deserializers,
|
||||
viewRegistry: this.views,
|
||||
uriHandlerRegistry: this.uriHandlerRegistry
|
||||
uriHandlerRegistry: this.uriHandlerRegistry,
|
||||
i18n: this.i18n
|
||||
});
|
||||
|
||||
/** @type {ThemeManager} */
|
||||
@ -149,6 +156,7 @@ class AtomEnvironment {
|
||||
|
||||
/** @type {MenuManager} */
|
||||
this.menu = new MenuManager({
|
||||
i18n: this.i18n,
|
||||
keymapManager: this.keymaps,
|
||||
packageManager: this.packages
|
||||
});
|
||||
@ -274,6 +282,19 @@ class AtomEnvironment {
|
||||
this.project.replace(projectSpecification);
|
||||
}
|
||||
|
||||
this.packages.initialize({
|
||||
devMode,
|
||||
configDirPath: this.configDirPath,
|
||||
resourcePath,
|
||||
safeMode
|
||||
});
|
||||
|
||||
this.i18n.initialize({
|
||||
configDirPath: this.configDirPath,
|
||||
packages: this.packages,
|
||||
resourcePath
|
||||
});
|
||||
|
||||
this.menu.initialize({ resourcePath });
|
||||
this.contextMenu.initialize({ resourcePath, devMode });
|
||||
|
||||
@ -287,12 +308,6 @@ class AtomEnvironment {
|
||||
this.commands.attach(this.window);
|
||||
|
||||
this.styles.initialize({ configDirPath: this.configDirPath });
|
||||
this.packages.initialize({
|
||||
devMode,
|
||||
configDirPath: this.configDirPath,
|
||||
resourcePath,
|
||||
safeMode
|
||||
});
|
||||
this.themes.initialize({
|
||||
configDirPath: this.configDirPath,
|
||||
resourcePath,
|
||||
|
154
src/i18n-helpers.js
Normal file
154
src/i18n-helpers.js
Normal file
@ -0,0 +1,154 @@
|
||||
const _ = require('underscore-plus');
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const { parse } = require("@formatjs/icu-messageformat-parser");
|
||||
|
||||
class I18nCacheHelper {
|
||||
constructor({ configDirPath, i18n }) {
|
||||
/**
|
||||
* cachedASTs[ns][lang] = string objs
|
||||
* (same shape as registeredStrings but with ASTs instead of strings)
|
||||
*/
|
||||
this.cachedASTs = {};
|
||||
/** @type {string} */
|
||||
this.configDirPath = configDirPath;
|
||||
this.i18n = i18n;
|
||||
|
||||
this.loadCaches();
|
||||
|
||||
this.debouncedCleanAndSave = _.debounce(() => {
|
||||
this.cleanCaches(this.i18n.registeredStrings);
|
||||
this.saveCaches();
|
||||
}, 5_000);
|
||||
}
|
||||
|
||||
fetchAST(ns, _path, str, lang) {
|
||||
let path = [ns, lang, ..._path];
|
||||
|
||||
let ast = optionalTravelDownObjectPath(
|
||||
this.cachedASTs,
|
||||
path
|
||||
);
|
||||
if (ast && "_AST" in ast) return ast._AST;
|
||||
|
||||
ast = parse(str, {
|
||||
// requiresOtherClause
|
||||
});
|
||||
|
||||
let lastBit = path.pop();
|
||||
let cachePath = travelDownOrMakePath(this.cachedASTs, path);
|
||||
cachePath[lastBit] = { _AST: ast };
|
||||
|
||||
this.debouncedCleanAndSave();
|
||||
|
||||
return ast;
|
||||
}
|
||||
|
||||
/**
|
||||
* go through `this.cachedASTs`, find stuff that doesn't exist in `registeredStrings`,
|
||||
* then yeet them
|
||||
*/
|
||||
cleanCaches(registeredStrings, cachedASTs) {
|
||||
if (!cachedASTs) cachedASTs = this.cachedASTs;
|
||||
|
||||
Object.entries(cachedASTs).forEach(([k, cachedValue]) => {
|
||||
let registeredValue = registeredStrings[k];
|
||||
|
||||
// path doesn't exist
|
||||
if (!registeredValue) {
|
||||
delete cachedASTs[k];
|
||||
return;
|
||||
}
|
||||
|
||||
// path is an object
|
||||
if (typeof registeredValue === "object") {
|
||||
// cached is not AST (plain obj) (good)
|
||||
if (
|
||||
typeof cachedValue === "object"
|
||||
&& !("_AST" in cachedValue)
|
||||
) {
|
||||
this.cleanCaches(registeredValue, cachedValue);
|
||||
return;
|
||||
}
|
||||
// cached is AST (bad)
|
||||
delete cachedASTs[k];
|
||||
return;
|
||||
}
|
||||
|
||||
// path is a string
|
||||
if (typeof registeredValue === "string") {
|
||||
// cached is AST (good)
|
||||
if ("_AST" in cachedValue) return;
|
||||
// cached is not AST (bad)
|
||||
delete cachedASTs[k];
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
saveCaches() {
|
||||
let cachedir = path.join(
|
||||
this.configDirPath,
|
||||
"compile-cache",
|
||||
"i18n"
|
||||
);
|
||||
fs.mkdirSync(cachedir, { recursive: true });
|
||||
|
||||
let cachefile = path.join(cachedir, "strings.json");
|
||||
fs.writeFileSync(cachefile, JSON.stringify(this.cachedASTs));
|
||||
}
|
||||
|
||||
loadCaches() {
|
||||
let cachefile = path.join(
|
||||
this.configDirPath,
|
||||
"compile-cache",
|
||||
"i18n",
|
||||
"strings.json"
|
||||
);
|
||||
if (fs.existsSync(cachefile)) {
|
||||
this.cachedASTs = JSON.parse(fs.readFileSync(cachefile, "utf-8"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function walkStrings(strings, cb, accum = []) {
|
||||
Object.entries(strings).forEach(([k, v]) => {
|
||||
let path = [...accum, k];
|
||||
if (typeof v === "string") cb(path, v, true);
|
||||
else if (typeof v === "object") {
|
||||
cb(path, null, false);
|
||||
walkStrings(v, cb, path);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function travelDownObjectPath(obj, path) {
|
||||
for (const pathFragment of path) {
|
||||
obj = obj[pathFragment];
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
function optionalTravelDownObjectPath(obj, path) {
|
||||
for (const pathFragment of path) {
|
||||
obj = obj[pathFragment];
|
||||
if (!obj) return undefined;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
function travelDownOrMakePath(obj, path) {
|
||||
for (const pathFragment of path) {
|
||||
if (!obj[pathFragment]) obj[pathFragment] = {};
|
||||
obj = obj[pathFragment];
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
I18nCacheHelper,
|
||||
walkStrings,
|
||||
travelDownObjectPath,
|
||||
optionalTravelDownObjectPath,
|
||||
travelDownOrMakePath
|
||||
};
|
253
src/i18n.js
Normal file
253
src/i18n.js
Normal file
@ -0,0 +1,253 @@
|
||||
const { splitKeyPath } = require("key-path-helpers");
|
||||
const fs = require("fs-plus");
|
||||
const path = require("path");
|
||||
const { default: IntlMessageFormat } = require("intl-messageformat");
|
||||
const {
|
||||
I18nCacheHelper,
|
||||
walkStrings,
|
||||
travelDownObjectPath,
|
||||
optionalTravelDownObjectPath,
|
||||
travelDownOrMakePath
|
||||
} = require("./i18n-helpers");
|
||||
|
||||
class I18n {
|
||||
constructor({ notificationManager, config }) {
|
||||
this.notificationManager = notificationManager;
|
||||
this.config = config;
|
||||
this.initialized = false;
|
||||
|
||||
/** registeredStrings[ns][lang] = string objs */
|
||||
this.registeredStrings = { core: {} };
|
||||
this.cachedFormatters = {};
|
||||
}
|
||||
|
||||
initialize({ configDirPath, packages, resourcePath }) {
|
||||
/** @type {string} */
|
||||
this.configDirPath = configDirPath;
|
||||
this.packages = packages;
|
||||
/** @type {string} */
|
||||
this.resourcePath = resourcePath;
|
||||
/** @type {I18nCacheHelper} */
|
||||
this.cacheHelper = new I18nCacheHelper({ configDirPath, i18n: this });
|
||||
|
||||
const ext = ".json";
|
||||
const extlen = ext.length;
|
||||
const dirpath = path.join(resourcePath, "i18n");
|
||||
const dircontents = fs.readdirSync(dirpath);
|
||||
|
||||
let languageTypes = dircontents.filter(p => p.endsWith(ext))
|
||||
.map(p => p.substring(0, p.length - extlen))
|
||||
.map(p => ({
|
||||
value: p,
|
||||
description: `${new Intl.DisplayNames(p, { type: "language" }).of(p)} (${p})`
|
||||
}));
|
||||
|
||||
this.config.setSchema("core.languageSettings", {
|
||||
type: "object",
|
||||
description: "These settings currently require a full restart of Pulsar to take effect.",
|
||||
properties: {
|
||||
primaryLanguage: {
|
||||
type: "string",
|
||||
order: 1,
|
||||
default: "en",
|
||||
enum: languageTypes
|
||||
},
|
||||
fallbackLanguages: {
|
||||
type: "array",
|
||||
order: 2,
|
||||
description: "List of fallback languages, if something can't be found in the primary language. Note; `en` is always the last fallback language, to ensure that things at least show up.",
|
||||
default: [],
|
||||
items: {
|
||||
// Array enum is meh, if you pause for the briefest moment and you
|
||||
// didn't stop at a valid enum value, the entry you just typed gets yeeted
|
||||
type: "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.updateConfigs();
|
||||
|
||||
// Preload languages (getting an obj would place it in the cache)
|
||||
// TODO reassess preloading the "en" fallback when we have more progress on translations
|
||||
this.getCoreLanguage(this.primaryLanguage);
|
||||
this.getCoreLanguage("en");
|
||||
this.packages.onDidActivatePackage(pkg => {
|
||||
this.getPkgLanguage(pkg.name, this.primaryLanguage);
|
||||
this.getPkgLanguage(pkg.name, "en");
|
||||
});
|
||||
|
||||
this.packages.onDidDeactivatePackage(pkg => {
|
||||
if (pkg.name in this.registeredStrings) {
|
||||
delete this.registeredStrings[pkg.name];
|
||||
}
|
||||
});
|
||||
|
||||
this.t = (key, opts) => {
|
||||
const path = splitKeyPath(key);
|
||||
const languagesToTry = [
|
||||
this.primaryLanguage,
|
||||
...this.fallbackLanguages,
|
||||
"en"
|
||||
];
|
||||
|
||||
for (const lang of languagesToTry) {
|
||||
const str = this.tSingleLanguage(lang, path, opts);
|
||||
if (typeof str === "string") return str;
|
||||
}
|
||||
|
||||
// key fallback
|
||||
let string_opts = opts
|
||||
? `: { ${
|
||||
Object.entries(opts)
|
||||
.map(o => `"${o[0]}": "${o[1]}"`)
|
||||
.join(", ")
|
||||
} }`
|
||||
: "";
|
||||
return `${key}${string_opts}`;
|
||||
}
|
||||
|
||||
this.initialized = true;
|
||||
}
|
||||
|
||||
updateConfigs() {
|
||||
this.primaryLanguage = this.config.get("core.languageSettings.primaryLanguage");
|
||||
this.fallbackLanguages = this.config.get("core.languageSettings.fallbackLanguages");
|
||||
}
|
||||
|
||||
registerStrings(packageId, strings) {
|
||||
if (!(typeof this.registeredStrings[packageId] === "object")) this.registeredStrings[packageId] = {};
|
||||
|
||||
walkStrings(strings, (path, string, isString) => {
|
||||
let last = path.pop();
|
||||
|
||||
let obj = travelDownObjectPath(this.registeredStrings[packageId], path);
|
||||
|
||||
if (isString) {
|
||||
obj[last] = string;
|
||||
} else if (!obj[last]) {
|
||||
obj[last] = {};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getT(ns) {
|
||||
if (!ns) return this.t;
|
||||
return (key, formats) => this.t(`${ns}.${key}`, formats);
|
||||
}
|
||||
|
||||
/**
|
||||
* attempts to translate for a single language, given a preparsed path array.
|
||||
* @return undefined if the language or string cannot be found,
|
||||
* and throws an error if the path isn't right.
|
||||
*/
|
||||
tSingleLanguage(lang, _path, opts) {
|
||||
let path = [..._path];
|
||||
|
||||
const ns = path.shift();
|
||||
if (!ns) throw new Error(`key path seems invalid: [${_path.map(p => `"${p}"`).join(", ")}]`);
|
||||
|
||||
const languageObj = this.getLanguageObj(ns, lang);
|
||||
if (languageObj === undefined) return undefined;
|
||||
|
||||
const str = optionalTravelDownObjectPath(languageObj, path);
|
||||
if (str !== undefined) {
|
||||
return this.format(ns, path, str, lang, opts);
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* gets a language object from a specified namespace
|
||||
* @return undefined if it can't be found
|
||||
*/
|
||||
getLanguageObj(ns, lang) {
|
||||
return ns === "core"
|
||||
? this.getCoreLanguage(lang)
|
||||
: this.getPkgLanguage(ns, lang);
|
||||
}
|
||||
|
||||
/**
|
||||
* gets a language for `core`
|
||||
* @return undefined if it can't be found
|
||||
*/
|
||||
getCoreLanguage(lang) {
|
||||
const loaded = this.registeredStrings.core[lang]
|
||||
if (loaded !== undefined) return loaded;
|
||||
|
||||
const fetched = this.fetchCoreLanguageFile(lang);
|
||||
if (fetched === undefined) return undefined;
|
||||
|
||||
this.registeredStrings.core[lang] = fetched;
|
||||
return fetched;
|
||||
}
|
||||
|
||||
/**
|
||||
* gets a language for a specific namespace
|
||||
* @return undefined if it can't be found
|
||||
*/
|
||||
getPkgLanguage(ns, lang) {
|
||||
const loaded = this.registeredStrings[ns]?.[lang];
|
||||
if (loaded !== undefined) return loaded;
|
||||
|
||||
const fetched = this.fetchPkgLanguageFile(ns, lang);
|
||||
if (fetched === undefined) return fetched;
|
||||
|
||||
if (typeof this.registeredStrings[ns] !== "object" || this.registeredStrings[ns] === null) {
|
||||
this.registeredStrings[ns] = {};
|
||||
}
|
||||
this.registeredStrings[ns][lang] = fetched;
|
||||
}
|
||||
|
||||
/**
|
||||
* fetches a core language from the disk
|
||||
* @return undefined if it can't be found
|
||||
*/
|
||||
fetchCoreLanguageFile(lang) {
|
||||
let filepath = path.join(this.resourcePath, "i18n", `${lang}.json`);
|
||||
let contents = JSON.parse(fs.readFileSync(filepath));
|
||||
|
||||
return contents;
|
||||
}
|
||||
|
||||
/**
|
||||
* fetches a language for a specific namespace
|
||||
* @return undefined if it can't be found
|
||||
*/
|
||||
fetchPkgLanguageFile(ns, lang) {
|
||||
// TODO this could probably be optimised
|
||||
let packages = this.packages.getAvailablePackages();
|
||||
let foundPackage = packages.find(p => p.name === ns);
|
||||
|
||||
const i18nDir = path.join(foundPackage.path, "i18n");
|
||||
const langfile = path.join(i18nDir, `${lang}.json`);
|
||||
|
||||
if (!(fs.isDirectorySync(i18nDir) && fs.existsSync(langfile))) return;
|
||||
|
||||
let contents = JSON.parse(fs.readFileSync(langfile));
|
||||
|
||||
return contents;
|
||||
}
|
||||
|
||||
/**
|
||||
* formats a string with opts,
|
||||
* and caches the message formatter for the provided path.
|
||||
*/
|
||||
format(ns, _path, str, lang, opts) {
|
||||
let path = [ns, lang, ..._path];
|
||||
|
||||
let cachedFormatter = optionalTravelDownObjectPath(this.cachedFormatters, path);
|
||||
if (cachedFormatter !== undefined) return cachedFormatter.format(opts);
|
||||
|
||||
let ast = this.cacheHelper.fetchAST(ns, _path, str, lang);
|
||||
let formatter = new IntlMessageFormat(ast, lang);
|
||||
|
||||
let last = path.pop();
|
||||
let cachePath = travelDownOrMakePath(this.cachedFormatters, path);
|
||||
cachePath[last] = formatter;
|
||||
return formatter.format(opts);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = I18n;
|
@ -106,9 +106,10 @@ module.exports = class ApplicationMenu {
|
||||
// Replaces VERSION with the current version.
|
||||
substituteVersion(template) {
|
||||
let item = this.flattenMenuTemplate(template).find(
|
||||
({ label }) => label === 'VERSION'
|
||||
({ label }) => label?.includes("VERSION")
|
||||
);
|
||||
if (item) item.label = `Version ${this.version}`;
|
||||
// TODO maybe this can be done with the i18n API instead of custom replace?
|
||||
if (item) item.label = item.label.replace("VERSION", this.version);
|
||||
}
|
||||
|
||||
// Sets the proper visible state the update menu items
|
||||
|
@ -12,8 +12,8 @@ function addItemToMenu(item, menu) {
|
||||
}
|
||||
}
|
||||
|
||||
function merge(menu, item, itemSpecificity = Infinity) {
|
||||
item = cloneMenuItem(item);
|
||||
function merge(menu, item, t, itemSpecificity = Infinity) {
|
||||
item = cloneAndLocaliseMenuItem(item, t);
|
||||
ItemSpecificities.set(item, itemSpecificity);
|
||||
const matchingItemIndex = findMatchingItemIndex(menu, item);
|
||||
|
||||
@ -25,7 +25,7 @@ function merge(menu, item, itemSpecificity = Infinity) {
|
||||
const matchingItem = menu[matchingItemIndex];
|
||||
if (item.submenu != null) {
|
||||
for (let submenuItem of item.submenu) {
|
||||
merge(matchingItem.submenu, submenuItem, itemSpecificity);
|
||||
merge(matchingItem.submenu, submenuItem, t, itemSpecificity);
|
||||
}
|
||||
} else if (
|
||||
itemSpecificity &&
|
||||
@ -35,8 +35,9 @@ function merge(menu, item, itemSpecificity = Infinity) {
|
||||
}
|
||||
}
|
||||
|
||||
function unmerge(menu, item) {
|
||||
item = cloneMenuItem(item);
|
||||
|
||||
function unmerge(menu, item, t) {
|
||||
item = cloneAndLocaliseMenuItem(item, t);
|
||||
const matchingItemIndex = findMatchingItemIndex(menu, item);
|
||||
if (matchingItemIndex === -1) {
|
||||
return;
|
||||
@ -74,11 +75,12 @@ function normalizeLabel(label) {
|
||||
return process.platform === 'darwin' ? label : label.replace(/&/g, '');
|
||||
}
|
||||
|
||||
function cloneMenuItem(item) {
|
||||
function cloneAndLocaliseMenuItem(item, t) {
|
||||
item = _.pick(
|
||||
item,
|
||||
'type',
|
||||
'label',
|
||||
'localisedLabel',
|
||||
'id',
|
||||
'enabled',
|
||||
'visible',
|
||||
@ -92,11 +94,18 @@ function cloneMenuItem(item) {
|
||||
'beforeGroupContaining',
|
||||
'afterGroupContaining'
|
||||
);
|
||||
if (item.localisedLabel) {
|
||||
if (typeof item.localisedLabel === "string") {
|
||||
item.label = t(item.localisedLabel);
|
||||
} else {
|
||||
item.label = t(item.localisedLabel.key, item.localisedLabel.opts);
|
||||
}
|
||||
}
|
||||
if (item.id === null || item.id === undefined) {
|
||||
item.id = normalizeLabel(item.label);
|
||||
}
|
||||
if (item.submenu != null) {
|
||||
item.submenu = item.submenu.map(submenuItem => cloneMenuItem(submenuItem));
|
||||
item.submenu = item.submenu.map(submenuItem => cloneAndLocaliseMenuItem(submenuItem, t));
|
||||
}
|
||||
return item;
|
||||
}
|
||||
@ -133,6 +142,6 @@ module.exports = {
|
||||
merge,
|
||||
unmerge,
|
||||
normalizeLabel,
|
||||
cloneMenuItem,
|
||||
cloneAndLocaliseMenuItem,
|
||||
acceleratorForKeystroke
|
||||
};
|
||||
|
@ -60,8 +60,8 @@ if (buildMetadata) {
|
||||
//
|
||||
// See {::add} for more info about adding menu's directly.
|
||||
module.exports = MenuManager = class MenuManager {
|
||||
constructor({resourcePath, keymapManager, packageManager}) {
|
||||
this.resourcePath = resourcePath;
|
||||
constructor({ i18n, keymapManager, packageManager }) {
|
||||
this.i18n = i18n;
|
||||
this.keymapManager = keymapManager;
|
||||
this.packageManager = packageManager;
|
||||
this.initialized = false;
|
||||
@ -101,9 +101,8 @@ module.exports = MenuManager = class MenuManager {
|
||||
// added menu items.
|
||||
add(items) {
|
||||
items = _.deepClone(items);
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const item = items[i];
|
||||
if (item.label == null) {
|
||||
for (const item of items) {
|
||||
if (item.label == null && item.localisedLabel == null) {
|
||||
continue; // TODO: Should we emit a warning here?
|
||||
}
|
||||
this.merge(this.template, item);
|
||||
@ -220,11 +219,11 @@ module.exports = MenuManager = class MenuManager {
|
||||
// Merges an item in a submenu aware way such that new items are always
|
||||
// appended to the bottom of existing menus where possible.
|
||||
merge(menu, item) {
|
||||
MenuHelpers.merge(menu, item);
|
||||
MenuHelpers.merge(menu, item, this.i18n.t);
|
||||
}
|
||||
|
||||
unmerge(menu, item) {
|
||||
MenuHelpers.unmerge(menu, item);
|
||||
MenuHelpers.unmerge(menu, item, this.i18n.t);
|
||||
}
|
||||
|
||||
sendToBrowserProcess(template, keystrokesByCommand) {
|
||||
@ -242,7 +241,8 @@ module.exports = MenuManager = class MenuManager {
|
||||
}
|
||||
|
||||
sortPackagesMenu() {
|
||||
const packagesMenu = _.find(this.template, ({id}) => MenuHelpers.normalizeLabel(id) === 'Packages');
|
||||
let packagesLabel = this.i18n.t("core.menu.packages.self");
|
||||
const packagesMenu = _.find(this.template, ({id}) => MenuHelpers.normalizeLabel(id) === packagesLabel);
|
||||
if (!(packagesMenu && packagesMenu.submenu != null)) {
|
||||
return;
|
||||
}
|
||||
@ -255,5 +255,4 @@ module.exports = MenuManager = class MenuManager {
|
||||
});
|
||||
return this.update();
|
||||
}
|
||||
|
||||
};
|
||||
|
@ -38,7 +38,8 @@ module.exports = class PackageManager {
|
||||
grammarRegistry: this.grammarRegistry,
|
||||
deserializerManager: this.deserializerManager,
|
||||
viewRegistry: this.viewRegistry,
|
||||
uriHandlerRegistry: this.uriHandlerRegistry
|
||||
uriHandlerRegistry: this.uriHandlerRegistry,
|
||||
i18n: this.i18n
|
||||
} = params);
|
||||
|
||||
this.emitter = new Emitter();
|
||||
@ -587,7 +588,8 @@ module.exports = class PackageManager {
|
||||
menuManager: this.menuManager,
|
||||
contextMenuManager: this.contextMenuManager,
|
||||
deserializerManager: this.deserializerManager,
|
||||
viewRegistry: this.viewRegistry
|
||||
viewRegistry: this.viewRegistry,
|
||||
i18n: this.i18n
|
||||
};
|
||||
|
||||
pack = metadata.theme ? new ThemePackage(options) : new Package(options);
|
||||
@ -692,7 +694,8 @@ module.exports = class PackageManager {
|
||||
menuManager: this.menuManager,
|
||||
contextMenuManager: this.contextMenuManager,
|
||||
deserializerManager: this.deserializerManager,
|
||||
viewRegistry: this.viewRegistry
|
||||
viewRegistry: this.viewRegistry,
|
||||
i18n: this.i18n
|
||||
};
|
||||
|
||||
const pack = metadata.theme
|
||||
|
@ -30,6 +30,7 @@ module.exports = class Package {
|
||||
this.contextMenuManager = params.contextMenuManager;
|
||||
this.deserializerManager = params.deserializerManager;
|
||||
this.viewRegistry = params.viewRegistry;
|
||||
this.i18n = params.i18n;
|
||||
this.emitter = new Emitter();
|
||||
|
||||
this.mainModule = null;
|
||||
@ -240,7 +241,10 @@ module.exports = class Package {
|
||||
}
|
||||
if (typeof this.mainModule.activate === 'function') {
|
||||
this.mainModule.activate(
|
||||
this.packageManager.getPackageState(this.name) || {}
|
||||
this.packageManager.getPackageState(this.name) || {},
|
||||
{
|
||||
t: this.i18n.getT(this.name)
|
||||
}
|
||||
);
|
||||
}
|
||||
this.mainActivated = true;
|
||||
|
54
yarn.lock
54
yarn.lock
@ -1438,6 +1438,45 @@
|
||||
minimatch "^3.1.2"
|
||||
strip-json-comments "^3.1.1"
|
||||
|
||||
"@formatjs/ecma402-abstract@1.14.3":
|
||||
version "1.14.3"
|
||||
resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-1.14.3.tgz#6428f243538a11126180d121ce8d4b2f17465738"
|
||||
integrity sha512-SlsbRC/RX+/zg4AApWIFNDdkLtFbkq3LNoZWXZCE/nHVKqoIJyaoQyge/I0Y38vLxowUn9KTtXgusLD91+orbg==
|
||||
dependencies:
|
||||
"@formatjs/intl-localematcher" "0.2.32"
|
||||
tslib "^2.4.0"
|
||||
|
||||
"@formatjs/fast-memoize@2.0.1":
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@formatjs/fast-memoize/-/fast-memoize-2.0.1.tgz#f15aaa73caad5562899c69bdcad8db82adcd3b0b"
|
||||
integrity sha512-M2GgV+qJn5WJQAYewz7q2Cdl6fobQa69S1AzSM2y0P68ZDbK5cWrJIcPCO395Of1ksftGZoOt4LYCO/j9BKBSA==
|
||||
dependencies:
|
||||
tslib "^2.4.0"
|
||||
|
||||
"@formatjs/icu-messageformat-parser@2.3.0", "@formatjs/icu-messageformat-parser@^2.3.0":
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.3.0.tgz#8e8fd577c3e39454ef14bba4963f2e1d5f2cc46c"
|
||||
integrity sha512-xqtlqYAbfJDF4b6e4O828LBNOWXrFcuYadqAbYORlDRwhyJ2bH+xpUBPldZbzRGUN2mxlZ4Ykhm7jvERtmI8NQ==
|
||||
dependencies:
|
||||
"@formatjs/ecma402-abstract" "1.14.3"
|
||||
"@formatjs/icu-skeleton-parser" "1.3.18"
|
||||
tslib "^2.4.0"
|
||||
|
||||
"@formatjs/icu-skeleton-parser@1.3.18":
|
||||
version "1.3.18"
|
||||
resolved "https://registry.yarnpkg.com/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.3.18.tgz#7aed3d60e718c8ad6b0e64820be44daa1e29eeeb"
|
||||
integrity sha512-ND1ZkZfmLPcHjAH1sVpkpQxA+QYfOX3py3SjKWMUVGDow18gZ0WPqz3F+pJLYQMpS2LnnQ5zYR2jPVYTbRwMpg==
|
||||
dependencies:
|
||||
"@formatjs/ecma402-abstract" "1.14.3"
|
||||
tslib "^2.4.0"
|
||||
|
||||
"@formatjs/intl-localematcher@0.2.32":
|
||||
version "0.2.32"
|
||||
resolved "https://registry.yarnpkg.com/@formatjs/intl-localematcher/-/intl-localematcher-0.2.32.tgz#00d4d307cd7d514b298e15a11a369b86c8933ec1"
|
||||
integrity sha512-k/MEBstff4sttohyEpXxCmC3MqbUn9VvHGlZ8fauLzkbwXmVrEeyzS+4uhrvAk9DWU9/7otYWxyDox4nT/KVLQ==
|
||||
dependencies:
|
||||
tslib "^2.4.0"
|
||||
|
||||
"@gar/promisify@^1.0.1":
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6"
|
||||
@ -5518,6 +5557,16 @@ internal-slot@^1.0.3:
|
||||
has "^1.0.3"
|
||||
side-channel "^1.0.4"
|
||||
|
||||
intl-messageformat@^10.3.3:
|
||||
version "10.3.3"
|
||||
resolved "https://registry.yarnpkg.com/intl-messageformat/-/intl-messageformat-10.3.3.tgz#576798d31c9f8d90f9beadaa5a3878b8d30177a2"
|
||||
integrity sha512-un/f07/g2e/3Q8e1ghDKET+el22Bi49M7O/rHxd597R+oLpPOMykSv5s51cABVfu3FZW+fea4hrzf2MHu1W4hw==
|
||||
dependencies:
|
||||
"@formatjs/ecma402-abstract" "1.14.3"
|
||||
"@formatjs/fast-memoize" "2.0.1"
|
||||
"@formatjs/icu-messageformat-parser" "2.3.0"
|
||||
tslib "^2.4.0"
|
||||
|
||||
invert-kv@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6"
|
||||
@ -9506,6 +9555,11 @@ truncate-utf8-bytes@^1.0.0:
|
||||
dependencies:
|
||||
utf8-byte-length "^1.0.1"
|
||||
|
||||
tslib@^2.4.0:
|
||||
version "2.5.0"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.0.tgz#42bfed86f5787aeb41d031866c8f402429e0fddf"
|
||||
integrity sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==
|
||||
|
||||
tunnel-agent@^0.6.0:
|
||||
version "0.6.0"
|
||||
resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd"
|
||||
|
Loading…
Reference in New Issue
Block a user