mirror of
https://github.com/pulsar-edit/pulsar.git
synced 2024-09-11 19:17:09 +03:00
Merge branch 'master' into settings-view-update-package-snippets-view
This commit is contained in:
commit
c04aea2fb6
@ -7,6 +7,10 @@
|
|||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
- The settings-view package now lists a package’s snippets more accurately
|
- 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
|
||||||
|
version). This fixes issues with Electron 21
|
||||||
|
|
||||||
## 1.103.0
|
## 1.103.0
|
||||||
|
|
||||||
|
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": []
|
"warnings": []
|
||||||
}</p>
|
}</p>
|
||||||
</dd>
|
</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>
|
<dt><a href="#fs">fs</a></dt>
|
||||||
<dd></dd>
|
<dd></dd>
|
||||||
<dt><a href="#dalek">dalek</a></dt>
|
<dt><a href="#dalek">dalek</a></dt>
|
||||||
@ -376,6 +430,68 @@ This file will manage the updating of `autocomplete-css` `completions.json`.
|
|||||||
"warnings": []
|
"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
|
**Kind**: global constant
|
||||||
<a name="fs"></a>
|
<a name="fs"></a>
|
||||||
|
|
||||||
|
@ -98,6 +98,60 @@
|
|||||||
"warnings": []
|
"warnings": []
|
||||||
}</p>
|
}</p>
|
||||||
</dd>
|
</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>
|
<dt><a href="#fs">fs</a></dt>
|
||||||
<dd></dd>
|
<dd></dd>
|
||||||
<dt><a href="#dalek">dalek</a></dt>
|
<dt><a href="#dalek">dalek</a></dt>
|
||||||
@ -384,6 +438,68 @@ This file will manage the updating of `autocomplete-css` `completions.json`.
|
|||||||
"warnings": []
|
"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
|
**Kind**: global constant
|
||||||
<a name="fs"></a>
|
<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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,227 +2,227 @@
|
|||||||
{
|
{
|
||||||
label: 'Pulsar'
|
label: 'Pulsar'
|
||||||
submenu: [
|
submenu: [
|
||||||
{ label: 'About Pulsar', command: 'application:about' }
|
{ localisedLabel: 'core.menu.pulsar.about', command: 'application:about' }
|
||||||
{ label: 'View License', command: 'application:open-license' }
|
{ localisedLabel: 'core.menu.pulsar.view-license', command: 'application:open-license' }
|
||||||
{ label: 'VERSION', enabled: false }
|
{ localisedLabel: 'core.menu.pulsar.version', enabled: false }
|
||||||
{ label: 'Restart and Install Update', command: 'application:install-update', visible: false}
|
{ localisedLabel: 'core.menu.pulsar.restart-and-install-update', command: 'application:install-update', visible: false }
|
||||||
{ label: 'Check for Update', command: 'application:check-for-update', visible: false}
|
{ localisedLabel: 'core.menu.pulsar.check-for-update', command: 'application:check-for-update', visible: false }
|
||||||
{ label: 'Checking for Update', enabled: false, visible: false}
|
{ localisedLabel: 'core.menu.pulsar.checking-for-update', enabled: false, visible: false }
|
||||||
{ label: 'Downloading Update', enabled: false, visible: false}
|
{ localisedLabel: 'core.menu.pulsar.downloading-update', enabled: false, visible: false }
|
||||||
{ type: 'separator' }
|
{ type: 'separator' }
|
||||||
{ label: 'Preferences…', command: 'application:show-settings' }
|
{ localisedLabel: 'core.menu.pulsar.preferences', command: 'application:show-settings' }
|
||||||
{ type: 'separator' }
|
{ type: 'separator' }
|
||||||
{ label: 'Config…', command: 'application:open-your-config' }
|
{ localisedLabel: 'core.menu.pulsar.config', command: 'application:open-your-config' }
|
||||||
{ label: 'Init Script…', command: 'application:open-your-init-script' }
|
{ localisedLabel: 'core.menu.pulsar.init-script', command: 'application:open-your-init-script' }
|
||||||
{ label: 'Keymap…', command: 'application:open-your-keymap' }
|
{ localisedLabel: 'core.menu.pulsar.keymap', command: 'application:open-your-keymap' }
|
||||||
{ label: 'Snippets…', command: 'application:open-your-snippets' }
|
{ localisedLabel: 'core.menu.pulsar.snippets', command: 'application:open-your-snippets' }
|
||||||
{ label: 'Stylesheet…', command: 'application:open-your-stylesheet' }
|
{ localisedLabel: 'core.menu.pulsar.stylesheet', command: 'application:open-your-stylesheet' }
|
||||||
{ type: 'separator' }
|
{ 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' }
|
{ type: 'separator' }
|
||||||
{ label: 'Services', role: 'services', submenu: [] }
|
{ localisedLabel: 'core.menu.macos.services', role: 'services', submenu: [] }
|
||||||
{ type: 'separator' }
|
{ type: 'separator' }
|
||||||
{ label: 'Hide Pulsar', command: 'application:hide' }
|
{ localisedLabel: 'core.menu.macos.hide-self', command: 'application:hide' }
|
||||||
{ label: 'Hide Others', command: 'application:hide-other-applications' }
|
{ localisedLabel: 'core.menu.macos.hide-others', command: 'application:hide-other-applications' }
|
||||||
{ label: 'Show All', command: 'application:unhide-all-applications' }
|
{ localisedLabel: 'core.menu.macos.show-all', command: 'application:unhide-all-applications' }
|
||||||
{ type: 'separator' }
|
{ type: 'separator' }
|
||||||
{ label: 'Quit Pulsar', command: 'application:quit' }
|
{ localisedLabel: 'core.menu.pulsar.quit', command: 'application:quit' }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
label: 'File'
|
localisedLabel: 'core.menu.file.self'
|
||||||
submenu: [
|
submenu: [
|
||||||
{ label: 'New Window', command: 'application:new-window' }
|
{ localisedLabel: 'core.menu.file.new-window', command: 'application:new-window' }
|
||||||
{ label: 'New File', command: 'application:new-file' }
|
{ localisedLabel: 'core.menu.file.new-file', command: 'application:new-file' }
|
||||||
{ label: 'Open…', command: 'application:open' }
|
{ localisedLabel: 'core.menu.file.open', command: 'application:open' }
|
||||||
{ label: 'Add Project Folder…', command: 'application:add-project-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: [
|
submenu: [
|
||||||
{ label: 'Clear Project History', command: 'application:clear-project-history' }
|
{ localisedLabel: 'core.menu.file.project-history.clear', command: 'application:clear-project-history' }
|
||||||
{ type: 'separator' }
|
{ 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' }
|
{ type: 'separator' }
|
||||||
{ label: 'Save', command: 'core:save' }
|
{ localisedLabel: 'core.menu.file.save', command: 'core:save' }
|
||||||
{ label: 'Save As…', command: 'core:save-as' }
|
{ localisedLabel: 'core.menu.file.save-as', command: 'core:save-as' }
|
||||||
{ label: 'Save All', command: 'window:save-all' }
|
{ localisedLabel: 'core.menu.file.save-all', command: 'window:save-all' }
|
||||||
{ type: 'separator' }
|
{ type: 'separator' }
|
||||||
{ label: 'Close Tab', command: 'core:close' }
|
{ localisedLabel: 'core.menu.file.close-tab', command: 'core:close' }
|
||||||
{ label: 'Close Pane', command: 'pane:close' }
|
{ localisedLabel: 'core.menu.file.close-pane', command: 'pane:close' }
|
||||||
{ label: 'Close Window', command: 'window:close' }
|
{ localisedLabel: 'core.menu.file.close-window', command: 'window:close' }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
label: 'Edit'
|
localisedLabel: 'core.menu.edit.self'
|
||||||
submenu: [
|
submenu: [
|
||||||
{ label: 'Undo', command: 'core:undo' }
|
{ localisedLabel: 'core.menu.edit.undo', command: 'core:undo' }
|
||||||
{ label: 'Redo', command: 'core:redo' }
|
{ localisedLabel: 'core.menu.edit.redo', command: 'core:redo' }
|
||||||
{ type: 'separator' }
|
{ type: 'separator' }
|
||||||
{ label: 'Cut', command: 'core:cut' }
|
{ localisedLabel: 'core.menu.edit.cut', command: 'core:cut' }
|
||||||
{ label: 'Copy', command: 'core:copy' }
|
{ localisedLabel: 'core.menu.edit.copy', command: 'core:copy' }
|
||||||
{ label: 'Copy Path', command: 'editor:copy-path' }
|
{ localisedLabel: 'core.menu.edit.copy-path', command: 'editor:copy-path' }
|
||||||
{ label: 'Paste', command: 'core:paste' }
|
{ localisedLabel: 'core.menu.edit.paste', command: 'core:paste' }
|
||||||
{ label: 'Paste Without Reformatting', command: 'editor:paste-without-reformatting' }
|
{ localisedLabel: 'core.menu.edit.paste-without-reformatting', command: 'editor:paste-without-reformatting' }
|
||||||
{ label: 'Select All', command: 'core:select-all' }
|
{ localisedLabel: 'core.menu.edit.select-all', command: 'core:select-all' }
|
||||||
{ type: 'separator' }
|
{ 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: [
|
submenu: [
|
||||||
{ label: 'Indent', command: 'editor:indent-selected-rows' }
|
{ localisedLabel: 'core.menu.edit.lines.indent', command: 'editor:indent-selected-rows' }
|
||||||
{ label: 'Outdent', command: 'editor:outdent-selected-rows' }
|
{ localisedLabel: 'core.menu.edit.lines.outdent', command: 'editor:outdent-selected-rows' }
|
||||||
{ label: 'Auto Indent', command: 'editor:auto-indent' }
|
{ localisedLabel: 'core.menu.edit.lines.auto-indent', command: 'editor:auto-indent' }
|
||||||
{ type: 'separator' }
|
{ type: 'separator' }
|
||||||
{ label: 'Move Line Up', command: 'editor:move-line-up' }
|
{ localisedLabel: 'core.menu.edit.lines.move-up', command: 'editor:move-line-up' }
|
||||||
{ label: 'Move Line Down', command: 'editor:move-line-down' }
|
{ localisedLabel: 'core.menu.edit.lines.move-down', command: 'editor:move-line-down' }
|
||||||
{ label: 'Duplicate Lines', command: 'editor:duplicate-lines' }
|
{ localisedLabel: 'core.menu.edit.lines.duplicate', command: 'editor:duplicate-lines' }
|
||||||
{ label: 'Delete Line', command: 'editor:delete-line' }
|
{ localisedLabel: 'core.menu.edit.lines.delete', command: 'editor:delete-line' }
|
||||||
{ label: 'Join Lines', command: 'editor:join-lines' }
|
{ localisedLabel: 'core.menu.edit.lines.join', command: 'editor:join-lines' }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
label: 'Columns',
|
localisedLabel: 'core.menu.edit.columns.self'
|
||||||
submenu: [
|
submenu: [
|
||||||
{ label: 'Move Selection Left', command: 'editor:move-selection-left' }
|
{ localisedLabel: 'core.menu.edit.columns.move-selection-left', command: 'editor:move-selection-left' }
|
||||||
{ label: 'Move Selection Right', command: 'editor:move-selection-right' }
|
{ localisedLabel: 'core.menu.edit.columns.move-selection-right', command: 'editor:move-selection-right' }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
label: 'Text',
|
localisedLabel: 'core.menu.edit.text.self'
|
||||||
submenu: [
|
submenu: [
|
||||||
{ label: 'Upper Case', command: 'editor:upper-case' }
|
{ localisedLabel: 'core.menu.edit.text.upper-case', command: 'editor:upper-case' }
|
||||||
{ label: 'Lower Case', command: 'editor:lower-case' }
|
{ localisedLabel: 'core.menu.edit.text.lower-case', command: 'editor:lower-case' }
|
||||||
{ type: 'separator' }
|
{ type: 'separator' }
|
||||||
{ label: 'Delete to End of Word', command: 'editor:delete-to-end-of-word' }
|
{ localisedLabel: 'core.menu.edit.text.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' }
|
{ localisedLabel: 'core.menu.edit.text.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' }
|
{ localisedLabel: 'core.menu.edit.text.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-line', command: 'editor:delete-line' }
|
||||||
{ type: 'separator' }
|
{ 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: [
|
submenu: [
|
||||||
{ label: 'Fold', command: 'editor:fold-current-row' }
|
{ localisedLabel: 'core.menu.edit.folding.fold', command: 'editor:fold-current-row' }
|
||||||
{ label: 'Unfold', command: 'editor:unfold-current-row' }
|
{ localisedLabel: 'core.menu.edit.folding.unfold', command: 'editor:unfold-current-row' }
|
||||||
{ label: 'Fold All', command: 'editor:fold-all' }
|
{ localisedLabel: 'core.menu.edit.folding.fold-all', command: 'editor:fold-all' }
|
||||||
{ label: 'Unfold All', command: 'editor:unfold-all' }
|
{ localisedLabel: 'core.menu.edit.folding.unfold-all', command: 'editor:unfold-all' }
|
||||||
{ type: 'separator' }
|
{ type: 'separator' }
|
||||||
{ label: 'Fold Level 1', command: 'editor:fold-at-indent-level-1' }
|
{ localisedLabel: 'core.menu.edit.folding.fold-level-1', command: 'editor:fold-at-indent-level-1' }
|
||||||
{ label: 'Fold Level 2', command: 'editor:fold-at-indent-level-2' }
|
{ localisedLabel: 'core.menu.edit.folding.fold-level-2', command: 'editor:fold-at-indent-level-2' }
|
||||||
{ label: 'Fold Level 3', command: 'editor:fold-at-indent-level-3' }
|
{ localisedLabel: 'core.menu.edit.folding.fold-level-3', command: 'editor:fold-at-indent-level-3' }
|
||||||
{ label: 'Fold Level 4', command: 'editor:fold-at-indent-level-4' }
|
{ localisedLabel: 'core.menu.edit.folding.fold-level-4', command: 'editor:fold-at-indent-level-4' }
|
||||||
{ label: 'Fold Level 5', command: 'editor:fold-at-indent-level-5' }
|
{ localisedLabel: 'core.menu.edit.folding.fold-level-5', command: 'editor:fold-at-indent-level-5' }
|
||||||
{ label: 'Fold Level 6', command: 'editor:fold-at-indent-level-6' }
|
{ localisedLabel: 'core.menu.edit.folding.fold-level-6', command: 'editor:fold-at-indent-level-6' }
|
||||||
{ label: 'Fold Level 7', command: 'editor:fold-at-indent-level-7' }
|
{ localisedLabel: 'core.menu.edit.folding.fold-level-7', command: 'editor:fold-at-indent-level-7' }
|
||||||
{ label: 'Fold Level 8', command: 'editor:fold-at-indent-level-8' }
|
{ localisedLabel: 'core.menu.edit.folding.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-9', command: 'editor:fold-at-indent-level-9' }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
label: 'View'
|
localisedLabel: 'core.menu.view.self'
|
||||||
submenu: [
|
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: [
|
submenu: [
|
||||||
{ label: 'Split Up', command: 'pane:split-up-and-copy-active-item' }
|
{ localisedLabel: 'core.menu.view.panes.split-up', command: 'pane:split-up-and-copy-active-item' }
|
||||||
{ label: 'Split Down', command: 'pane:split-down-and-copy-active-item' }
|
{ localisedLabel: 'core.menu.view.panes.split-down', command: 'pane:split-down-and-copy-active-item' }
|
||||||
{ label: 'Split Left', command: 'pane:split-left-and-copy-active-item' }
|
{ localisedLabel: 'core.menu.view.panes.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-right', command: 'pane:split-right-and-copy-active-item' }
|
||||||
{ type: 'separator' }
|
{ type: 'separator' }
|
||||||
{ label: 'Focus Next Pane', command: 'window:focus-next-pane' }
|
{ localisedLabel: 'core.menu.view.panes.focus-next', command: 'window:focus-next-pane' }
|
||||||
{ label: 'Focus Previous Pane', command: 'window:focus-previous-pane' }
|
{ localisedLabel: 'core.menu.view.panes.focus-previous', command: 'window:focus-previous-pane' }
|
||||||
{ type: 'separator' }
|
{ type: 'separator' }
|
||||||
{ label: 'Focus Pane Above', command: 'window:focus-pane-above' }
|
{ localisedLabel: 'core.menu.view.panes.focus-above', command: 'window:focus-pane-above' }
|
||||||
{ label: 'Focus Pane Below', command: 'window:focus-pane-below' }
|
{ localisedLabel: 'core.menu.view.panes.focus-below', command: 'window:focus-pane-below' }
|
||||||
{ label: 'Focus Pane On Left', command: 'window:focus-pane-on-left' }
|
{ localisedLabel: 'core.menu.view.panes.focus-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-on-right', command: 'window:focus-pane-on-right' }
|
||||||
{ type: 'separator' }
|
{ 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: [
|
submenu: [
|
||||||
{ label: 'Open In Dev Mode…', command: 'application:open-dev' }
|
{ localisedLabel: 'core.menu.view.developer.open-in-dev-mode', command: 'application:open-dev' }
|
||||||
{ label: 'Reload Window', command: 'window:reload' }
|
{ localisedLabel: 'core.menu.view.developer.reload-window', command: 'window:reload' }
|
||||||
{ label: 'Run Package Specs', command: 'window:run-package-specs' }
|
{ localisedLabel: 'core.menu.view.developer.run-package-specs', command: 'window:run-package-specs' }
|
||||||
{ label: 'Toggle Developer Tools', command: 'window:toggle-dev-tools' }
|
{ localisedLabel: 'core.menu.view.developer.toggle-dev-tools', command: 'window:toggle-dev-tools' }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
{ type: 'separator' }
|
{ type: 'separator' }
|
||||||
{ label: 'Increase Font Size', command: 'window:increase-font-size' }
|
{ localisedLabel: 'core.menu.view.increase-font-size', command: 'window:increase-font-size' }
|
||||||
{ label: 'Decrease Font Size', command: 'window:decrease-font-size' }
|
{ localisedLabel: 'core.menu.view.decrease-font-size', command: 'window:decrease-font-size' }
|
||||||
{ label: 'Reset Font Size', command: 'window:reset-font-size' }
|
{ localisedLabel: 'core.menu.view.reset-font-size', command: 'window:reset-font-size' }
|
||||||
{ type: 'separator' }
|
{ 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: [
|
submenu: [
|
||||||
{ label: 'Add Selection Above', command: 'editor:add-selection-above' }
|
{ localisedLabel: 'core.menu.selection.add-above', command: 'editor:add-selection-above' }
|
||||||
{ label: 'Add Selection Below', command: 'editor:add-selection-below' }
|
{ localisedLabel: 'core.menu.selection.add-below', command: 'editor:add-selection-below' }
|
||||||
{ label: 'Single Selection', command: 'editor:consolidate-selections'}
|
{ localisedLabel: 'core.menu.selection.single', command: 'editor:consolidate-selections' }
|
||||||
{ label: 'Split into Lines', command: 'editor:split-selections-into-lines'}
|
{ localisedLabel: 'core.menu.selection.split-into-lines', command: 'editor:split-selections-into-lines' }
|
||||||
{ type: 'separator' }
|
{ type: 'separator' }
|
||||||
{ label: 'Select to Top', command: 'core:select-to-top' }
|
{ localisedLabel: 'core.menu.selection.to-top', command: 'core:select-to-top' }
|
||||||
{ label: 'Select to Bottom', command: 'core:select-to-bottom' }
|
{ localisedLabel: 'core.menu.selection.to-bottom', command: 'core:select-to-bottom' }
|
||||||
{ type: 'separator' }
|
{ type: 'separator' }
|
||||||
{ label: 'Select Line', command: 'editor:select-line' }
|
{ localisedLabel: 'core.menu.selection.line', command: 'editor:select-line' }
|
||||||
{ label: 'Select Word', command: 'editor:select-word' }
|
{ localisedLabel: 'core.menu.selection.word', command: 'editor:select-word' }
|
||||||
{ label: 'Select to Beginning of Word', command: 'editor:select-to-beginning-of-word' }
|
{ localisedLabel: 'core.menu.selection.to-beginning-of-word', command: 'editor:select-to-beginning-of-word' }
|
||||||
{ label: 'Select to Beginning of Line', command: 'editor:select-to-beginning-of-line' }
|
{ localisedLabel: 'core.menu.selection.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' }
|
{ localisedLabel: 'core.menu.selection.to-first-char-of-line', command: 'editor:select-to-first-character-of-line' }
|
||||||
{ label: 'Select to End of Word', command: 'editor:select-to-end-of-word' }
|
{ localisedLabel: 'core.menu.selection.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.to-end-of-line', command: 'editor:select-to-end-of-line' }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
label: 'Find'
|
localisedLabel: 'core.menu.find.self'
|
||||||
submenu: []
|
submenu: []
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
label: 'Packages'
|
localisedLabel: 'core.menu.packages.self'
|
||||||
submenu: [
|
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' }
|
{ type: 'separator' }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
label: 'Window'
|
localisedLabel: 'core.menu.window.self'
|
||||||
role: 'window'
|
role: 'window'
|
||||||
submenu: [
|
submenu: [
|
||||||
{ label: 'Minimize', command: 'application:minimize' }
|
{ localisedLabel: 'core.menu.window.minimise', command: 'application:minimize' }
|
||||||
{ label: 'Zoom', command: 'application:zoom' }
|
{ localisedLabel: 'core.menu.window.zoom', command: 'application:zoom' }
|
||||||
{ type: 'separator' }
|
{ 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'
|
role: 'help'
|
||||||
submenu: [
|
submenu: [
|
||||||
{ label: 'Terms of Use', command: 'application:open-terms-of-use' }
|
{ localisedLabel: 'core.menu.help.terms-of-use', command: 'application:open-terms-of-use' }
|
||||||
{ label: 'Documentation', command: 'application:open-documentation' }
|
{ localisedLabel: 'core.menu.help.docs', command: 'application:open-documentation' }
|
||||||
{ label: 'Frequently Asked Questions', command: 'application:open-faq' }
|
{ localisedLabel: 'core.menu.help.faq', command: 'application:open-faq' }
|
||||||
{ type: 'separator' }
|
{ type: 'separator' }
|
||||||
{ label: 'Community Discussions', command: 'application:open-discussions' }
|
{ localisedLabel: 'core.menu.help.community-discussions', command: 'application:open-discussions' }
|
||||||
{ label: 'Report Issue', command: 'application:report-issue' }
|
{ localisedLabel: 'core.menu.help.report-issue', command: 'application:report-issue' }
|
||||||
{ label: 'Search Issues', command: 'application:search-issues' }
|
{ localisedLabel: 'core.menu.help.search-issues', command: 'application:search-issues' }
|
||||||
{ type: 'separator' }
|
{ type: 'separator' }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
234
menus/linux.cson
234
menus/linux.cson
@ -1,204 +1,204 @@
|
|||||||
'menu': [
|
'menu': [
|
||||||
{
|
{
|
||||||
label: '&File'
|
localisedLabel: 'core.menu.file.self' # accelerator F
|
||||||
submenu: [
|
submenu: [
|
||||||
{ label: 'New &Window', command: 'application:new-window' }
|
{ localisedLabel: 'core.menu.file.new-window', command: 'application:new-window' } # accelerator W
|
||||||
{ label: '&New File', command: 'application:new-file' }
|
{ localisedLabel: 'core.menu.file.new-file', command: 'application:new-file' } # accelerator N
|
||||||
{ label: '&Open File…', command: 'application:open-file' }
|
{ localisedLabel: 'core.menu.file.open-file', command: 'application:open-file' } # accelerator O
|
||||||
{ label: 'Open Folder…', command: 'application:open-folder' }
|
{ localisedLabel: 'core.menu.file.open-folder', command: 'application:open-folder' }
|
||||||
{ label: 'Add Project Folder…', command: 'application:add-project-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: [
|
submenu: [
|
||||||
{ label: 'Clear Project History', command: 'application:clear-project-history' }
|
{ localisedLabel: 'core.menu.file.project-history.clear', command: 'application:clear-project-history' }
|
||||||
{ type: 'separator' }
|
{ 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' }
|
{ type: 'separator' }
|
||||||
{ label: '&Save', command: 'core:save' }
|
{ localisedLabel: 'core.menu.file.save', command: 'core:save' } # accelerator S
|
||||||
{ label: 'Save &As…', command: 'core:save-as' }
|
{ localisedLabel: 'core.menu.file.save-as', command: 'core:save-as' } # accelerator A
|
||||||
{ label: 'Save A&ll', command: 'window:save-all' }
|
{ localisedLabel: 'core.menu.file.save-all', command: 'window:save-all' } # accelerator L
|
||||||
{ type: 'separator' }
|
{ type: 'separator' }
|
||||||
{ label: '&Close Tab', command: 'core:close' }
|
{ localisedLabel: 'core.menu.file.close-tab', command: 'core:close' } # accelerator C
|
||||||
{ label: 'Close &Pane', command: 'pane:close' }
|
{ localisedLabel: 'core.menu.file.close-pane', command: 'pane:close' } # accelerator P
|
||||||
{ label: 'Clos&e Window', command: 'window:close' }
|
{ localisedLabel: 'core.menu.file.close-window', command: 'window:close' } # accelerator E
|
||||||
{ type: 'separator' }
|
{ 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: [
|
submenu: [
|
||||||
{ label: '&Undo', command: 'core:undo' }
|
{ localisedLabel: 'core.menu.edit.undo', command: 'core:undo' } # accelerator U
|
||||||
{ label: '&Redo', command: 'core:redo' }
|
{ localisedLabel: 'core.menu.edit.redo', command: 'core:redo' } # accelerator R
|
||||||
{ type: 'separator' }
|
{ type: 'separator' }
|
||||||
{ label: '&Cut', command: 'core:cut' }
|
{ localisedLabel: 'core.menu.edit.cut', command: 'core:cut' } # accelerator C
|
||||||
{ label: 'C&opy', command: 'core:copy' }
|
{ localisedLabel: 'core.menu.edit.copy', command: 'core:copy' } # accelerator O
|
||||||
{ label: 'Copy Pat&h', command: 'editor:copy-path' }
|
{ localisedLabel: 'core.menu.edit.copy-path', command: 'editor:copy-path' } # accelerator H
|
||||||
{ label: '&Paste', command: 'core:paste' }
|
{ localisedLabel: 'core.menu.edit.paste', command: 'core:paste' } # accelerator P
|
||||||
{ label: 'Paste Without Reformatting', command: 'editor:paste-without-reformatting' }
|
{ localisedLabel: 'core.menu.edit.paste-without-reformatting', command: 'editor:paste-without-reformatting' }
|
||||||
{ label: 'Select &All', command: 'core:select-all' }
|
{ localisedLabel: 'core.menu.edit.select-all', command: 'core:select-all' } # accelerator A
|
||||||
{ type: 'separator' }
|
{ 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: [
|
submenu: [
|
||||||
{ label: '&Indent', command: 'editor:indent-selected-rows' }
|
{ localisedLabel: 'core.menu.edit.lines.indent', command: 'editor:indent-selected-rows' } # accelerator I
|
||||||
{ label: '&Outdent', command: 'editor:outdent-selected-rows' }
|
{ localisedLabel: 'core.menu.edit.lines.outdent', command: 'editor:outdent-selected-rows' } # accelerator O
|
||||||
{ label: '&Auto Indent', command: 'editor:auto-indent' }
|
{ localisedLabel: 'core.menu.edit.lines.auto-indent', command: 'editor:auto-indent' } # accelerator A
|
||||||
{ type: 'separator' }
|
{ type: 'separator' }
|
||||||
{ label: 'Move Line &Up', command: 'editor:move-line-up' }
|
{ localisedLabel: 'core.menu.edit.lines.move-up', command: 'editor:move-line-up' } # accelerator U
|
||||||
{ label: 'Move Line &Down', command: 'editor:move-line-down' }
|
{ localisedLabel: 'core.menu.edit.lines.move-down', command: 'editor:move-line-down' } # accelerator D
|
||||||
{ label: 'Du&plicate Lines', command: 'editor:duplicate-lines' }
|
{ localisedLabel: 'core.menu.edit.lines.duplicate', command: 'editor:duplicate-lines' } # accelerator P
|
||||||
{ label: 'D&elete Line', command: 'editor:delete-line' }
|
{ localisedLabel: 'core.menu.edit.lines.delete', command: 'editor:delete-line' } # accelerator E
|
||||||
{ label: '&Join Lines', command: 'editor:join-lines' }
|
{ localisedLabel: 'core.menu.edit.lines.join', command: 'editor:join-lines' } # accelerator J
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
label: 'Columns',
|
localisedLabel: 'core.menu.edit.columns.self'
|
||||||
submenu: [
|
submenu: [
|
||||||
{ label: 'Move Selection &Left', command: 'editor:move-selection-left' }
|
{ localisedLabel: 'core.menu.edit.columns.move-selection-left', command: 'editor:move-selection-left' } # accelerator L
|
||||||
{ label: 'Move Selection &Right', command: 'editor:move-selection-right' }
|
{ localisedLabel: 'core.menu.edit.columns.move-selection-right', command: 'editor:move-selection-right' } # accelerator R
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
label: 'Text',
|
localisedLabel: 'core.menu.edit.text.self'
|
||||||
submenu: [
|
submenu: [
|
||||||
{ label: '&Upper Case', command: 'editor:upper-case' }
|
{ localisedLabel: 'core.menu.edit.text.upper-case', command: 'editor:upper-case' } # accelerator U
|
||||||
{ label: '&Lower Case', command: 'editor:lower-case' }
|
{ localisedLabel: 'core.menu.edit.text.lower-case', command: 'editor:lower-case' } # accelerator L
|
||||||
{ type: 'separator' }
|
{ type: 'separator' }
|
||||||
{ label: 'Delete to End of &Word', command: 'editor:delete-to-end-of-word' }
|
{ localisedLabel: 'core.menu.edit.text.delete-to-end-of-word', command: 'editor:delete-to-end-of-word' } # accelerator W
|
||||||
{ label: 'Delete to Previous Word Boundary', command: 'editor:delete-to-previous-word-boundary' }
|
{ localisedLabel: 'core.menu.edit.text.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' }
|
{ localisedLabel: 'core.menu.edit.text.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-line', command: 'editor:delete-line' } # accelerator D
|
||||||
{ type: 'separator' }
|
{ 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: [
|
submenu: [
|
||||||
{ label: '&Fold', command: 'editor:fold-current-row' }
|
{ localisedLabel: 'core.menu.edit.folding.fold', command: 'editor:fold-current-row' } # accelerator F
|
||||||
{ label: '&Unfold', command: 'editor:unfold-current-row' }
|
{ localisedLabel: 'core.menu.edit.folding.unfold', command: 'editor:unfold-current-row' } # accelerator U
|
||||||
{ label: 'Fol&d All', command: 'editor:fold-all' }
|
{ localisedLabel: 'core.menu.edit.folding.fold-all', command: 'editor:fold-all' } # accelerator D
|
||||||
{ label: 'Unfold &All', command: 'editor:unfold-all' }
|
{ localisedLabel: 'core.menu.edit.folding.unfold-all', command: 'editor:unfold-all' } # accelerator A
|
||||||
{ type: 'separator' }
|
{ type: 'separator' }
|
||||||
{ label: 'Fold Level 1', command: 'editor:fold-at-indent-level-1' }
|
{ localisedLabel: 'core.menu.edit.folding.fold-level-1', command: 'editor:fold-at-indent-level-1' }
|
||||||
{ label: 'Fold Level 2', command: 'editor:fold-at-indent-level-2' }
|
{ localisedLabel: 'core.menu.edit.folding.fold-level-2', command: 'editor:fold-at-indent-level-2' }
|
||||||
{ label: 'Fold Level 3', command: 'editor:fold-at-indent-level-3' }
|
{ localisedLabel: 'core.menu.edit.folding.fold-level-3', command: 'editor:fold-at-indent-level-3' }
|
||||||
{ label: 'Fold Level 4', command: 'editor:fold-at-indent-level-4' }
|
{ localisedLabel: 'core.menu.edit.folding.fold-level-4', command: 'editor:fold-at-indent-level-4' }
|
||||||
{ label: 'Fold Level 5', command: 'editor:fold-at-indent-level-5' }
|
{ localisedLabel: 'core.menu.edit.folding.fold-level-5', command: 'editor:fold-at-indent-level-5' }
|
||||||
{ label: 'Fold Level 6', command: 'editor:fold-at-indent-level-6' }
|
{ localisedLabel: 'core.menu.edit.folding.fold-level-6', command: 'editor:fold-at-indent-level-6' }
|
||||||
{ label: 'Fold Level 7', command: 'editor:fold-at-indent-level-7' }
|
{ localisedLabel: 'core.menu.edit.folding.fold-level-7', command: 'editor:fold-at-indent-level-7' }
|
||||||
{ label: 'Fold Level 8', command: 'editor:fold-at-indent-level-8' }
|
{ localisedLabel: 'core.menu.edit.folding.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-9', command: 'editor:fold-at-indent-level-9' }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
{ type: 'separator' }
|
{ type: 'separator' }
|
||||||
{ label: '&Preferences', command: 'application:show-settings' }
|
{ localisedLabel: 'core.menu.pulsar.preferences', command: 'application:show-settings' } # accelerator P
|
||||||
{ type: 'separator' }
|
{ type: 'separator' }
|
||||||
{ label: 'Config…', command: 'application:open-your-config' }
|
{ localisedLabel: 'core.menu.pulsar.config', command: 'application:open-your-config' }
|
||||||
{ label: 'Init Script…', command: 'application:open-your-init-script' }
|
{ localisedLabel: 'core.menu.pulsar.init-script', command: 'application:open-your-init-script' }
|
||||||
{ label: 'Keymap…', command: 'application:open-your-keymap' }
|
{ localisedLabel: 'core.menu.pulsar.keymap', command: 'application:open-your-keymap' }
|
||||||
{ label: 'Snippets…', command: 'application:open-your-snippets' }
|
{ localisedLabel: 'core.menu.pulsar.snippets', command: 'application:open-your-snippets' }
|
||||||
{ label: 'Stylesheet…', command: 'application:open-your-stylesheet' }
|
{ localisedLabel: 'core.menu.pulsar.stylesheet', command: 'application:open-your-stylesheet' }
|
||||||
{ type: 'separator' }
|
{ type: 'separator' }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
label: '&View'
|
localisedLabel: 'core.menu.view.self' # accelerator V
|
||||||
submenu: [
|
submenu: [
|
||||||
{ label: 'Toggle &Full Screen', command: 'window:toggle-full-screen' }
|
{ localisedLabel: 'core.menu.view.toggle-full-screen', command: 'window:toggle-full-screen' } # accelerator F
|
||||||
{ label: 'Toggle Menu Bar', command: 'window:toggle-menu-bar' }
|
{ localisedLabel: 'core.menu.view.toggle-menu-bar', command: 'window:toggle-menu-bar' }
|
||||||
{
|
{
|
||||||
label: 'Panes'
|
localisedLabel: 'core.menu.view.panes.self'
|
||||||
submenu: [
|
submenu: [
|
||||||
{ label: 'Split Up', command: 'pane:split-up-and-copy-active-item' }
|
{ localisedLabel: 'core.menu.view.panes.split-up', command: 'pane:split-up-and-copy-active-item' }
|
||||||
{ label: 'Split Down', command: 'pane:split-down-and-copy-active-item' }
|
{ localisedLabel: 'core.menu.view.panes.split-down', command: 'pane:split-down-and-copy-active-item' }
|
||||||
{ label: 'Split Left', command: 'pane:split-left-and-copy-active-item' }
|
{ localisedLabel: 'core.menu.view.panes.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-right', command: 'pane:split-right-and-copy-active-item' }
|
||||||
{ type: 'separator' }
|
{ type: 'separator' }
|
||||||
{ label: 'Focus Next Pane', command: 'window:focus-next-pane' }
|
{ localisedLabel: 'core.menu.view.panes.focus-next', command: 'window:focus-next-pane' }
|
||||||
{ label: 'Focus Previous Pane', command: 'window:focus-previous-pane' }
|
{ localisedLabel: 'core.menu.view.panes.focus-previous', command: 'window:focus-previous-pane' }
|
||||||
{ type: 'separator' }
|
{ type: 'separator' }
|
||||||
{ label: 'Focus Pane Above', command: 'window:focus-pane-above' }
|
{ localisedLabel: 'core.menu.view.panes.focus-above', command: 'window:focus-pane-above' }
|
||||||
{ label: 'Focus Pane Below', command: 'window:focus-pane-below' }
|
{ localisedLabel: 'core.menu.view.panes.focus-below', command: 'window:focus-pane-below' }
|
||||||
{ label: 'Focus Pane On Left', command: 'window:focus-pane-on-left' }
|
{ localisedLabel: 'core.menu.view.panes.focus-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-on-right', command: 'window:focus-pane-on-right' }
|
||||||
{ type: 'separator' }
|
{ 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: [
|
submenu: [
|
||||||
{ label: 'Open In &Dev Mode…', command: 'application:open-dev' }
|
{ localisedLabel: 'core.menu.view.developer.open-in-dev-mode', command: 'application:open-dev' } # accelerator D
|
||||||
{ label: '&Reload Window', command: 'window:reload' }
|
{ localisedLabel: 'core.menu.view.developer.reload-window', command: 'window:reload' } # accelerator R
|
||||||
{ label: 'Run Package &Specs', command: 'window:run-package-specs' }
|
{ localisedLabel: 'core.menu.view.developer.run-package-specs', command: 'window:run-package-specs' } # accelerator S
|
||||||
{ label: 'Toggle Developer &Tools', command: 'window:toggle-dev-tools' }
|
{ localisedLabel: 'core.menu.view.developer.toggle-dev-tools', command: 'window:toggle-dev-tools' } # accelerator T
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
{ type: 'separator' }
|
{ type: 'separator' }
|
||||||
{ label: '&Increase Font Size', command: 'window:increase-font-size' }
|
{ localisedLabel: 'core.menu.view.increase-font-size', command: 'window:increase-font-size' } # accelerator I
|
||||||
{ label: '&Decrease Font Size', command: 'window:decrease-font-size' }
|
{ localisedLabel: 'core.menu.view.decrease-font-size', command: 'window:decrease-font-size' } # accelerator D
|
||||||
{ label: 'Re&set Font Size', command: 'window:reset-font-size' }
|
{ localisedLabel: 'core.menu.view.reset-font-size', command: 'window:reset-font-size' } # accelerator S
|
||||||
{ type: 'separator' }
|
{ 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: [
|
submenu: [
|
||||||
{ label: 'Add Selection &Above', command: 'editor:add-selection-above' }
|
{ localisedLabel: 'core.menu.selection.add-above', command: 'editor:add-selection-above' } # accelerator A
|
||||||
{ label: 'Add Selection &Below', command: 'editor:add-selection-below' }
|
{ localisedLabel: 'core.menu.selection.add-below', command: 'editor:add-selection-below' } # accelerator B
|
||||||
{ label: 'S&plit into Lines', command: 'editor:split-selections-into-lines'}
|
{ localisedLabel: 'core.menu.selection.single', command: 'editor:consolidate-selections' }
|
||||||
{ label: 'Single Selection', command: 'editor:consolidate-selections'}
|
{ localisedLabel: 'core.menu.selection.split-into-lines', command: 'editor:split-selections-into-lines' } # accelerator P
|
||||||
{ type: 'separator' }
|
{ type: 'separator' }
|
||||||
{ label: 'Select to &Top', command: 'core:select-to-top' }
|
{ localisedLabel: 'core.menu.selection.to-top', command: 'core:select-to-top' } # accelerator T
|
||||||
{ label: 'Select to Botto&m', command: 'core:select-to-bottom' }
|
{ localisedLabel: 'core.menu.selection.to-bottom', command: 'core:select-to-bottom' } # accelerator M
|
||||||
{ type: 'separator' }
|
{ type: 'separator' }
|
||||||
{ label: 'Select &Line', command: 'editor:select-line' }
|
{ localisedLabel: 'core.menu.selection.line', command: 'editor:select-line' } # accelerator L
|
||||||
{ label: 'Select &Word', command: 'editor:select-word' }
|
{ localisedLabel: 'core.menu.selection.word', command: 'editor:select-word' } # accelerator W
|
||||||
{ label: 'Select to Beginning of W&ord', command: 'editor:select-to-beginning-of-word' }
|
{ localisedLabel: 'core.menu.selection.to-beginning-of-word', command: 'editor:select-to-beginning-of-word' } # accelerator O
|
||||||
{ label: 'Select to Beginning of L&ine', command: 'editor:select-to-beginning-of-line' }
|
{ localisedLabel: 'core.menu.selection.to-beginning-of-line', command: 'editor:select-to-beginning-of-line' } # accelerator I
|
||||||
{ label: 'Select to First &Character of Line', command: 'editor:select-to-first-character-of-line' }
|
{ localisedLabel: 'core.menu.selection.to-first-char-of-line', command: 'editor:select-to-first-character-of-line' } # accelerator C
|
||||||
{ label: 'Select to End of Wor&d', command: 'editor:select-to-end-of-word' }
|
{ localisedLabel: 'core.menu.selection.to-end-of-word', command: 'editor:select-to-end-of-word' } # accelerator D
|
||||||
{ label: 'Select to End of Lin&e', command: 'editor:select-to-end-of-line' }
|
{ 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: []
|
submenu: []
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
label: '&Packages'
|
localisedLabel: 'core.menu.packages.self' # accelerator P
|
||||||
submenu: [
|
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' }
|
{ type: 'separator' }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
label: '&Help'
|
localisedLabel: 'core.menu.help.self' # accelerator H
|
||||||
submenu: [
|
submenu: [
|
||||||
{ label: 'View &Terms of Use', command: 'application:open-terms-of-use' }
|
{ localisedLabel: 'core.menu.help.terms-of-use', command: 'application:open-terms-of-use' } # accelerator T
|
||||||
{ label: 'View &License', command: 'application:open-license' }
|
{ localisedLabel: 'core.menu.pulsar.view-license', command: 'application:open-license' } # accelerator L
|
||||||
{ label: "VERSION", enabled: false }
|
{ localisedLabel: 'core.menu.pulsar.version', enabled: false }
|
||||||
{ type: 'separator' }
|
{ type: 'separator' }
|
||||||
{ label: '&Documentation', command: 'application:open-documentation' }
|
{ localisedLabel: 'core.menu.help.docs', command: 'application:open-documentation' } # accelerator D
|
||||||
{ label: 'Frequently Asked Questions', command: 'application:open-faq' }
|
{ localisedLabel: 'core.menu.help.faq', command: 'application:open-faq' }
|
||||||
{ type: 'separator' }
|
{ type: 'separator' }
|
||||||
{ label: 'Community Discussions', command: 'application:open-discussions' }
|
{ localisedLabel: 'core.menu.help.community-discussions', command: 'application:open-discussions' }
|
||||||
{ label: 'Report Issue', command: 'application:report-issue' }
|
{ localisedLabel: 'core.menu.help.report-issue', 'application:report-issue' }
|
||||||
{ label: 'Search Issues', command: 'application:search-issues' }
|
{ localisedLabel: 'core.menu.help.search-issues', command: 'application:search-issues' }
|
||||||
{ type: 'separator' }
|
{ type: 'separator' }
|
||||||
{ label: 'About Pulsar', command: 'application:about' }
|
{ localisedLabel: 'core.menu.pulsar.about', command: 'application:about' }
|
||||||
{ type: 'separator' }
|
{ type: 'separator' }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
10
package.json
10
package.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "pulsar",
|
"name": "pulsar",
|
||||||
"author": "Pulsar Community <noreply@pulsar-edit.com>",
|
"author": "Pulsar-Edit <admin@pulsar-edit.dev>",
|
||||||
"productName": "Pulsar",
|
"productName": "Pulsar",
|
||||||
"version": "1.103.0-dev",
|
"version": "1.103.0-dev",
|
||||||
"description": "A Community-led Hyper-Hackable Text Editor",
|
"description": "A Community-led Hyper-Hackable Text Editor",
|
||||||
@ -24,6 +24,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@atom/source-map-support": "^0.3.4",
|
"@atom/source-map-support": "^0.3.4",
|
||||||
"@babel/core": "7.18.6",
|
"@babel/core": "7.18.6",
|
||||||
|
"@formatjs/icu-messageformat-parser": "^2.3.0",
|
||||||
"about": "file:packages/about",
|
"about": "file:packages/about",
|
||||||
"archive-view": "file:packages/archive-view",
|
"archive-view": "file:packages/archive-view",
|
||||||
"async": "3.2.4",
|
"async": "3.2.4",
|
||||||
@ -45,7 +46,7 @@
|
|||||||
"base16-tomorrow-dark-theme": "file:packages/base16-tomorrow-dark-theme",
|
"base16-tomorrow-dark-theme": "file:packages/base16-tomorrow-dark-theme",
|
||||||
"base16-tomorrow-light-theme": "file:packages/base16-tomorrow-light-theme",
|
"base16-tomorrow-light-theme": "file:packages/base16-tomorrow-light-theme",
|
||||||
"bookmarks": "file:packages/bookmarks",
|
"bookmarks": "file:packages/bookmarks",
|
||||||
"bracket-matcher": "https://github.com/pulsar-edit/bracket-matcher.git#c877977",
|
"bracket-matcher": "file:packages/bracket-matcher",
|
||||||
"chai": "4.3.4",
|
"chai": "4.3.4",
|
||||||
"clear-cut": "^2.0.2",
|
"clear-cut": "^2.0.2",
|
||||||
"coffeescript": "1.12.7",
|
"coffeescript": "1.12.7",
|
||||||
@ -55,14 +56,14 @@
|
|||||||
"dedent": "^0.7.0",
|
"dedent": "^0.7.0",
|
||||||
"deprecation-cop": "file:packages/deprecation-cop",
|
"deprecation-cop": "file:packages/deprecation-cop",
|
||||||
"dev-live-reload": "file:packages/dev-live-reload",
|
"dev-live-reload": "file:packages/dev-live-reload",
|
||||||
"document-register-element": "^1.14.10",
|
"document-register-element": "https://github.com/pulsar-edit/document-register-element.git#1f5868f",
|
||||||
"encoding-selector": "file:packages/encoding-selector",
|
"encoding-selector": "file:packages/encoding-selector",
|
||||||
"etch": "0.14.1",
|
"etch": "0.14.1",
|
||||||
"event-kit": "^2.5.3",
|
"event-kit": "^2.5.3",
|
||||||
"exception-reporting": "file:packages/exception-reporting",
|
"exception-reporting": "file:packages/exception-reporting",
|
||||||
"find-and-replace": "https://github.com/atom-community/find-and-replace/archive/refs/tags/v0.220.1.tar.gz",
|
"find-and-replace": "https://github.com/atom-community/find-and-replace/archive/refs/tags/v0.220.1.tar.gz",
|
||||||
"find-parent-dir": "^0.3.0",
|
"find-parent-dir": "^0.3.0",
|
||||||
"first-mate": "7.4.3",
|
"second-mate": "https://github.com/pulsar-edit/second-mate.git#14aa7bd",
|
||||||
"focus-trap": "6.3.0",
|
"focus-trap": "6.3.0",
|
||||||
"fs-admin": "0.19.0",
|
"fs-admin": "0.19.0",
|
||||||
"fs-plus": "^3.1.1",
|
"fs-plus": "^3.1.1",
|
||||||
@ -77,6 +78,7 @@
|
|||||||
"grim": "2.0.3",
|
"grim": "2.0.3",
|
||||||
"image-view": "file:packages/image-view",
|
"image-view": "file:packages/image-view",
|
||||||
"incompatible-packages": "file:packages/incompatible-packages",
|
"incompatible-packages": "file:packages/incompatible-packages",
|
||||||
|
"intl-messageformat": "^10.3.3",
|
||||||
"jasmine-json": "~0.0",
|
"jasmine-json": "~0.0",
|
||||||
"jasmine-reporters": "1.1.0",
|
"jasmine-reporters": "1.1.0",
|
||||||
"jasmine-tagged": "^1.1.4",
|
"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-atom-api** | [`atom/autocomplete-atom-api`][autocomplete-atom-api] | |
|
||||||
| **autocomplete-css** | [`./autocomplete-css`](./autocomplete-css) | |
|
| **autocomplete-css** | [`./autocomplete-css`](./autocomplete-css) | |
|
||||||
| **autocomplete-html** | [`./autocomplete-html`](./autocomplete-html) | |
|
| **autocomplete-html** | [`./autocomplete-html`](./autocomplete-html) | |
|
||||||
| **autocomplete-plus** | [`./autocomplete-plus`][./autocomplete-plus] | |
|
| **autocomplete-plus** | [`./autocomplete-plus`](./autocomplete-plus) | |
|
||||||
| **autocomplete-snippets** | [`./autocomplete-snippets`](./autocomplete-snippets) | |
|
| **autocomplete-snippets** | [`./autocomplete-snippets`](./autocomplete-snippets) | |
|
||||||
| **autoflow** | [`./autoflow`](./autoflow) | |
|
| **autoflow** | [`./autoflow`](./autoflow) | |
|
||||||
| **autosave** | [`pulsar-edit/autosave`][autosave] | [#17834](https://github.com/atom/atom/issues/17834) |
|
| **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) | |
|
| **language-yaml** | [`./language-yaml`](./language-yaml) | |
|
||||||
| **line-ending-selector** | [`./line-ending-selector`](./line-ending-selector) | |
|
| **line-ending-selector** | [`./line-ending-selector`](./line-ending-selector) | |
|
||||||
| **link** | [`./link`](./link) | |
|
| **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) |
|
| **notifications** | [`atom/notifications`][notifications] | [#18277](https://github.com/atom/atom/issues/18277) |
|
||||||
| **one-dark-syntax** | [`./one-dark-syntax`](./one-dark-syntax) | |
|
| **one-dark-syntax** | [`./one-dark-syntax`](./one-dark-syntax) | |
|
||||||
| **one-dark-ui** | [`./one-dark-ui`](./one-dark-ui) | |
|
| **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) | |
|
| **solarized-light-syntax** | [`./solarized-light-syntax`](./solarized-light-syntax) | |
|
||||||
| **spell-check** | [`atom/spell-check`][spell-check] | |
|
| **spell-check** | [`atom/spell-check`][spell-check] | |
|
||||||
| **status-bar** | [`./status-bar`](./status-bar) | |
|
| **status-bar** | [`./status-bar`](./status-bar) | |
|
||||||
| **styleguide** | [`./styleguide`][./styleguide] | |
|
| **styleguide** | [`./styleguide`](./styleguide) | |
|
||||||
| **symbols-view** | [`pulsar-edit/symbols-view`][symbols-view] | |
|
| **symbols-view** | [`pulsar-edit/symbols-view`][symbols-view] | |
|
||||||
| **tabs** | [`./tabs`](./tabs) | |
|
| **tabs** | [`./tabs`](./tabs) | |
|
||||||
| **timecop** | [`pulsar-edit/timecop`][timecop] | [#18272](https://github.com/atom/atom/issues/18272) |
|
| **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) | |
|
| **update-package-dependencies** | [`./update-package-dependencies`](./update-package-dependencies) | |
|
||||||
| **welcome** | [`./welcome`](./welcome) | |
|
| **welcome** | [`./welcome`](./welcome) | |
|
||||||
| **whitespace** | [`./whitespace`](./whitespace) | |
|
| **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
|
[autocomplete-atom-api]: https://github.com/pulsar-edit/autocomplete-atom-api
|
||||||
[autosave]: https://github.com/pulsar-edit/autosave
|
[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)
|
![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) {
|
function getLocalAttributeDocsURL (attribute, tag) {
|
||||||
return `${getTagDocsURL(tag)}#attr-${attribute}`
|
return `${getTagDocsURL(tag)}#attributes`
|
||||||
}
|
}
|
||||||
|
|
||||||
function getGlobalAttributeDocsURL (attribute) {
|
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": {
|
"engines": {
|
||||||
"atom": ">=0.174.0 <2.0.0"
|
"atom": ">=0.174.0 <2.0.0"
|
||||||
},
|
},
|
||||||
|
"scripts": {
|
||||||
|
"update": "node ./update/update.js",
|
||||||
|
"update:test": "jest ./update/update.test.js"
|
||||||
|
},
|
||||||
"providedServices": {
|
"providedServices": {
|
||||||
"autocomplete.provider": {
|
"autocomplete.provider": {
|
||||||
"versions": {
|
"versions": {
|
||||||
@ -16,6 +20,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"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})
|
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(() => {
|
beforeEach(() => {
|
||||||
waitsForPromise(() => atom.packages.activatePackage('autocomplete-html'))
|
waitsForPromise(() => atom.packages.activatePackage('autocomplete-html'))
|
||||||
waitsForPromise(() => atom.packages.activatePackage('language-html'))
|
waitsForPromise(() => atom.packages.activatePackage('language-html'))
|
||||||
@ -59,9 +79,9 @@ describe('HTML autocompletions', () => {
|
|||||||
editor.setCursorBufferPosition([0, 1])
|
editor.setCursorBufferPosition([0, 1])
|
||||||
|
|
||||||
const completions = getCompletions()
|
const completions = getCompletions()
|
||||||
expect(completions.length).toBe(113)
|
expect(completions.length).toBeGreaterThan(113) // Fun Fact last check this was 232
|
||||||
expect(completions[0].description).toContain('Creates a hyperlink to other web pages')
|
expect(completions[0].description.length).toBeGreaterThan(0)
|
||||||
expect(completions[0].descriptionMoreURL.endsWith('/HTML/Element/a')).toBe(true)
|
expect(completions[0].descriptionMoreURL.length).toBeGreaterThan(0)
|
||||||
|
|
||||||
for (let completion of completions) {
|
for (let completion of completions) {
|
||||||
expect(completion.text.length).toBeGreaterThan(0)
|
expect(completion.text.length).toBeGreaterThan(0)
|
||||||
@ -75,36 +95,35 @@ describe('HTML autocompletions', () => {
|
|||||||
editor.setCursorBufferPosition([0, 2])
|
editor.setCursorBufferPosition([0, 2])
|
||||||
|
|
||||||
let completions = getCompletions()
|
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].type).toBe('tag')
|
||||||
expect(completions[0].descriptionMoreURL.endsWith('/HTML/Element/datalist')).toBe(true)
|
expect(isValueInCompletions('dd', completions)).toBe(true)
|
||||||
expect(completions[1].text).toBe('dd')
|
expect(isValueInCompletions('del', completions)).toBe(true)
|
||||||
expect(completions[2].text).toBe('del')
|
expect(isValueInCompletions('details', completions)).toBe(true)
|
||||||
expect(completions[3].text).toBe('details')
|
expect(isValueInCompletions('dfn', completions)).toBe(true)
|
||||||
expect(completions[4].text).toBe('dfn')
|
expect(isValueInCompletions('dialog', completions)).toBe(true)
|
||||||
expect(completions[5].text).toBe('dialog')
|
expect(isValueInCompletions('div', completions)).toBe(true)
|
||||||
expect(completions[6].text).toBe('div')
|
expect(isValueInCompletions('dl', completions)).toBe(true)
|
||||||
expect(completions[7].text).toBe('dl')
|
expect(isValueInCompletions('dt', completions)).toBe(true)
|
||||||
expect(completions[8].text).toBe('dt')
|
|
||||||
|
|
||||||
editor.setText('<D')
|
editor.setText('<D')
|
||||||
editor.setCursorBufferPosition([0, 2])
|
editor.setCursorBufferPosition([0, 2])
|
||||||
|
|
||||||
completions = getCompletions()
|
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].type).toBe('tag')
|
||||||
expect(completions[1].text).toBe('dd')
|
expect(isValueInCompletions('dd', completions)).toBe(true)
|
||||||
expect(completions[2].text).toBe('del')
|
expect(isValueInCompletions('del', completions)).toBe(true)
|
||||||
expect(completions[3].text).toBe('details')
|
expect(isValueInCompletions('details', completions)).toBe(true)
|
||||||
expect(completions[4].text).toBe('dfn')
|
expect(isValueInCompletions('dfn', completions)).toBe(true)
|
||||||
expect(completions[5].text).toBe('dialog')
|
expect(isValueInCompletions('dialog', completions)).toBe(true)
|
||||||
expect(completions[6].text).toBe('div')
|
expect(isValueInCompletions('div', completions)).toBe(true)
|
||||||
expect(completions[7].text).toBe('dl')
|
expect(isValueInCompletions('dl', completions)).toBe(true)
|
||||||
expect(completions[8].text).toBe('dt')
|
expect(isValueInCompletions('dt', completions)).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("does not autocomplete tag names if there's a space after the <", () => {
|
it("does not autocomplete tag names if there's a space after the <", () => {
|
||||||
@ -122,15 +141,16 @@ describe('HTML autocompletions', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('does not provide a descriptionMoreURL if the tag does not have a unique description', () => {
|
it('does not provide a descriptionMoreURL if the tag does not have a unique description', () => {
|
||||||
// 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.setText('<i')
|
||||||
editor.setCursorBufferPosition([0, 2])
|
editor.setCursorBufferPosition([0, 2])
|
||||||
|
|
||||||
const completions = getCompletions()
|
const completions = getCompletions()
|
||||||
|
const loc = getValueInCompletionsIndex('isindex', completions)
|
||||||
|
|
||||||
expect(completions[2].text).toBe('ilayer')
|
expect(isValueInCompletions('isindex', completions)).toBe(true)
|
||||||
expect(completions[2].description).toBe('HTML <ilayer> tag')
|
expect(completions[loc].description).toBe("HTML <isindex> tag")
|
||||||
expect(completions[2].descriptionMoreURL).toBeNull()
|
expect(completions[loc].descriptionMoreURL).toBeNull()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('autocompletes attribute names without a prefix', () => {
|
it('autocompletes attribute names without a prefix', () => {
|
||||||
@ -138,9 +158,9 @@ describe('HTML autocompletions', () => {
|
|||||||
editor.setCursorBufferPosition([0, 5])
|
editor.setCursorBufferPosition([0, 5])
|
||||||
|
|
||||||
let completions = getCompletions()
|
let completions = getCompletions()
|
||||||
expect(completions.length).toBe(86)
|
expect(completions.length).toBeGreaterThan(86) // Fun fact last check this was 264
|
||||||
expect(completions[0].description).toContain('Provides a hint for generating a keyboard shortcut')
|
expect(completions[0].description.length).toBeGreaterThan(0)
|
||||||
expect(completions[0].descriptionMoreURL.endsWith('/HTML/Global_attributes/accesskey')).toBe(true)
|
expect(completions[0].descriptionMoreURL.length).toBeGreaterThan(0)
|
||||||
|
|
||||||
for (var completion of completions) {
|
for (var completion of completions) {
|
||||||
expect(completion.snippet.length).toBeGreaterThan(0)
|
expect(completion.snippet.length).toBeGreaterThan(0)
|
||||||
@ -153,9 +173,9 @@ describe('HTML autocompletions', () => {
|
|||||||
editor.setCursorBufferPosition([0, 9])
|
editor.setCursorBufferPosition([0, 9])
|
||||||
|
|
||||||
completions = getCompletions()
|
completions = getCompletions()
|
||||||
expect(completions.length).toBe(98)
|
expect(completions.length).toBeGreaterThan(98) // Last check 274
|
||||||
expect(completions[0].rightLabel).toBe('<marquee>')
|
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) {
|
for (completion of completions) {
|
||||||
expect(completion.snippet.length).toBeGreaterThan(0)
|
expect(completion.snippet.length).toBeGreaterThan(0)
|
||||||
@ -186,57 +206,59 @@ describe('HTML autocompletions', () => {
|
|||||||
editor.setCursorBufferPosition([0, 6])
|
editor.setCursorBufferPosition([0, 6])
|
||||||
|
|
||||||
let completions = getCompletions()
|
let completions = getCompletions()
|
||||||
expect(completions.length).toBe(3)
|
expect(completions.length).toBeGreaterThan(3) // Last check 9
|
||||||
|
|
||||||
expect(completions[0].snippet).toBe('class="$1"$0')
|
let loc = getValueInCompletionsIndex('class', completions, 'displayText')
|
||||||
expect(completions[0].displayText).toBe('class')
|
|
||||||
expect(completions[0].type).toBe('attribute')
|
expect(completions[loc].snippet).toBe('class="$1"$0')
|
||||||
expect(completions[1].displayText).toBe('contenteditable')
|
expect(completions[loc].displayText).toBe('class')
|
||||||
expect(completions[2].displayText).toBe('contextmenu')
|
expect(completions[loc].type).toBe('attribute')
|
||||||
|
expect(isValueInCompletions('contenteditable', completions, 'displayText'))
|
||||||
|
expect(isValueInCompletions('contextmenu', completions, 'displayText'))
|
||||||
|
|
||||||
editor.setText('<div C')
|
editor.setText('<div C')
|
||||||
editor.setCursorBufferPosition([0, 6])
|
editor.setCursorBufferPosition([0, 6])
|
||||||
|
|
||||||
completions = getCompletions()
|
completions = getCompletions()
|
||||||
expect(completions.length).toBe(3)
|
expect(completions.length).toBeGreaterThan(3) // Last check 9
|
||||||
|
|
||||||
expect(completions[0].displayText).toBe('class')
|
expect(isValueInCompletions('class', completions, 'displayText'))
|
||||||
expect(completions[1].displayText).toBe('contenteditable')
|
expect(isValueInCompletions('contenteditable', completions, 'displayText'))
|
||||||
expect(completions[2].displayText).toBe('contextmenu')
|
expect(isValueInCompletions('contextmenu', completions, 'displayText'))
|
||||||
|
|
||||||
editor.setText('<div c>')
|
editor.setText('<div c>')
|
||||||
editor.setCursorBufferPosition([0, 6])
|
editor.setCursorBufferPosition([0, 6])
|
||||||
|
|
||||||
completions = getCompletions()
|
completions = getCompletions()
|
||||||
expect(completions.length).toBe(3)
|
expect(completions.length).toBeGreaterThan(3)
|
||||||
|
|
||||||
expect(completions[0].displayText).toBe('class')
|
expect(isValueInCompletions('class', completions, 'displayText'))
|
||||||
expect(completions[1].displayText).toBe('contenteditable')
|
expect(isValueInCompletions('contenteditable', completions, 'displayText'))
|
||||||
expect(completions[2].displayText).toBe('contextmenu')
|
expect(isValueInCompletions('contextmenu', completions, 'displayText'))
|
||||||
|
|
||||||
editor.setText('<div c></div>')
|
editor.setText('<div c></div>')
|
||||||
editor.setCursorBufferPosition([0, 6])
|
editor.setCursorBufferPosition([0, 6])
|
||||||
|
|
||||||
completions = getCompletions()
|
completions = getCompletions()
|
||||||
expect(completions.length).toBe(3)
|
expect(completions.length).toBeGreaterThan(3)
|
||||||
|
|
||||||
expect(completions[0].displayText).toBe('class')
|
expect(isValueInCompletions('class', completions, 'displayText'))
|
||||||
expect(completions[1].displayText).toBe('contenteditable')
|
expect(isValueInCompletions('contenteditable', completions, 'displayText'))
|
||||||
expect(completions[2].displayText).toBe('contextmenu')
|
expect(isValueInCompletions('contextmenu', completions, 'displayText'))
|
||||||
|
|
||||||
editor.setText('<marquee di')
|
editor.setText('<marquee di')
|
||||||
editor.setCursorBufferPosition([0, 12])
|
editor.setCursorBufferPosition([0, 12])
|
||||||
|
|
||||||
completions = getCompletions()
|
completions = getCompletions()
|
||||||
expect(completions[0].displayText).toBe('direction')
|
expect(isValueInCompletions('direction', completions, 'displayText'))
|
||||||
expect(completions[1].displayText).toBe('dir')
|
expect(isValueInCompletions('dir', completions, 'displayText'))
|
||||||
|
|
||||||
editor.setText('<marquee dI')
|
editor.setText('<marquee dI')
|
||||||
editor.setCursorBufferPosition([0, 12])
|
editor.setCursorBufferPosition([0, 12])
|
||||||
|
|
||||||
completions = getCompletions()
|
completions = getCompletions()
|
||||||
expect(completions[0].displayText).toBe('direction')
|
expect(isValueInCompletions('direction', completions, 'displayText'))
|
||||||
expect(completions[1].displayText).toBe('dir')
|
expect(isValueInCompletions('dir', completions, 'displayText'))
|
||||||
})
|
})
|
||||||
|
|
||||||
it('autocompletes attribute names without a prefix surrounded by whitespace', () => {
|
it('autocompletes attribute names without a prefix surrounded by whitespace', () => {
|
||||||
@ -245,7 +267,7 @@ describe('HTML autocompletions', () => {
|
|||||||
|
|
||||||
const completions = getCompletions()
|
const completions = getCompletions()
|
||||||
for (let completion of completions) { expect(completion.type).toBe('attribute') }
|
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', () => {
|
it('autocompletes attribute names with a prefix surrounded by whitespace', () => {
|
||||||
@ -254,7 +276,7 @@ describe('HTML autocompletions', () => {
|
|||||||
|
|
||||||
const completions = getCompletions()
|
const completions = getCompletions()
|
||||||
for (let completion of completions) { expect(completion.type).toBe('attribute') }
|
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", () => {
|
it("respects the 'flag' type when autocompleting attribute names", () => {
|
||||||
@ -262,7 +284,7 @@ describe('HTML autocompletions', () => {
|
|||||||
editor.setCursorBufferPosition([0, 8])
|
editor.setCursorBufferPosition([0, 8])
|
||||||
|
|
||||||
const completions = getCompletions()
|
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', () => {
|
it('does not autocomplete attribute names in between an attribute name and value', () => {
|
||||||
@ -308,7 +330,7 @@ describe('HTML autocompletions', () => {
|
|||||||
editor.setCursorBufferPosition([0, 6])
|
editor.setCursorBufferPosition([0, 6])
|
||||||
|
|
||||||
const completions = getCompletions()
|
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', () => {
|
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()
|
const completions = getCompletions()
|
||||||
|
|
||||||
expect(completions[0].displayText).toBe('onabort')
|
const loc = getValueInCompletionsIndex('onabort', completions, 'displayText')
|
||||||
expect(completions[0].description).toBe('Global onabort attribute')
|
|
||||||
expect(completions[0].descriptionMoreURL).toBeNull()
|
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', () => {
|
it('autocompletes attribute values without a prefix', () => {
|
||||||
@ -332,7 +356,7 @@ describe('HTML autocompletions', () => {
|
|||||||
expect(completions[0].text).toBe('scroll')
|
expect(completions[0].text).toBe('scroll')
|
||||||
expect(completions[0].type).toBe('value')
|
expect(completions[0].type).toBe('value')
|
||||||
expect(completions[0].description.length).toBeGreaterThan(0)
|
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[1].text).toBe('slide')
|
||||||
expect(completions[2].text).toBe('alternate')
|
expect(completions[2].text).toBe('alternate')
|
||||||
|
|
||||||
@ -419,7 +443,7 @@ describe('HTML autocompletions', () => {
|
|||||||
expect(completions[0].text).toBe('button')
|
expect(completions[0].text).toBe('button')
|
||||||
expect(completions[0].type).toBe('value')
|
expect(completions[0].type).toBe('value')
|
||||||
expect(completions[0].description.length).toBeGreaterThan(0)
|
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[1].text).toBe('reset')
|
||||||
expect(completions[2].text).toBe('submit')
|
expect(completions[2].text).toBe('submit')
|
||||||
|
|
||||||
@ -433,7 +457,7 @@ describe('HTML autocompletions', () => {
|
|||||||
expect(completions[0].text).toBe('alternate')
|
expect(completions[0].text).toBe('alternate')
|
||||||
expect(completions[0].type).toBe('value')
|
expect(completions[0].type).toBe('value')
|
||||||
expect(completions[0].description.length).toBeGreaterThan(0)
|
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", () => {
|
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,
|
||||||
|
};
|
1
packages/bracket-matcher/CONTRIBUTING.md
Normal file
1
packages/bracket-matcher/CONTRIBUTING.md
Normal file
@ -0,0 +1 @@
|
|||||||
|
[See how you can contribute](https://github.com/pulsar-edit/.github/blob/main/CONTRIBUTING.md)
|
20
packages/bracket-matcher/LICENSE.md
Normal file
20
packages/bracket-matcher/LICENSE.md
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
Copyright (c) 2014 GitHub Inc.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of this software and associated documentation files (the
|
||||||
|
"Software"), to deal in the Software without restriction, including
|
||||||
|
without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
52
packages/bracket-matcher/README.md
Normal file
52
packages/bracket-matcher/README.md
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
# Bracket Matcher package
|
||||||
|
|
||||||
|
Highlights and jumps between `[]`, `()`, and `{}`. Also highlights matching XML
|
||||||
|
and HTML tags.
|
||||||
|
|
||||||
|
Autocompletes `[]`, `()`, `{}`, `""`, `''`, `“”`, `‘’`, `«»`, `‹›`, and
|
||||||
|
backticks by default.
|
||||||
|
|
||||||
|
Use <kbd>ctrl-m</kbd> to jump to the bracket matching the one adjacent to the cursor.
|
||||||
|
It jumps to the nearest enclosing bracket when there's no adjacent bracket,
|
||||||
|
|
||||||
|
Use <kbd>ctrl-cmd-m</kbd> to select all the text inside the current brackets.
|
||||||
|
|
||||||
|
Use <kbd>alt-cmd-.</kbd> to close the current XML/HTML tag.
|
||||||
|
|
||||||
|
---
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
Matching brackets and quotes are sensibly inserted for you. If you dislike this
|
||||||
|
functionality, you can disable it from the Bracket Matcher section of the
|
||||||
|
Settings View.
|
||||||
|
|
||||||
|
#### Custom Pairs
|
||||||
|
|
||||||
|
You can customize matching pairs in Bracket Matcher at any time. You can do so either globally via the Settings View or at the scope level via your `config.cson`. Changes take effect immediately.
|
||||||
|
|
||||||
|
* **Autocomplete Characters** - Comma-separated pairs that the editor will treat as brackets / quotes. Entries in this field override the package defaults.
|
||||||
|
* For example: `<>, (), []`
|
||||||
|
|
||||||
|
* **Pairs With Extra Newline** - Comma-separated pairs that enhance the editor's auto indent feature. When used, a newline is automatically added between the pair when enter is pressed between them. Note: This feature is meant to be used in combination with brackets defined for indentation by the active language package (`increaseIndentPattern` / `decreaseIndentPattern`).
|
||||||
|
Example:
|
||||||
|
```
|
||||||
|
fn main() {
|
||||||
|
| <---- Cursor positioned at one indent level higher
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Scoped settings
|
||||||
|
In addition to the global settings, you are also able to add scope-specific modifications to Pulsar in your `config.cson`. This is especially useful for editor rule changes specific to each language. Scope-specific settings override package defaults _and_ global settings.
|
||||||
|
Example:
|
||||||
|
```cson
|
||||||
|
".rust.source":
|
||||||
|
"bracket-matcher":
|
||||||
|
autocompleteCharacters: [
|
||||||
|
"()"
|
||||||
|
"[]"
|
||||||
|
"{}"
|
||||||
|
"<>"
|
||||||
|
"\"\""
|
||||||
|
"``"
|
||||||
|
]
|
||||||
|
```
|
18
packages/bracket-matcher/keymaps/bracket-matcher.cson
Normal file
18
packages/bracket-matcher/keymaps/bracket-matcher.cson
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
'atom-text-editor':
|
||||||
|
'ctrl-m': 'bracket-matcher:go-to-matching-bracket'
|
||||||
|
'ctrl-]': 'bracket-matcher:remove-brackets-from-selection'
|
||||||
|
|
||||||
|
'.platform-darwin atom-text-editor':
|
||||||
|
'ctrl-cmd-m': 'bracket-matcher:select-inside-brackets'
|
||||||
|
'alt-cmd-.': 'bracket-matcher:close-tag'
|
||||||
|
'ctrl-backspace': 'bracket-matcher:remove-matching-brackets'
|
||||||
|
|
||||||
|
'.platform-linux atom-text-editor':
|
||||||
|
'ctrl-alt-,': 'bracket-matcher:select-inside-brackets'
|
||||||
|
'ctrl-alt-.': 'bracket-matcher:close-tag'
|
||||||
|
'ctrl-alt-backspace': 'bracket-matcher:remove-matching-brackets'
|
||||||
|
|
||||||
|
'.platform-win32 atom-text-editor':
|
||||||
|
'ctrl-alt-,': 'bracket-matcher:select-inside-brackets'
|
||||||
|
'ctrl-alt-.': 'bracket-matcher:close-tag'
|
||||||
|
'ctrl-alt-backspace': 'bracket-matcher:remove-matching-brackets'
|
585
packages/bracket-matcher/lib/bracket-matcher-view.js
Normal file
585
packages/bracket-matcher/lib/bracket-matcher-view.js
Normal file
@ -0,0 +1,585 @@
|
|||||||
|
const {CompositeDisposable} = require('atom')
|
||||||
|
const _ = require('underscore-plus')
|
||||||
|
const {Range, Point} = require('atom')
|
||||||
|
const TagFinder = require('./tag-finder')
|
||||||
|
|
||||||
|
const MAX_ROWS_TO_SCAN = 10000
|
||||||
|
const ONE_CHAR_FORWARD_TRAVERSAL = Object.freeze(Point(0, 1))
|
||||||
|
const ONE_CHAR_BACKWARD_TRAVERSAL = Object.freeze(Point(0, -1))
|
||||||
|
const TWO_CHARS_BACKWARD_TRAVERSAL = Object.freeze(Point(0, -2))
|
||||||
|
const MAX_ROWS_TO_SCAN_FORWARD_TRAVERSAL = Object.freeze(Point(MAX_ROWS_TO_SCAN, 0))
|
||||||
|
const MAX_ROWS_TO_SCAN_BACKWARD_TRAVERSAL = Object.freeze(Point(-MAX_ROWS_TO_SCAN, 0))
|
||||||
|
|
||||||
|
module.exports =
|
||||||
|
class BracketMatcherView {
|
||||||
|
constructor (editor, editorElement, matchManager) {
|
||||||
|
this.destroy = this.destroy.bind(this)
|
||||||
|
this.updateMatch = this.updateMatch.bind(this)
|
||||||
|
this.editor = editor
|
||||||
|
this.matchManager = matchManager
|
||||||
|
this.gutter = this.editor.gutterWithName('line-number')
|
||||||
|
this.subscriptions = new CompositeDisposable()
|
||||||
|
this.tagFinder = new TagFinder(this.editor)
|
||||||
|
this.pairHighlighted = false
|
||||||
|
this.tagHighlighted = false
|
||||||
|
|
||||||
|
// ranges for possible selection
|
||||||
|
this.bracket1Range = null
|
||||||
|
this.bracket2Range = null
|
||||||
|
|
||||||
|
this.subscriptions.add(
|
||||||
|
this.editor.onDidTokenize(this.updateMatch),
|
||||||
|
this.editor.getBuffer().onDidChangeText(this.updateMatch),
|
||||||
|
this.editor.onDidChangeGrammar(this.updateMatch),
|
||||||
|
this.editor.onDidChangeSelectionRange(this.updateMatch),
|
||||||
|
this.editor.onDidAddCursor(this.updateMatch),
|
||||||
|
this.editor.onDidRemoveCursor(this.updateMatch),
|
||||||
|
|
||||||
|
atom.commands.add(editorElement, 'bracket-matcher:go-to-matching-bracket', () =>
|
||||||
|
this.goToMatchingBracket()
|
||||||
|
),
|
||||||
|
|
||||||
|
atom.commands.add(editorElement, 'bracket-matcher:go-to-enclosing-bracket', () =>
|
||||||
|
this.gotoPrecedingStartBracket()
|
||||||
|
),
|
||||||
|
|
||||||
|
atom.commands.add(editorElement, 'bracket-matcher:select-inside-brackets', () =>
|
||||||
|
this.selectInsideBrackets()
|
||||||
|
),
|
||||||
|
|
||||||
|
atom.commands.add(editorElement, 'bracket-matcher:close-tag', () =>
|
||||||
|
this.closeTag()
|
||||||
|
),
|
||||||
|
|
||||||
|
atom.commands.add(editorElement, 'bracket-matcher:remove-matching-brackets', () =>
|
||||||
|
this.removeMatchingBrackets()
|
||||||
|
),
|
||||||
|
|
||||||
|
atom.commands.add(editorElement, 'bracket-matcher:select-matching-brackets', () =>
|
||||||
|
this.selectMatchingBrackets()
|
||||||
|
),
|
||||||
|
|
||||||
|
this.editor.onDidDestroy(this.destroy)
|
||||||
|
)
|
||||||
|
|
||||||
|
this.updateMatch()
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy () {
|
||||||
|
this.subscriptions.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
updateMatch () {
|
||||||
|
if (this.pairHighlighted) {
|
||||||
|
this.editor.destroyMarker(this.startMarker.id)
|
||||||
|
this.editor.destroyMarker(this.endMarker.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.pairHighlighted = false
|
||||||
|
this.tagHighlighted = false
|
||||||
|
|
||||||
|
if (!this.editor.getLastSelection().isEmpty()) return
|
||||||
|
|
||||||
|
const {position, matchPosition} = this.findCurrentPair()
|
||||||
|
|
||||||
|
let startRange = null
|
||||||
|
let endRange = null
|
||||||
|
let highlightTag = false
|
||||||
|
let highlightPair = false
|
||||||
|
if (position && matchPosition) {
|
||||||
|
this.bracket1Range = (startRange = Range(position, position.traverse(ONE_CHAR_FORWARD_TRAVERSAL)))
|
||||||
|
this.bracket2Range = (endRange = Range(matchPosition, matchPosition.traverse(ONE_CHAR_FORWARD_TRAVERSAL)))
|
||||||
|
highlightPair = true
|
||||||
|
} else {
|
||||||
|
this.bracket1Range = null
|
||||||
|
this.bracket2Range = null
|
||||||
|
if (this.hasSyntaxTree()) {
|
||||||
|
({startRange, endRange} = this.findMatchingTagNameRangesWithSyntaxTree())
|
||||||
|
} else {
|
||||||
|
({startRange, endRange} = this.tagFinder.findMatchingTags())
|
||||||
|
if (this.isCursorOnCommentOrString()) return
|
||||||
|
}
|
||||||
|
if (startRange) {
|
||||||
|
highlightTag = true
|
||||||
|
highlightPair = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!highlightTag && !highlightPair) return
|
||||||
|
|
||||||
|
this.startMarker = this.createMarker(startRange)
|
||||||
|
this.endMarker = this.createMarker(endRange)
|
||||||
|
this.pairHighlighted = highlightPair
|
||||||
|
this.tagHighlighted = highlightTag
|
||||||
|
}
|
||||||
|
|
||||||
|
selectMatchingBrackets () {
|
||||||
|
if (!this.bracket1Range && !this.bracket2Range) return
|
||||||
|
this.editor.setSelectedBufferRanges([this.bracket1Range, this.bracket2Range])
|
||||||
|
this.matchManager.changeBracketsMode = true
|
||||||
|
}
|
||||||
|
|
||||||
|
removeMatchingBrackets () {
|
||||||
|
if (this.editor.hasMultipleCursors()) {
|
||||||
|
this.editor.backspace()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.editor.transact(() => {
|
||||||
|
if (this.editor.getLastSelection().isEmpty()) {
|
||||||
|
this.editor.selectLeft()
|
||||||
|
}
|
||||||
|
|
||||||
|
const text = this.editor.getSelectedText()
|
||||||
|
this.editor.moveRight()
|
||||||
|
|
||||||
|
// check if the character to the left is part of a pair
|
||||||
|
if (
|
||||||
|
this.matchManager.pairedCharacters.hasOwnProperty(text) ||
|
||||||
|
this.matchManager.pairedCharactersInverse.hasOwnProperty(text)
|
||||||
|
) {
|
||||||
|
let {position, matchPosition, bracket} = this.findCurrentPair()
|
||||||
|
|
||||||
|
if (position && matchPosition) {
|
||||||
|
this.editor.setCursorBufferPosition(matchPosition)
|
||||||
|
this.editor.delete()
|
||||||
|
// if on the same line and the cursor is in front of an end pair
|
||||||
|
// offset by one to make up for the missing character
|
||||||
|
if (position.row === matchPosition.row && this.matchManager.pairedCharactersInverse.hasOwnProperty(bracket)) {
|
||||||
|
position = position.traverse(ONE_CHAR_BACKWARD_TRAVERSAL)
|
||||||
|
}
|
||||||
|
this.editor.setCursorBufferPosition(position)
|
||||||
|
this.editor.delete()
|
||||||
|
} else {
|
||||||
|
this.editor.backspace()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.editor.backspace()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
findMatchingEndBracket (startBracketPosition, startBracket, endBracket) {
|
||||||
|
if (startBracket === endBracket) return
|
||||||
|
|
||||||
|
if (this.hasSyntaxTree()) {
|
||||||
|
return this.findMatchingEndBracketWithSyntaxTree(startBracketPosition, startBracket, endBracket)
|
||||||
|
} else {
|
||||||
|
const scopeDescriptor = this.editor.scopeDescriptorForBufferPosition(startBracketPosition)
|
||||||
|
if (this.isScopeCommentedOrString(scopeDescriptor.getScopesArray())) return
|
||||||
|
return this.findMatchingEndBracketWithRegexSearch(startBracketPosition, startBracket, endBracket)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
findMatchingStartBracket (endBracketPosition, startBracket, endBracket) {
|
||||||
|
if (startBracket === endBracket) return
|
||||||
|
|
||||||
|
if (this.hasSyntaxTree()) {
|
||||||
|
return this.findMatchingStartBracketWithSyntaxTree(endBracketPosition, startBracket, endBracket)
|
||||||
|
} else {
|
||||||
|
const scopeDescriptor = this.editor.scopeDescriptorForBufferPosition(endBracketPosition)
|
||||||
|
if (this.isScopeCommentedOrString(scopeDescriptor.getScopesArray())) return
|
||||||
|
return this.findMatchingStartBracketWithRegexSearch(endBracketPosition, startBracket, endBracket)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
findMatchingEndBracketWithSyntaxTree (bracketPosition, startBracket, endBracket) {
|
||||||
|
let result
|
||||||
|
const bracketEndPosition = bracketPosition.traverse([0, startBracket.length])
|
||||||
|
this.editor.buffer.getLanguageMode().getSyntaxNodeContainingRange(
|
||||||
|
new Range(bracketPosition, bracketEndPosition),
|
||||||
|
node => {
|
||||||
|
if (bracketEndPosition.isGreaterThan(node.startPosition) && bracketEndPosition.isLessThan(node.endPosition)) {
|
||||||
|
const matchNode = node.children.find(child =>
|
||||||
|
bracketEndPosition.isLessThanOrEqual(child.startPosition) &&
|
||||||
|
child.type === endBracket
|
||||||
|
)
|
||||||
|
if (matchNode) result = Point.fromObject(matchNode.startPosition)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
findMatchingStartBracketWithSyntaxTree (bracketPosition, startBracket, endBracket) {
|
||||||
|
let result
|
||||||
|
const bracketEndPosition = bracketPosition.traverse([0, startBracket.length])
|
||||||
|
this.editor.buffer.getLanguageMode().getSyntaxNodeContainingRange(
|
||||||
|
new Range(bracketPosition, bracketEndPosition),
|
||||||
|
node => {
|
||||||
|
if (bracketPosition.isGreaterThan(node.startPosition)) {
|
||||||
|
const matchNode = node.children.find(child =>
|
||||||
|
bracketPosition.isGreaterThanOrEqual(child.endPosition) &&
|
||||||
|
child.type === startBracket
|
||||||
|
)
|
||||||
|
if (matchNode) result = Point.fromObject(matchNode.startPosition)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
findMatchingTagNameRangesWithSyntaxTree () {
|
||||||
|
const position = this.editor.getCursorBufferPosition()
|
||||||
|
const {startTag, endTag} = this.findContainingTagsWithSyntaxTree(position)
|
||||||
|
if (startTag && (startTag.range.containsPoint(position) || endTag.range.containsPoint(position))) {
|
||||||
|
if (startTag === endTag) {
|
||||||
|
const {range} = startTag.child(1)
|
||||||
|
return {startRange: range, endRange: range}
|
||||||
|
} else if (endTag.firstChild.type === '</') {
|
||||||
|
return {
|
||||||
|
startRange: startTag.child(1).range,
|
||||||
|
endRange: endTag.child(1).range
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
startRange: startTag.child(1).range,
|
||||||
|
endRange: endTag.child(2).range
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
findMatchingTagsWithSyntaxTree () {
|
||||||
|
const position = this.editor.getCursorBufferPosition()
|
||||||
|
const {startTag, endTag} = this.findContainingTagsWithSyntaxTree(position)
|
||||||
|
if (startTag) {
|
||||||
|
return {startRange: startTag.range, endRange: endTag.range}
|
||||||
|
} else {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
findContainingTagsWithSyntaxTree (position) {
|
||||||
|
let startTag, endTag
|
||||||
|
if (position.column === this.editor.buffer.lineLengthForRow(position.row)) position.column--;
|
||||||
|
this.editor.buffer.getLanguageMode().getSyntaxNodeAtPosition(position, node => {
|
||||||
|
if (node.type.includes('element') && node.childCount > 0) {
|
||||||
|
const {firstChild, lastChild} = node
|
||||||
|
if (
|
||||||
|
firstChild.childCount > 2 &&
|
||||||
|
firstChild.firstChild.type === '<'
|
||||||
|
) {
|
||||||
|
if (lastChild === firstChild && firstChild.lastChild.type === '/>') {
|
||||||
|
startTag = firstChild
|
||||||
|
endTag = firstChild
|
||||||
|
} else if (
|
||||||
|
lastChild.childCount > 2 &&
|
||||||
|
(lastChild.firstChild.type === '</' ||
|
||||||
|
lastChild.firstChild.type === '<' && lastChild.child(1).type === '/')
|
||||||
|
) {
|
||||||
|
startTag = firstChild
|
||||||
|
endTag = lastChild
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return {startTag, endTag}
|
||||||
|
}
|
||||||
|
|
||||||
|
findMatchingEndBracketWithRegexSearch (startBracketPosition, startBracket, endBracket) {
|
||||||
|
const scanRange = new Range(
|
||||||
|
startBracketPosition.traverse(ONE_CHAR_FORWARD_TRAVERSAL),
|
||||||
|
startBracketPosition.traverse(MAX_ROWS_TO_SCAN_FORWARD_TRAVERSAL)
|
||||||
|
)
|
||||||
|
let endBracketPosition = null
|
||||||
|
let unpairedCount = 0
|
||||||
|
this.editor.scanInBufferRange(this.matchManager.pairRegexes[startBracket], scanRange, result => {
|
||||||
|
if (this.isRangeCommentedOrString(result.range)) return
|
||||||
|
switch (result.match[0]) {
|
||||||
|
case startBracket:
|
||||||
|
unpairedCount++
|
||||||
|
break
|
||||||
|
case endBracket:
|
||||||
|
unpairedCount--
|
||||||
|
if (unpairedCount < 0) {
|
||||||
|
endBracketPosition = result.range.start
|
||||||
|
result.stop()
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return endBracketPosition
|
||||||
|
}
|
||||||
|
|
||||||
|
findMatchingStartBracketWithRegexSearch (endBracketPosition, startBracket, endBracket) {
|
||||||
|
const scanRange = new Range(
|
||||||
|
endBracketPosition.traverse(MAX_ROWS_TO_SCAN_BACKWARD_TRAVERSAL),
|
||||||
|
endBracketPosition
|
||||||
|
)
|
||||||
|
let startBracketPosition = null
|
||||||
|
let unpairedCount = 0
|
||||||
|
this.editor.backwardsScanInBufferRange(this.matchManager.pairRegexes[startBracket], scanRange, result => {
|
||||||
|
if (this.isRangeCommentedOrString(result.range)) return
|
||||||
|
switch (result.match[0]) {
|
||||||
|
case startBracket:
|
||||||
|
unpairedCount--
|
||||||
|
if (unpairedCount < 0) {
|
||||||
|
startBracketPosition = result.range.start
|
||||||
|
result.stop()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case endBracket:
|
||||||
|
unpairedCount++
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return startBracketPosition
|
||||||
|
}
|
||||||
|
|
||||||
|
findPrecedingStartBracket (cursorPosition) {
|
||||||
|
if (this.hasSyntaxTree()) {
|
||||||
|
return this.findPrecedingStartBracketWithSyntaxTree(cursorPosition)
|
||||||
|
} else {
|
||||||
|
return this.findPrecedingStartBracketWithRegexSearch(cursorPosition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
findPrecedingStartBracketWithSyntaxTree (cursorPosition) {
|
||||||
|
let result
|
||||||
|
this.editor.buffer.getLanguageMode().getSyntaxNodeAtPosition(cursorPosition, node => {
|
||||||
|
for (const child of node.children) {
|
||||||
|
if (cursorPosition.isLessThanOrEqual(child.startPosition)) break
|
||||||
|
if (
|
||||||
|
child.type in this.matchManager.pairedCharacters ||
|
||||||
|
child.type in this.matchManager.pairedCharactersInverse
|
||||||
|
) {
|
||||||
|
result = Point.fromObject(child.startPosition)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
findPrecedingStartBracketWithRegexSearch (cursorPosition) {
|
||||||
|
const scanRange = new Range(Point.ZERO, cursorPosition)
|
||||||
|
const startBracket = _.escapeRegExp(_.keys(this.matchManager.pairedCharacters).join(''))
|
||||||
|
const endBracket = _.escapeRegExp(_.keys(this.matchManager.pairedCharactersInverse).join(''))
|
||||||
|
const combinedRegExp = new RegExp(`[${startBracket}${endBracket}]`, 'g')
|
||||||
|
const startBracketRegExp = new RegExp(`[${startBracket}]`, 'g')
|
||||||
|
const endBracketRegExp = new RegExp(`[${endBracket}]`, 'g')
|
||||||
|
let startPosition = null
|
||||||
|
let unpairedCount = 0
|
||||||
|
this.editor.backwardsScanInBufferRange(combinedRegExp, scanRange, result => {
|
||||||
|
if (this.isRangeCommentedOrString(result.range)) return
|
||||||
|
if (result.match[0].match(endBracketRegExp)) {
|
||||||
|
unpairedCount++
|
||||||
|
} else if (result.match[0].match(startBracketRegExp)) {
|
||||||
|
unpairedCount--
|
||||||
|
if (unpairedCount < 0) {
|
||||||
|
startPosition = result.range.start
|
||||||
|
result.stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return startPosition
|
||||||
|
}
|
||||||
|
|
||||||
|
createMarker (bufferRange) {
|
||||||
|
const marker = this.editor.markBufferRange(bufferRange)
|
||||||
|
this.editor.decorateMarker(marker, {type: 'highlight', class: 'bracket-matcher', deprecatedRegionClass: 'bracket-matcher'})
|
||||||
|
if (atom.config.get('bracket-matcher.highlightMatchingLineNumber', {scope: this.editor.getRootScopeDescriptor()}) && this.gutter) {
|
||||||
|
this.gutter.decorateMarker(marker, {type: 'highlight', class: 'bracket-matcher', deprecatedRegionClass: 'bracket-matcher'})
|
||||||
|
}
|
||||||
|
return marker
|
||||||
|
}
|
||||||
|
|
||||||
|
findCurrentPair () {
|
||||||
|
const currentPosition = this.editor.getCursorBufferPosition()
|
||||||
|
const previousPosition = currentPosition.traverse(ONE_CHAR_BACKWARD_TRAVERSAL)
|
||||||
|
const nextPosition = currentPosition.traverse(ONE_CHAR_FORWARD_TRAVERSAL)
|
||||||
|
const currentCharacter = this.editor.getTextInBufferRange(new Range(currentPosition, nextPosition))
|
||||||
|
const previousCharacter = this.editor.getTextInBufferRange(new Range(previousPosition, currentPosition))
|
||||||
|
|
||||||
|
let position, matchPosition, currentBracket, matchingBracket
|
||||||
|
if ((matchingBracket = this.matchManager.pairedCharacters[currentCharacter])) {
|
||||||
|
position = currentPosition
|
||||||
|
currentBracket = currentCharacter
|
||||||
|
matchPosition = this.findMatchingEndBracket(position, currentBracket, matchingBracket)
|
||||||
|
} else if ((matchingBracket = this.matchManager.pairedCharacters[previousCharacter])) {
|
||||||
|
position = previousPosition
|
||||||
|
currentBracket = previousCharacter
|
||||||
|
matchPosition = this.findMatchingEndBracket(position, currentBracket, matchingBracket)
|
||||||
|
} else if ((matchingBracket = this.matchManager.pairedCharactersInverse[previousCharacter])) {
|
||||||
|
position = previousPosition
|
||||||
|
currentBracket = previousCharacter
|
||||||
|
matchPosition = this.findMatchingStartBracket(position, matchingBracket, currentBracket)
|
||||||
|
} else if ((matchingBracket = this.matchManager.pairedCharactersInverse[currentCharacter])) {
|
||||||
|
position = currentPosition
|
||||||
|
currentBracket = currentCharacter
|
||||||
|
matchPosition = this.findMatchingStartBracket(position, matchingBracket, currentBracket)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {position, matchPosition, bracket: currentBracket}
|
||||||
|
}
|
||||||
|
|
||||||
|
goToMatchingBracket () {
|
||||||
|
if (!this.pairHighlighted) return this.gotoPrecedingStartBracket()
|
||||||
|
const position = this.editor.getCursorBufferPosition()
|
||||||
|
|
||||||
|
if (this.tagHighlighted) {
|
||||||
|
let tagCharacterOffset
|
||||||
|
let startRange = this.startMarker.getBufferRange()
|
||||||
|
const tagLength = startRange.end.column - startRange.start.column
|
||||||
|
let endRange = this.endMarker.getBufferRange()
|
||||||
|
if (startRange.compare(endRange) > 0) {
|
||||||
|
[startRange, endRange] = [endRange, startRange]
|
||||||
|
}
|
||||||
|
|
||||||
|
// include the <
|
||||||
|
startRange = new Range(startRange.start.traverse(ONE_CHAR_BACKWARD_TRAVERSAL), endRange.end.traverse(ONE_CHAR_BACKWARD_TRAVERSAL))
|
||||||
|
// include the </
|
||||||
|
endRange = new Range(endRange.start.traverse(TWO_CHARS_BACKWARD_TRAVERSAL), endRange.end.traverse(TWO_CHARS_BACKWARD_TRAVERSAL))
|
||||||
|
|
||||||
|
if (position.isLessThan(endRange.start)) {
|
||||||
|
tagCharacterOffset = position.column - startRange.start.column
|
||||||
|
if (tagCharacterOffset > 0) { tagCharacterOffset++ }
|
||||||
|
tagCharacterOffset = Math.min(tagCharacterOffset, tagLength + 2) // include </
|
||||||
|
this.editor.setCursorBufferPosition(endRange.start.traverse([0, tagCharacterOffset]))
|
||||||
|
} else {
|
||||||
|
tagCharacterOffset = position.column - endRange.start.column
|
||||||
|
if (tagCharacterOffset > 1) { tagCharacterOffset-- }
|
||||||
|
tagCharacterOffset = Math.min(tagCharacterOffset, tagLength + 1) // include <
|
||||||
|
this.editor.setCursorBufferPosition(startRange.start.traverse([0, tagCharacterOffset]))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const previousPosition = position.traverse(ONE_CHAR_BACKWARD_TRAVERSAL)
|
||||||
|
const startPosition = this.startMarker.getStartBufferPosition()
|
||||||
|
const endPosition = this.endMarker.getStartBufferPosition()
|
||||||
|
|
||||||
|
if (position.isEqual(startPosition)) {
|
||||||
|
this.editor.setCursorBufferPosition(endPosition.traverse(ONE_CHAR_FORWARD_TRAVERSAL))
|
||||||
|
} else if (previousPosition.isEqual(startPosition)) {
|
||||||
|
this.editor.setCursorBufferPosition(endPosition)
|
||||||
|
} else if (position.isEqual(endPosition)) {
|
||||||
|
this.editor.setCursorBufferPosition(startPosition.traverse(ONE_CHAR_FORWARD_TRAVERSAL))
|
||||||
|
} else if (previousPosition.isEqual(endPosition)) {
|
||||||
|
this.editor.setCursorBufferPosition(startPosition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gotoPrecedingStartBracket () {
|
||||||
|
if (this.pairHighlighted) return
|
||||||
|
|
||||||
|
const matchPosition = this.findPrecedingStartBracket(this.editor.getCursorBufferPosition())
|
||||||
|
if (matchPosition) {
|
||||||
|
this.editor.setCursorBufferPosition(matchPosition)
|
||||||
|
} else {
|
||||||
|
let startRange, endRange
|
||||||
|
if (this.hasSyntaxTree()) {
|
||||||
|
({startRange, endRange} = this.findMatchingTagsWithSyntaxTree())
|
||||||
|
} else {
|
||||||
|
({startRange, endRange} = this.tagFinder.findStartEndTags())
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startRange) {
|
||||||
|
if (startRange.compare(endRange) > 0) {
|
||||||
|
[startRange, endRange] = [endRange, startRange]
|
||||||
|
}
|
||||||
|
this.editor.setCursorBufferPosition(startRange.start)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
multiCursorSelect() {
|
||||||
|
this.editor.getCursorBufferPositions().forEach(position => {
|
||||||
|
let startPosition = this.findPrecedingStartBracket(position)
|
||||||
|
if(startPosition) {
|
||||||
|
const startBracket = this.editor.getTextInRange(Range.fromPointWithDelta(startPosition, 0, 1))
|
||||||
|
const endPosition = this.findMatchingEndBracket(startPosition, startBracket, this.matchManager.pairedCharacters[startBracket])
|
||||||
|
startPosition = startPosition.traverse([0, 1])
|
||||||
|
if (startPosition && endPosition) {
|
||||||
|
const rangeToSelect = new Range(startPosition, endPosition)
|
||||||
|
this.editor.addSelectionForBufferRange(rangeToSelect)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let startRange, endRange;
|
||||||
|
if (this.hasSyntaxTree()) {
|
||||||
|
({startRange, endRange} = this.findMatchingTagsWithSyntaxTree())
|
||||||
|
} else {
|
||||||
|
({startRange, endRange} = this.tagFinder.findStartEndTags(true))
|
||||||
|
if (startRange && startRange.compare(endRange) > 0) {
|
||||||
|
[startRange, endRange] = [endRange, startRange]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (startRange) {
|
||||||
|
const startPosition = startRange.end
|
||||||
|
const endPosition = endRange.start
|
||||||
|
const rangeToSelect = new Range(startPosition, endPosition)
|
||||||
|
this.editor.setSelectedBufferRange(rangeToSelect)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
selectInsideBrackets () {
|
||||||
|
let endPosition, endRange, startPosition, startRange
|
||||||
|
if (this.pairHighlighted) {
|
||||||
|
startRange = this.startMarker.getBufferRange()
|
||||||
|
endRange = this.endMarker.getBufferRange()
|
||||||
|
|
||||||
|
if (this.tagHighlighted) {
|
||||||
|
if (this.hasSyntaxTree()) {
|
||||||
|
({startRange, endRange} = this.findMatchingTagsWithSyntaxTree())
|
||||||
|
} else {
|
||||||
|
({startRange, endRange} = this.tagFinder.findStartEndTags(true))
|
||||||
|
if (startRange && startRange.compare(endRange) > 0) {
|
||||||
|
[startRange, endRange] = [endRange, startRange]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
startPosition = startRange.end
|
||||||
|
endPosition = endRange.start
|
||||||
|
|
||||||
|
const rangeToSelect = new Range(startPosition, endPosition)
|
||||||
|
this.editor.setSelectedBufferRange(rangeToSelect)
|
||||||
|
} else {
|
||||||
|
this.multiCursorSelect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert at the current cursor position a closing tag if there exists an
|
||||||
|
// open tag that is not closed afterwards.
|
||||||
|
closeTag () {
|
||||||
|
const cursorPosition = this.editor.getCursorBufferPosition()
|
||||||
|
const preFragment = this.editor.getTextInBufferRange([Point.ZERO, cursorPosition])
|
||||||
|
const postFragment = this.editor.getTextInBufferRange([cursorPosition, Point.INFINITY])
|
||||||
|
|
||||||
|
const tag = this.tagFinder.closingTagForFragments(preFragment, postFragment)
|
||||||
|
if (tag) {
|
||||||
|
this.editor.insertText(`</${tag}>`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isCursorOnCommentOrString () {
|
||||||
|
return this.isScopeCommentedOrString(this.editor.getLastCursor().getScopeDescriptor().getScopesArray())
|
||||||
|
}
|
||||||
|
|
||||||
|
isRangeCommentedOrString (range) {
|
||||||
|
return this.isScopeCommentedOrString(this.editor.scopeDescriptorForBufferPosition(range.start).getScopesArray())
|
||||||
|
}
|
||||||
|
|
||||||
|
isScopeCommentedOrString (scopesArray) {
|
||||||
|
for (let scope of scopesArray.reverse()) {
|
||||||
|
scope = scope.split('.')
|
||||||
|
if (scope.includes('embedded') && scope.includes('source')) return false
|
||||||
|
if (scope.includes('comment') || scope.includes('string')) return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
hasSyntaxTree () {
|
||||||
|
return this.editor.buffer.getLanguageMode().getSyntaxNodeAtPosition
|
||||||
|
}
|
||||||
|
}
|
299
packages/bracket-matcher/lib/bracket-matcher.js
Normal file
299
packages/bracket-matcher/lib/bracket-matcher.js
Normal file
@ -0,0 +1,299 @@
|
|||||||
|
const _ = require('underscore-plus')
|
||||||
|
const {CompositeDisposable} = require('atom')
|
||||||
|
const SelectorCache = require('./selector-cache')
|
||||||
|
|
||||||
|
module.exports =
|
||||||
|
class BracketMatcher {
|
||||||
|
constructor (editor, editorElement, matchManager) {
|
||||||
|
this.insertText = this.insertText.bind(this)
|
||||||
|
this.insertNewline = this.insertNewline.bind(this)
|
||||||
|
this.backspace = this.backspace.bind(this)
|
||||||
|
this.editor = editor
|
||||||
|
this.matchManager = matchManager
|
||||||
|
this.subscriptions = new CompositeDisposable()
|
||||||
|
this.bracketMarkers = []
|
||||||
|
|
||||||
|
this.origEditorInsertText = this.editor.insertText.bind(this.editor)
|
||||||
|
_.adviseBefore(this.editor, 'insertText', this.insertText)
|
||||||
|
_.adviseBefore(this.editor, 'insertNewline', this.insertNewline)
|
||||||
|
_.adviseBefore(this.editor, 'backspace', this.backspace)
|
||||||
|
|
||||||
|
this.subscriptions.add(
|
||||||
|
atom.commands.add(editorElement, 'bracket-matcher:remove-brackets-from-selection', event => {
|
||||||
|
if (!this.removeBrackets()) event.abortKeyBinding()
|
||||||
|
}),
|
||||||
|
|
||||||
|
this.editor.onDidDestroy(() => this.unsubscribe())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
insertText (text, options) {
|
||||||
|
if (!text) return true
|
||||||
|
if ((options && options.select) || (options && options.undo === 'skip')) return true
|
||||||
|
|
||||||
|
let autoCompleteOpeningBracket, bracketMarker, pair
|
||||||
|
if (this.matchManager.changeBracketsMode) {
|
||||||
|
this.matchManager.changeBracketsMode = false
|
||||||
|
if (this.isClosingBracket(text)) {
|
||||||
|
text = this.matchManager.pairedCharactersInverse[text]
|
||||||
|
}
|
||||||
|
if (this.isOpeningBracket(text)) {
|
||||||
|
this.editor.mutateSelectedText(selection => {
|
||||||
|
const selectionText = selection.getText()
|
||||||
|
if (this.isOpeningBracket(selectionText)) {
|
||||||
|
selection.insertText(text)
|
||||||
|
}
|
||||||
|
if (this.isClosingBracket(selectionText)) {
|
||||||
|
selection.insertText(this.matchManager.pairedCharacters[text])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.wrapSelectionInBrackets(text)) return false
|
||||||
|
if (this.editor.hasMultipleCursors()) return true
|
||||||
|
|
||||||
|
const cursorBufferPosition = this.editor.getCursorBufferPosition()
|
||||||
|
const previousCharacters = this.editor.getTextInBufferRange([[cursorBufferPosition.row, 0], cursorBufferPosition])
|
||||||
|
const nextCharacter = this.editor.getTextInBufferRange([cursorBufferPosition, cursorBufferPosition.traverse([0, 1])])
|
||||||
|
const previousCharacter = previousCharacters.slice(-1)
|
||||||
|
|
||||||
|
const hasWordAfterCursor = /\w/.test(nextCharacter)
|
||||||
|
const hasWordBeforeCursor = /\w/.test(previousCharacter)
|
||||||
|
const hasQuoteBeforeCursor = this.isQuote(previousCharacter) && (previousCharacter === text[0])
|
||||||
|
const hasEscapeCharacterBeforeCursor = endsWithEscapeCharacter(previousCharacters)
|
||||||
|
const hasEscapeSequenceBeforeCursor = endsWithEscapeSequence(previousCharacters)
|
||||||
|
|
||||||
|
if (text === '#' && this.isCursorOnInterpolatedString()) {
|
||||||
|
autoCompleteOpeningBracket = this.getScopedSetting('bracket-matcher.autocompleteBrackets') && !hasEscapeCharacterBeforeCursor
|
||||||
|
text += '{'
|
||||||
|
pair = '}'
|
||||||
|
} else {
|
||||||
|
autoCompleteOpeningBracket = (
|
||||||
|
this.isOpeningBracket(text) &&
|
||||||
|
!hasWordAfterCursor &&
|
||||||
|
this.getScopedSetting('bracket-matcher.autocompleteBrackets') &&
|
||||||
|
!(this.isQuote(text) && (hasWordBeforeCursor || hasQuoteBeforeCursor || hasEscapeSequenceBeforeCursor)) &&
|
||||||
|
!hasEscapeCharacterBeforeCursor
|
||||||
|
)
|
||||||
|
pair = this.matchManager.pairedCharacters[text]
|
||||||
|
}
|
||||||
|
|
||||||
|
let skipOverExistingClosingBracket = false
|
||||||
|
if (this.isClosingBracket(text) && (nextCharacter === text) && !hasEscapeCharacterBeforeCursor) {
|
||||||
|
bracketMarker = this.bracketMarkers.find(marker => marker.isValid() && marker.getBufferRange().end.isEqual(cursorBufferPosition))
|
||||||
|
if (bracketMarker || this.getScopedSetting('bracket-matcher.alwaysSkipClosingPairs')) {
|
||||||
|
skipOverExistingClosingBracket = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (skipOverExistingClosingBracket) {
|
||||||
|
if (bracketMarker) bracketMarker.destroy()
|
||||||
|
_.remove(this.bracketMarkers, bracketMarker)
|
||||||
|
this.editor.moveRight()
|
||||||
|
return false
|
||||||
|
} else if (autoCompleteOpeningBracket) {
|
||||||
|
this.editor.transact(() => {
|
||||||
|
this.origEditorInsertText(text + pair)
|
||||||
|
this.editor.moveLeft()
|
||||||
|
})
|
||||||
|
const range = [cursorBufferPosition, cursorBufferPosition.traverse([0, text.length])]
|
||||||
|
this.bracketMarkers.push(this.editor.markBufferRange(range))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
insertNewline () {
|
||||||
|
if (this.editor.hasMultipleCursors()) return
|
||||||
|
if (!this.editor.getLastSelection().isEmpty()) return
|
||||||
|
|
||||||
|
const cursorBufferPosition = this.editor.getCursorBufferPosition()
|
||||||
|
const previousCharacters = this.editor.getTextInBufferRange([[cursorBufferPosition.row, 0], cursorBufferPosition])
|
||||||
|
const nextCharacter = this.editor.getTextInBufferRange([cursorBufferPosition, cursorBufferPosition.traverse([0, 1])])
|
||||||
|
const previousCharacter = previousCharacters.slice(-1)
|
||||||
|
const hasEscapeCharacterBeforeCursor = endsWithEscapeCharacter(previousCharacters)
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.matchManager.pairsWithExtraNewline[previousCharacter] === nextCharacter &&
|
||||||
|
!hasEscapeCharacterBeforeCursor
|
||||||
|
) {
|
||||||
|
this.editor.transact(() => {
|
||||||
|
this.origEditorInsertText('\n\n')
|
||||||
|
this.editor.moveUp()
|
||||||
|
if (this.getScopedSetting('editor.autoIndent')) {
|
||||||
|
const cursorRow = this.editor.getCursorBufferPosition().row
|
||||||
|
this.editor.autoIndentBufferRows(cursorRow, cursorRow + 1)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
backspace () {
|
||||||
|
if (this.editor.hasMultipleCursors()) return
|
||||||
|
if (!this.editor.getLastSelection().isEmpty()) return
|
||||||
|
|
||||||
|
const cursorBufferPosition = this.editor.getCursorBufferPosition()
|
||||||
|
const previousCharacters = this.editor.getTextInBufferRange([[cursorBufferPosition.row, 0], cursorBufferPosition])
|
||||||
|
const nextCharacter = this.editor.getTextInBufferRange([cursorBufferPosition, cursorBufferPosition.traverse([0, 1])])
|
||||||
|
const previousCharacter = previousCharacters.slice(-1)
|
||||||
|
const hasEscapeCharacterBeforeCursor = endsWithEscapeCharacter(previousCharacters)
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.matchManager.pairedCharacters[previousCharacter] === nextCharacter &&
|
||||||
|
!hasEscapeCharacterBeforeCursor &&
|
||||||
|
this.getScopedSetting('bracket-matcher.autocompleteBrackets')
|
||||||
|
) {
|
||||||
|
this.editor.transact(() => {
|
||||||
|
this.editor.moveLeft()
|
||||||
|
this.editor.delete()
|
||||||
|
this.editor.delete()
|
||||||
|
})
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
removeBrackets () {
|
||||||
|
let bracketsRemoved = false
|
||||||
|
this.editor.mutateSelectedText(selection => {
|
||||||
|
let selectionEnd
|
||||||
|
if (!this.selectionIsWrappedByMatchingBrackets(selection)) return
|
||||||
|
|
||||||
|
const range = selection.getBufferRange()
|
||||||
|
const options = {reversed: selection.isReversed()}
|
||||||
|
const selectionStart = range.start
|
||||||
|
if (range.start.row === range.end.row) {
|
||||||
|
selectionEnd = range.end.traverse([0, -2])
|
||||||
|
} else {
|
||||||
|
selectionEnd = range.end.traverse([0, -1])
|
||||||
|
}
|
||||||
|
|
||||||
|
const text = selection.getText()
|
||||||
|
selection.insertText(text.substring(1, text.length - 1))
|
||||||
|
selection.setBufferRange([selectionStart, selectionEnd], options)
|
||||||
|
bracketsRemoved = true
|
||||||
|
})
|
||||||
|
return bracketsRemoved
|
||||||
|
}
|
||||||
|
|
||||||
|
wrapSelectionInBrackets (bracket) {
|
||||||
|
let pair
|
||||||
|
if (bracket === '#') {
|
||||||
|
if (!this.isCursorOnInterpolatedString()) return false
|
||||||
|
bracket = '#{'
|
||||||
|
pair = '}'
|
||||||
|
} else {
|
||||||
|
if (!this.isOpeningBracket(bracket)) return false
|
||||||
|
pair = this.matchManager.pairedCharacters[bracket]
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.editor.selections.some(s => !s.isEmpty())) return false
|
||||||
|
if (!this.getScopedSetting('bracket-matcher.wrapSelectionsInBrackets')) return false
|
||||||
|
|
||||||
|
let selectionWrapped = false
|
||||||
|
this.editor.mutateSelectedText(selection => {
|
||||||
|
let selectionEnd
|
||||||
|
if (selection.isEmpty()) return
|
||||||
|
|
||||||
|
// Don't wrap in #{} if the selection spans more than one line
|
||||||
|
if ((bracket === '#{') && !selection.getBufferRange().isSingleLine()) return
|
||||||
|
|
||||||
|
selectionWrapped = true
|
||||||
|
const range = selection.getBufferRange()
|
||||||
|
const options = {reversed: selection.isReversed()}
|
||||||
|
selection.insertText(`${bracket}${selection.getText()}${pair}`)
|
||||||
|
const selectionStart = range.start.traverse([0, bracket.length])
|
||||||
|
if (range.start.row === range.end.row) {
|
||||||
|
selectionEnd = range.end.traverse([0, bracket.length])
|
||||||
|
} else {
|
||||||
|
selectionEnd = range.end
|
||||||
|
}
|
||||||
|
selection.setBufferRange([selectionStart, selectionEnd], options)
|
||||||
|
})
|
||||||
|
|
||||||
|
return selectionWrapped
|
||||||
|
}
|
||||||
|
|
||||||
|
isQuote (string) {
|
||||||
|
return /['"`]/.test(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
isCursorOnInterpolatedString () {
|
||||||
|
const cursor = this.editor.getLastCursor()
|
||||||
|
const languageMode = this.editor.getBuffer().getLanguageMode()
|
||||||
|
if (languageMode.getSyntaxNodeAtPosition) {
|
||||||
|
const node = languageMode.getSyntaxNodeAtPosition(
|
||||||
|
cursor.getBufferPosition(),
|
||||||
|
(node, grammar) => grammar.scopeName === 'source.ruby' && /string|symbol/.test(node.type)
|
||||||
|
)
|
||||||
|
if (node) {
|
||||||
|
const {firstChild} = node
|
||||||
|
if (firstChild) {
|
||||||
|
return ['"', ':"', '%('].includes(firstChild.text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
if (this.interpolatedStringSelector == null) {
|
||||||
|
const segments = [
|
||||||
|
'*.*.*.interpolated.ruby',
|
||||||
|
'string.interpolated.ruby',
|
||||||
|
'string.regexp.interpolated.ruby',
|
||||||
|
'string.quoted.double.coffee',
|
||||||
|
'string.unquoted.heredoc.ruby',
|
||||||
|
'string.quoted.double.livescript',
|
||||||
|
'string.quoted.double.heredoc.livescript',
|
||||||
|
'string.quoted.double.elixir',
|
||||||
|
'string.quoted.double.heredoc.elixir',
|
||||||
|
'comment.documentation.heredoc.elixir'
|
||||||
|
]
|
||||||
|
this.interpolatedStringSelector = SelectorCache.get(segments.join(' | '))
|
||||||
|
}
|
||||||
|
return this.interpolatedStringSelector.matches(this.editor.getLastCursor().getScopeDescriptor().getScopesArray())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isOpeningBracket (string) {
|
||||||
|
return this.matchManager.pairedCharacters.hasOwnProperty(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
isClosingBracket (string) {
|
||||||
|
return this.matchManager.pairedCharactersInverse.hasOwnProperty(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
selectionIsWrappedByMatchingBrackets (selection) {
|
||||||
|
if (selection.isEmpty()) return false
|
||||||
|
const selectedText = selection.getText()
|
||||||
|
const firstCharacter = selectedText[0]
|
||||||
|
const lastCharacter = selectedText[selectedText.length - 1]
|
||||||
|
return this.matchManager.pairedCharacters[firstCharacter] === lastCharacter
|
||||||
|
}
|
||||||
|
|
||||||
|
unsubscribe () {
|
||||||
|
this.subscriptions.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
getScopedSetting (key) {
|
||||||
|
return atom.config.get(key, {scope: this.editor.getRootScopeDescriptor()})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const BACKSLASHES_REGEX = /(\\+)$/
|
||||||
|
const ESCAPE_SEQUENCE_REGEX = /(\\+)[^\\]$/
|
||||||
|
|
||||||
|
// odd number of backslashes
|
||||||
|
function endsWithEscapeCharacter (string) {
|
||||||
|
const backslashesMatch = string.match(BACKSLASHES_REGEX)
|
||||||
|
return backslashesMatch && backslashesMatch[1].length % 2 === 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// even number of backslashes or odd number of backslashes followed by another character
|
||||||
|
function endsWithEscapeSequence (string) {
|
||||||
|
const backslashesMatch = string.match(BACKSLASHES_REGEX)
|
||||||
|
const escapeSequenceMatch = string.match(ESCAPE_SEQUENCE_REGEX)
|
||||||
|
return (
|
||||||
|
(escapeSequenceMatch && escapeSequenceMatch[1].length % 2 === 1) ||
|
||||||
|
(backslashesMatch && backslashesMatch[1].length % 2 === 0)
|
||||||
|
)
|
||||||
|
}
|
20
packages/bracket-matcher/lib/main.js
Normal file
20
packages/bracket-matcher/lib/main.js
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
const MatchManager = require('./match-manager')
|
||||||
|
const BracketMatcherView = require('./bracket-matcher-view')
|
||||||
|
const BracketMatcher = require('./bracket-matcher')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
activate () {
|
||||||
|
const watchedEditors = new WeakSet()
|
||||||
|
|
||||||
|
atom.workspace.observeTextEditors(editor => {
|
||||||
|
if (watchedEditors.has(editor)) return
|
||||||
|
|
||||||
|
const editorElement = atom.views.getView(editor)
|
||||||
|
const matchManager = new MatchManager(editor, editorElement)
|
||||||
|
new BracketMatcherView(editor, editorElement, matchManager)
|
||||||
|
new BracketMatcher(editor, editorElement, matchManager)
|
||||||
|
watchedEditors.add(editor)
|
||||||
|
editor.onDidDestroy(() => watchedEditors.delete(editor))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
60
packages/bracket-matcher/lib/match-manager.js
Normal file
60
packages/bracket-matcher/lib/match-manager.js
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
const _ = require('underscore-plus')
|
||||||
|
const {CompositeDisposable} = require('atom')
|
||||||
|
|
||||||
|
module.exports =
|
||||||
|
class MatchManager {
|
||||||
|
appendPair (pairList, [itemLeft, itemRight]) {
|
||||||
|
const newPair = {}
|
||||||
|
newPair[itemLeft] = itemRight
|
||||||
|
pairList = _.extend(pairList, newPair)
|
||||||
|
}
|
||||||
|
|
||||||
|
processAutoPairs (autocompletePairs, pairedList, dataFun) {
|
||||||
|
if (autocompletePairs.length) {
|
||||||
|
for (let autocompletePair of autocompletePairs) {
|
||||||
|
const pairArray = autocompletePair.split('')
|
||||||
|
this.appendPair(pairedList, dataFun(pairArray))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateConfig () {
|
||||||
|
this.pairedCharacters = {}
|
||||||
|
this.pairedCharactersInverse = {}
|
||||||
|
this.pairRegexes = {}
|
||||||
|
this.pairsWithExtraNewline = {}
|
||||||
|
this.processAutoPairs(this.getScopedSetting('bracket-matcher.autocompleteCharacters'), this.pairedCharacters, x => [x[0], x[1]])
|
||||||
|
this.processAutoPairs(this.getScopedSetting('bracket-matcher.autocompleteCharacters'), this.pairedCharactersInverse, x => [x[1], x[0]])
|
||||||
|
this.processAutoPairs(this.getScopedSetting('bracket-matcher.pairsWithExtraNewline'), this.pairsWithExtraNewline, x => [x[0], x[1]])
|
||||||
|
for (let startPair in this.pairedCharacters) {
|
||||||
|
const endPair = this.pairedCharacters[startPair]
|
||||||
|
this.pairRegexes[startPair] = new RegExp(`[${_.escapeRegExp(startPair + endPair)}]`, 'g')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getScopedSetting (key) {
|
||||||
|
return atom.config.get(key, {scope: this.editor.getRootScopeDescriptor()})
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor (editor, editorElement) {
|
||||||
|
this.destroy = this.destroy.bind(this)
|
||||||
|
this.editor = editor
|
||||||
|
this.subscriptions = new CompositeDisposable()
|
||||||
|
|
||||||
|
this.updateConfig()
|
||||||
|
|
||||||
|
// Subscribe to config changes
|
||||||
|
const scope = this.editor.getRootScopeDescriptor()
|
||||||
|
this.subscriptions.add(
|
||||||
|
atom.config.observe('bracket-matcher.autocompleteCharacters', {scope}, () => this.updateConfig()),
|
||||||
|
atom.config.observe('bracket-matcher.pairsWithExtraNewline', {scope}, () => this.updateConfig()),
|
||||||
|
this.editor.onDidDestroy(this.destroy)
|
||||||
|
)
|
||||||
|
|
||||||
|
this.changeBracketsMode = false
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy () {
|
||||||
|
this.subscriptions.dispose()
|
||||||
|
}
|
||||||
|
}
|
11
packages/bracket-matcher/lib/selector-cache.js
Normal file
11
packages/bracket-matcher/lib/selector-cache.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
const {ScopeSelector} = require('second-mate')
|
||||||
|
const cache = {}
|
||||||
|
|
||||||
|
exports.get = function (selector) {
|
||||||
|
let scopeSelector = cache[selector]
|
||||||
|
if (!scopeSelector) {
|
||||||
|
scopeSelector = new ScopeSelector(selector)
|
||||||
|
cache[selector] = scopeSelector
|
||||||
|
}
|
||||||
|
return scopeSelector
|
||||||
|
}
|
18
packages/bracket-matcher/lib/self-closing-tags.json
Normal file
18
packages/bracket-matcher/lib/self-closing-tags.json
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
[
|
||||||
|
"area",
|
||||||
|
"base",
|
||||||
|
"br",
|
||||||
|
"col",
|
||||||
|
"command",
|
||||||
|
"embed",
|
||||||
|
"hr",
|
||||||
|
"img",
|
||||||
|
"input",
|
||||||
|
"keygen",
|
||||||
|
"link",
|
||||||
|
"meta",
|
||||||
|
"param",
|
||||||
|
"source",
|
||||||
|
"track",
|
||||||
|
"wbr"
|
||||||
|
]
|
258
packages/bracket-matcher/lib/tag-finder.js
Normal file
258
packages/bracket-matcher/lib/tag-finder.js
Normal file
@ -0,0 +1,258 @@
|
|||||||
|
const {Range} = require('atom')
|
||||||
|
const _ = require('underscore-plus')
|
||||||
|
const SelfClosingTags = require('./self-closing-tags')
|
||||||
|
|
||||||
|
const TAG_SELECTOR_REGEX = /(\b|\.)(meta\.tag|punctuation\.definition\.tag)/
|
||||||
|
const COMMENT_SELECTOR_REGEX = /(\b|\.)comment/
|
||||||
|
|
||||||
|
// Creates a regex to match opening tag with match[1] and closing tags with match[2]
|
||||||
|
//
|
||||||
|
// * tagNameRegexStr - a regex string describing how to match the tagname.
|
||||||
|
// Should not contain capturing match groups.
|
||||||
|
//
|
||||||
|
// Returns a {RegExp}.
|
||||||
|
const generateTagStartOrEndRegex = function (tagNameRegexStr) {
|
||||||
|
const notSelfClosingTagEnd = "(?:[^>\\/\"']|\"[^\"]*\"|'[^']*')*>"
|
||||||
|
return new RegExp(`<(${tagNameRegexStr})${notSelfClosingTagEnd}|<\\/(${tagNameRegexStr})>`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const tagStartOrEndRegex = generateTagStartOrEndRegex('\\w[-\\w]*(?:(?:\\:|\\.)\\w[-\\w]*)*')
|
||||||
|
|
||||||
|
// Helper to find the matching start/end tag for the start/end tag under the
|
||||||
|
// cursor in XML, HTML, etc. editors.
|
||||||
|
module.exports =
|
||||||
|
class TagFinder {
|
||||||
|
constructor (editor) {
|
||||||
|
// 1. Tag prefix
|
||||||
|
// 2. Closing tag (optional)
|
||||||
|
// 3. Tag name
|
||||||
|
// 4. Attributes (ids, classes, etc. - optional)
|
||||||
|
// 5. Tag suffix
|
||||||
|
// 6. Self-closing tag (optional)
|
||||||
|
this.editor = editor
|
||||||
|
this.tagPattern = /(<(\/)?)(.+?)(\s+.*?)?((\/)?>|$)/
|
||||||
|
this.wordRegex = /.*?(>|$)/
|
||||||
|
}
|
||||||
|
|
||||||
|
patternForTagName (tagName) {
|
||||||
|
tagName = _.escapeRegExp(tagName)
|
||||||
|
// 1. Start tag
|
||||||
|
// 2. Tag name
|
||||||
|
// 3. Attributes (optional)
|
||||||
|
// 4. Tag suffix
|
||||||
|
// 5. Self-closing tag (optional)
|
||||||
|
// 6. End tag
|
||||||
|
return new RegExp(`(<(${tagName})(\\s+[^>]*?)?((/)?>))|(</${tagName}[^>]*>)`, 'gi')
|
||||||
|
}
|
||||||
|
|
||||||
|
isRangeCommented (range) {
|
||||||
|
return this.scopesForPositionMatchRegex(range.start, COMMENT_SELECTOR_REGEX)
|
||||||
|
}
|
||||||
|
|
||||||
|
isCursorOnTag () {
|
||||||
|
return this.scopesForPositionMatchRegex(this.editor.getCursorBufferPosition(), TAG_SELECTOR_REGEX)
|
||||||
|
}
|
||||||
|
|
||||||
|
scopesForPositionMatchRegex (position, regex) {
|
||||||
|
const {tokenizedBuffer, buffer} = this.editor
|
||||||
|
const {grammar} = tokenizedBuffer
|
||||||
|
let column = 0
|
||||||
|
const line = tokenizedBuffer.tokenizedLineForRow(position.row)
|
||||||
|
if (line == null) { return false }
|
||||||
|
const lineLength = buffer.lineLengthForRow(position.row)
|
||||||
|
const scopeIds = line.openScopes.slice()
|
||||||
|
for (let i = 0; i < line.tags.length; i++) {
|
||||||
|
const tag = line.tags[i]
|
||||||
|
if (tag >= 0) {
|
||||||
|
const nextColumn = column + tag
|
||||||
|
if ((nextColumn > position.column) || (nextColumn === lineLength)) { break }
|
||||||
|
column = nextColumn
|
||||||
|
} else if ((tag & 1) === 1) {
|
||||||
|
scopeIds.push(tag)
|
||||||
|
} else {
|
||||||
|
scopeIds.pop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return scopeIds.some(scopeId => regex.test(grammar.scopeForId(scopeId)))
|
||||||
|
}
|
||||||
|
|
||||||
|
findStartTag (tagName, endPosition, fullRange = false) {
|
||||||
|
const scanRange = new Range([0, 0], endPosition)
|
||||||
|
const pattern = this.patternForTagName(tagName)
|
||||||
|
let startRange = null
|
||||||
|
let unpairedCount = 0
|
||||||
|
this.editor.backwardsScanInBufferRange(pattern, scanRange, ({match, range, stop}) => {
|
||||||
|
if (this.isRangeCommented(range)) return
|
||||||
|
|
||||||
|
const [entireMatch, isStartTag, tagName, attributes, suffix, isSelfClosingTag, isEndTag] = match
|
||||||
|
|
||||||
|
if (isSelfClosingTag) return
|
||||||
|
|
||||||
|
if (isStartTag) {
|
||||||
|
unpairedCount--
|
||||||
|
if (unpairedCount < 0) {
|
||||||
|
stop()
|
||||||
|
startRange = range
|
||||||
|
if (!fullRange) {
|
||||||
|
// Move the start past the initial <
|
||||||
|
startRange.start = startRange.start.translate([0, 1])
|
||||||
|
|
||||||
|
// End right after the tag name
|
||||||
|
startRange.end = startRange.start.translate([0, tagName.length])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
unpairedCount++
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return startRange
|
||||||
|
}
|
||||||
|
|
||||||
|
findEndTag (tagName, startPosition, fullRange = false) {
|
||||||
|
const scanRange = new Range(startPosition, this.editor.buffer.getEndPosition())
|
||||||
|
const pattern = this.patternForTagName(tagName)
|
||||||
|
let endRange = null
|
||||||
|
let unpairedCount = 0
|
||||||
|
this.editor.scanInBufferRange(pattern, scanRange, ({match, range, stop}) => {
|
||||||
|
if (this.isRangeCommented(range)) return
|
||||||
|
|
||||||
|
const [entireMatch, isStartTag, tagName, attributes, suffix, isSelfClosingTag, isEndTag] = match
|
||||||
|
|
||||||
|
if (isSelfClosingTag) return
|
||||||
|
|
||||||
|
if (isStartTag) {
|
||||||
|
unpairedCount++
|
||||||
|
} else {
|
||||||
|
unpairedCount--
|
||||||
|
if (unpairedCount < 0) {
|
||||||
|
stop()
|
||||||
|
endRange = range
|
||||||
|
if (!fullRange) {
|
||||||
|
// Subtract </ and > from range
|
||||||
|
endRange = range.translate([0, 2], [0, -1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return endRange
|
||||||
|
}
|
||||||
|
|
||||||
|
findStartEndTags (fullRange = false) {
|
||||||
|
let ranges = {}
|
||||||
|
const endPosition = this.editor.getLastCursor().getCurrentWordBufferRange({wordRegex: this.wordRegex}).end
|
||||||
|
this.editor.backwardsScanInBufferRange(this.tagPattern, [[0, 0], endPosition], ({match, range, stop}) => {
|
||||||
|
stop()
|
||||||
|
|
||||||
|
const [entireMatch, prefix, isClosingTag, tagName, attributes, suffix, isSelfClosingTag] = Array.from(match)
|
||||||
|
|
||||||
|
let startRange = range
|
||||||
|
if (!fullRange) {
|
||||||
|
if (range.start.row === range.end.row) {
|
||||||
|
// Move the start past the initial <
|
||||||
|
startRange.start = startRange.start.translate([0, prefix.length])
|
||||||
|
// End right after the tag name
|
||||||
|
startRange.end = startRange.start.translate([0, tagName.length])
|
||||||
|
} else {
|
||||||
|
startRange = Range.fromObject([range.start.translate([0, prefix.length]), [range.start.row, Infinity]])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let endRange
|
||||||
|
if (isSelfClosingTag) {
|
||||||
|
endRange = startRange
|
||||||
|
} else if (isClosingTag) {
|
||||||
|
endRange = this.findStartTag(tagName, startRange.start, fullRange)
|
||||||
|
} else {
|
||||||
|
endRange = this.findEndTag(tagName, startRange.end, fullRange)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startRange && endRange) ranges = {startRange, endRange}
|
||||||
|
})
|
||||||
|
|
||||||
|
return ranges
|
||||||
|
}
|
||||||
|
|
||||||
|
findMatchingTags () {
|
||||||
|
return (this.isCursorOnTag() && this.findStartEndTags()) || {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parses a fragment of html returning the stack (i.e., an array) of open tags
|
||||||
|
//
|
||||||
|
// fragment - the fragment of html to be analysed
|
||||||
|
// stack - an array to be populated (can be non-empty)
|
||||||
|
// matchExpr - a RegExp describing how to match opening/closing tags
|
||||||
|
// the opening/closing descriptions must be captured subexpressions
|
||||||
|
// so that the code can refer to match[1] to check if an opening
|
||||||
|
// tag has been found, and to match[2] to check if a closing tag
|
||||||
|
// has been found
|
||||||
|
// cond - a condition to be checked at each iteration. If the function
|
||||||
|
// returns false the processing is immediately interrupted. When
|
||||||
|
// called the current stack is provided to the function.
|
||||||
|
//
|
||||||
|
// Returns an array of strings. Each string is a tag that is still to be closed
|
||||||
|
// (the most recent non closed tag is at the end of the array).
|
||||||
|
parseFragment (fragment, stack, matchExpr, cond) {
|
||||||
|
let match = fragment.match(matchExpr)
|
||||||
|
while (match && cond(stack)) {
|
||||||
|
if (SelfClosingTags.indexOf(match[1]) === -1) {
|
||||||
|
const topElem = stack[stack.length - 1]
|
||||||
|
|
||||||
|
if (match[2] && (topElem === match[2])) {
|
||||||
|
stack.pop()
|
||||||
|
} else if (match[1]) {
|
||||||
|
stack.push(match[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fragment = fragment.substr(match.index + match[0].length)
|
||||||
|
match = fragment.match(matchExpr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return stack
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parses the given fragment of html code returning the last unclosed tag.
|
||||||
|
//
|
||||||
|
// fragment - a string containing a fragment of html code.
|
||||||
|
//
|
||||||
|
// Returns an array of strings. Each string is a tag that is still to be closed
|
||||||
|
// (the most recent non closed tag is at the end of the array).
|
||||||
|
tagsNotClosedInFragment (fragment) {
|
||||||
|
return this.parseFragment(fragment, [], tagStartOrEndRegex, () => true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parses the given fragment of html code and returns true if the given tag
|
||||||
|
// has a matching closing tag in it. If tag is reopened and reclosed in the
|
||||||
|
// given fragment then the end point of that pair does not count as a matching
|
||||||
|
// closing tag.
|
||||||
|
tagDoesNotCloseInFragment (tags, fragment) {
|
||||||
|
if (tags.length === 0) { return false }
|
||||||
|
|
||||||
|
let stack = tags
|
||||||
|
const stackLength = stack.length
|
||||||
|
const tag = tags[tags.length - 1]
|
||||||
|
const escapedTag = _.escapeRegExp(tag)
|
||||||
|
stack = this.parseFragment(fragment, stack, generateTagStartOrEndRegex(escapedTag), s =>
|
||||||
|
s.length >= stackLength || s[s.length - 1] === tag
|
||||||
|
)
|
||||||
|
|
||||||
|
return (stack.length > 0) && (stack[stack.length - 1] === tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parses preFragment and postFragment returning the last open tag in
|
||||||
|
// preFragment that is not closed in postFragment.
|
||||||
|
//
|
||||||
|
// Returns a tag name or null if it can't find it.
|
||||||
|
closingTagForFragments (preFragment, postFragment) {
|
||||||
|
const tags = this.tagsNotClosedInFragment(preFragment)
|
||||||
|
const tag = tags[tags.length - 1]
|
||||||
|
if (this.tagDoesNotCloseInFragment(tags, postFragment)) {
|
||||||
|
return tag
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
22
packages/bracket-matcher/menus/bracket-matcher.cson
Normal file
22
packages/bracket-matcher/menus/bracket-matcher.cson
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
'menu': [
|
||||||
|
{
|
||||||
|
'label': 'Packages'
|
||||||
|
'submenu': [
|
||||||
|
'label': 'Bracket Matcher'
|
||||||
|
'submenu': [
|
||||||
|
{ 'label': 'Go To Matching Bracket', 'command': 'bracket-matcher:go-to-matching-bracket' }
|
||||||
|
{ 'label': 'Select Inside Brackets', 'command': 'bracket-matcher:select-inside-brackets' }
|
||||||
|
{ 'label': 'Remove Brackets From Selection', 'command': 'bracket-matcher:remove-brackets-from-selection' }
|
||||||
|
{ 'label': 'Close Current Tag', 'command': 'bracket-matcher:close-tag' }
|
||||||
|
{ 'label': 'Remove Matching Brackets', 'command': 'bracket-matcher:remove-matching-brackets' }
|
||||||
|
{ 'label': 'Select Matching Brackets', 'command': 'bracket-matcher:select-matching-brackets' }
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'label': 'Selection'
|
||||||
|
'submenu': [
|
||||||
|
{ 'label': 'Select Inside Brackets', 'command': 'bracket-matcher:select-inside-brackets' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
67
packages/bracket-matcher/package.json
Normal file
67
packages/bracket-matcher/package.json
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
{
|
||||||
|
"name": "bracket-matcher",
|
||||||
|
"version": "0.92.0",
|
||||||
|
"main": "./lib/main",
|
||||||
|
"description": "Highlight the matching bracket for the `(){}[]` character under the cursor. Move the cursor to the matching bracket with `ctrl-m`.",
|
||||||
|
"repository": "https://github.com/pulsar-edit/bracket-matcher",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"atom": "*"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"underscore-plus": "1.x"
|
||||||
|
},
|
||||||
|
"configSchema": {
|
||||||
|
"autocompleteCharacters": {
|
||||||
|
"description": "Autocompleted characters treated as matching pairs, such as `()`, and `{}`.",
|
||||||
|
"type": "array",
|
||||||
|
"default": [
|
||||||
|
"()",
|
||||||
|
"[]",
|
||||||
|
"{}",
|
||||||
|
"\"\"",
|
||||||
|
"''",
|
||||||
|
"``",
|
||||||
|
"“”",
|
||||||
|
"‘’",
|
||||||
|
"«»",
|
||||||
|
"‹›"
|
||||||
|
],
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pairsWithExtraNewline": {
|
||||||
|
"description": "Automatically add a newline between the pair when enter is pressed.",
|
||||||
|
"type": "array",
|
||||||
|
"default": [
|
||||||
|
"()",
|
||||||
|
"[]",
|
||||||
|
"{}"
|
||||||
|
],
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autocompleteBrackets": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"description": "Autocomplete bracket and quote characters, such as `(` and `)`, and `\"`."
|
||||||
|
},
|
||||||
|
"wrapSelectionsInBrackets": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"description": "Wrap selected text in brackets or quotes when the editor contains selections and the opening bracket or quote is typed."
|
||||||
|
},
|
||||||
|
"highlightMatchingLineNumber": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Highlight the line number of the matching bracket."
|
||||||
|
},
|
||||||
|
"alwaysSkipClosingPairs": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Always skip closing pairs in front of the cursor."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1913
packages/bracket-matcher/spec/bracket-matcher-spec.js
Normal file
1913
packages/bracket-matcher/spec/bracket-matcher-spec.js
Normal file
File diff suppressed because it is too large
Load Diff
142
packages/bracket-matcher/spec/close-tag-spec.js
Normal file
142
packages/bracket-matcher/spec/close-tag-spec.js
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
const TagFinder = require('../lib/tag-finder')
|
||||||
|
const tagFinder = new TagFinder()
|
||||||
|
|
||||||
|
describe('closeTag', () => {
|
||||||
|
describe('TagFinder::parseFragment', () => {
|
||||||
|
let fragment = ''
|
||||||
|
|
||||||
|
beforeEach(() => fragment = '<html><head><body></body>')
|
||||||
|
|
||||||
|
it('returns the last not closed elem in fragment, matching a given pattern', () => {
|
||||||
|
const stack = tagFinder.parseFragment(fragment, [], /<(\w+)|<\/(\w*)/, () => true)
|
||||||
|
expect(stack[stack.length - 1]).toBe('head')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('stops when cond become true', () => {
|
||||||
|
const stack = tagFinder.parseFragment(fragment, [], /<(\w+)|<\/(\w*)/, () => false)
|
||||||
|
expect(stack.length).toBe(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('uses the given match expression to match tags', () => {
|
||||||
|
const stack = tagFinder.parseFragment(fragment, [], /<(body)|(notag)/, () => true)
|
||||||
|
expect(stack[stack.length - 1]).toBe('body')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('TagFinder::tagsNotClosedInFragment', () => {
|
||||||
|
it('returns the outermost tag not closed in an HTML fragment', () => {
|
||||||
|
const fragment = '<html><head></head><body><h1><p></p>'
|
||||||
|
const tags = tagFinder.tagsNotClosedInFragment(fragment)
|
||||||
|
expect(tags).toEqual(['html', 'body', 'h1'])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('is not confused by tag attributes', () => {
|
||||||
|
const fragment = '<html><head></head><body class="c"><h1 class="p"><p></p>'
|
||||||
|
const tags = tagFinder.tagsNotClosedInFragment(fragment)
|
||||||
|
expect(tags).toEqual(['html', 'body', 'h1'])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('is not confused by namespace prefixes', () => {
|
||||||
|
const fragment = '<xhtml:html><xhtml:body><xhtml:h1>'
|
||||||
|
const tags = tagFinder.tagsNotClosedInFragment(fragment)
|
||||||
|
expect(tags).toEqual(['xhtml:html', 'xhtml:body', 'xhtml:h1'])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('TagFinder::tagDoesNotCloseInFragment', () => {
|
||||||
|
it('returns true if the given tag is not closed in the given fragment', () => {
|
||||||
|
const fragment = '</other1></other2></html>'
|
||||||
|
expect(tagFinder.tagDoesNotCloseInFragment('body', fragment)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns false if the given tag is closed in the given fragment', () => {
|
||||||
|
const fragment = '</other1></body></html>'
|
||||||
|
expect(tagFinder.tagDoesNotCloseInFragment(['body'], fragment)).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns true even if the given tag is re-opened and re-closed', () => {
|
||||||
|
const fragment = '<other> </other><body></body><html>'
|
||||||
|
expect(tagFinder.tagDoesNotCloseInFragment(['body'], fragment)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns false even if the given tag is re-opened and re-closed before closing', () => {
|
||||||
|
const fragment = '<other> </other><body></body></body><html>'
|
||||||
|
expect(tagFinder.tagDoesNotCloseInFragment(['body'], fragment)).toBe(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('TagFinder::closingTagForFragments', () => {
|
||||||
|
it('returns the last opened in preFragment tag that is not closed in postFragment', () => {
|
||||||
|
const preFragment = '<html><head></head><body><h1></h1><p>'
|
||||||
|
const postFragment = '</body></html>'
|
||||||
|
expect(tagFinder.closingTagForFragments(preFragment, postFragment)).toBe('p')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('correctly handles empty postFragment', () => {
|
||||||
|
const preFragment = '<html><head></head><body><h1></h1><p>'
|
||||||
|
const postFragment = ''
|
||||||
|
expect(tagFinder.closingTagForFragments(preFragment, postFragment)).toBe('p')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('correctly handles malformed tags', () => {
|
||||||
|
const preFragment = '<html><head></head></htm'
|
||||||
|
const postFragment = ''
|
||||||
|
expect(tagFinder.closingTagForFragments(preFragment, postFragment)).toBe('html')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns null if there is no open tag to be closed', () => {
|
||||||
|
const preFragment = '<html><head></head><body><h1></h1><p>'
|
||||||
|
const postFragment = '</p></body></html>'
|
||||||
|
expect(tagFinder.closingTagForFragments(preFragment, postFragment)).toBe(null)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('correctly closes tags containing hyphens', () => {
|
||||||
|
const preFragment = '<html><head></head><body><h1></h1><my-element>'
|
||||||
|
const postFragment = '</body></html>'
|
||||||
|
expect(tagFinder.closingTagForFragments(preFragment, postFragment)).toBe('my-element')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('correctly closes tags containing attributes', () => {
|
||||||
|
const preFragment = '<html><head></head><body class="foo bar"><div>'
|
||||||
|
const postFragment = '</body></html>'
|
||||||
|
expect(tagFinder.closingTagForFragments(preFragment, postFragment)).toBe('div')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('correctly closes tags containing an XML namespace', () => {
|
||||||
|
const preFragment = '<html><head></head><body><custom:tag>'
|
||||||
|
const postFragment = '</body></html>'
|
||||||
|
expect(tagFinder.closingTagForFragments(preFragment, postFragment)).toBe('custom:tag')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('correctly closes tags containing multiple XML namespaces', () => {
|
||||||
|
// This is not exactly valid syntax but it can't hurt to support it
|
||||||
|
const preFragment = '<html><head></head><body><custom:custom2:tag>'
|
||||||
|
const postFragment = '</body></html>'
|
||||||
|
expect(tagFinder.closingTagForFragments(preFragment, postFragment)).toBe('custom:custom2:tag')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('correctly closes tags in the present of JSX tags containing member accesses', () => {
|
||||||
|
const preFragment = '<Foo><Bar.Baz></Bar.Baz>'
|
||||||
|
const postFragment = ''
|
||||||
|
expect(tagFinder.closingTagForFragments(preFragment, postFragment)).toBe('Foo')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('correctly closes JSX tags containing member accesses', () => {
|
||||||
|
const preFragment = '<Foo.Bar><div></div>'
|
||||||
|
const postFragment = ''
|
||||||
|
expect(tagFinder.closingTagForFragments(preFragment, postFragment)).toBe('Foo.Bar')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('correctly closes JSX tags containing deep member accesses', () => {
|
||||||
|
const preFragment = '<Foo.Bar.Baz><div></div>'
|
||||||
|
const postFragment = ''
|
||||||
|
expect(tagFinder.closingTagForFragments(preFragment, postFragment)).toBe('Foo.Bar.Baz')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('correctly closes tags when there are other tags with the same prefix', () => {
|
||||||
|
const preFragment = '<thead><th>'
|
||||||
|
const postFragment = '</thead>'
|
||||||
|
expect(tagFinder.closingTagForFragments(preFragment, postFragment)).toBe('th')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
6
packages/bracket-matcher/spec/fixtures/multiplecursor.md
vendored
Normal file
6
packages/bracket-matcher/spec/fixtures/multiplecursor.md
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
[link-1](http://example.com/)
|
||||||
|
[another-link-2](http://example.com/)
|
||||||
|
[yet-another-link-3](http://example.com/)
|
||||||
|
[final-link-4](http://example.com/)
|
||||||
|
(different-type)
|
||||||
|
not anywhere
|
15
packages/bracket-matcher/spec/fixtures/sample.html
vendored
Normal file
15
packages/bracket-matcher/spec/fixtures/sample.html
vendored
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<p><a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
</div>
|
||||||
|
</div>
|
13
packages/bracket-matcher/spec/fixtures/sample.js
vendored
Normal file
13
packages/bracket-matcher/spec/fixtures/sample.js
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
var quicksort = function () {
|
||||||
|
var sort = function(items) {
|
||||||
|
if (items.length <= 1) return items;
|
||||||
|
var pivot = items.shift(), current, left = [], right = [];
|
||||||
|
while(items.length > 0) {
|
||||||
|
current = items.shift();
|
||||||
|
current < pivot ? left.push(current) : right.push(current);
|
||||||
|
}
|
||||||
|
return sort(left).concat(pivot).concat(sort(right));
|
||||||
|
};
|
||||||
|
|
||||||
|
return sort(Array.apply(this, arguments));
|
||||||
|
};
|
17
packages/bracket-matcher/spec/fixtures/sample.xml
vendored
Normal file
17
packages/bracket-matcher/spec/fixtures/sample.xml
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<html>
|
||||||
|
<body id="bod">
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
hello
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
world
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,11 @@
|
|||||||
|
@import "syntax-variables";
|
||||||
|
|
||||||
|
.bracket-matcher .region {
|
||||||
|
border-bottom: 1px dotted lime;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line-number.bracket-matcher.bracket-matcher {
|
||||||
|
color: @syntax-gutter-text-color-selected;
|
||||||
|
background-color: @syntax-gutter-background-color-selected;
|
||||||
|
}
|
@ -295,7 +295,7 @@ describe "Clojure grammar", ->
|
|||||||
#!/usr/bin/env boot
|
#!/usr/bin/env boot
|
||||||
"""
|
"""
|
||||||
for line in valid.split /\n/
|
for line in valid.split /\n/
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).not.toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync(line)).not.toBeNull()
|
||||||
|
|
||||||
invalid = """
|
invalid = """
|
||||||
\x20#!/usr/sbin/boot
|
\x20#!/usr/sbin/boot
|
||||||
@ -306,7 +306,7 @@ describe "Clojure grammar", ->
|
|||||||
#!\t/usr/bin/env --boot=bar
|
#!\t/usr/bin/env --boot=bar
|
||||||
"""
|
"""
|
||||||
for line in invalid.split /\n/
|
for line in invalid.split /\n/
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync(line)).toBeNull()
|
||||||
|
|
||||||
it "recognises Emacs modelines", ->
|
it "recognises Emacs modelines", ->
|
||||||
valid = """
|
valid = """
|
||||||
@ -325,7 +325,7 @@ describe "Clojure grammar", ->
|
|||||||
"-*- font:x;foo : bar ; mode : ClojureScript ; bar : foo ; foooooo:baaaaar;fo:ba-*-";
|
"-*- font:x;foo : bar ; mode : ClojureScript ; bar : foo ; foooooo:baaaaar;fo:ba-*-";
|
||||||
"""
|
"""
|
||||||
for line in valid.split /\n/
|
for line in valid.split /\n/
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).not.toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync(line)).not.toBeNull()
|
||||||
|
|
||||||
invalid = """
|
invalid = """
|
||||||
/* --*clojure-*- */
|
/* --*clojure-*- */
|
||||||
@ -343,7 +343,7 @@ describe "Clojure grammar", ->
|
|||||||
// -*-font:mode;mode:clojure--*-
|
// -*-font:mode;mode:clojure--*-
|
||||||
"""
|
"""
|
||||||
for line in invalid.split /\n/
|
for line in invalid.split /\n/
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync(line)).toBeNull()
|
||||||
|
|
||||||
it "recognises Vim modelines", ->
|
it "recognises Vim modelines", ->
|
||||||
valid = """
|
valid = """
|
||||||
@ -370,7 +370,7 @@ describe "Clojure grammar", ->
|
|||||||
# vim:noexpandtab titlestring=hi\|there\\\\ ft=clojure ts=4
|
# vim:noexpandtab titlestring=hi\|there\\\\ ft=clojure ts=4
|
||||||
"""
|
"""
|
||||||
for line in valid.split /\n/
|
for line in valid.split /\n/
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).not.toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync(line)).not.toBeNull()
|
||||||
|
|
||||||
invalid = """
|
invalid = """
|
||||||
ex: se filetype=clojure:
|
ex: se filetype=clojure:
|
||||||
@ -388,4 +388,4 @@ describe "Clojure grammar", ->
|
|||||||
# vim:noexpandtab titlestring=hi\\|there\\\\\\ ft=clojure ts=4
|
# vim:noexpandtab titlestring=hi\\|there\\\\\\ ft=clojure ts=4
|
||||||
"""
|
"""
|
||||||
for line in invalid.split /\n/
|
for line in invalid.split /\n/
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync(line)).toBeNull()
|
||||||
|
@ -29,7 +29,7 @@ describe "CoffeeScript (Literate) grammar", ->
|
|||||||
#!/usr/local/bin/env coffee --literate -w
|
#!/usr/local/bin/env coffee --literate -w
|
||||||
"""
|
"""
|
||||||
for line in valid.split /\n/
|
for line in valid.split /\n/
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).not.toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync(line)).not.toBeNull()
|
||||||
|
|
||||||
invalid = """
|
invalid = """
|
||||||
#!/usr/local/bin/coffee --no-head -literate -w
|
#!/usr/local/bin/coffee --no-head -literate -w
|
||||||
@ -37,7 +37,7 @@ describe "CoffeeScript (Literate) grammar", ->
|
|||||||
#!/usr/local/bin/env coffee --illiterate -w=l
|
#!/usr/local/bin/env coffee --illiterate -w=l
|
||||||
"""
|
"""
|
||||||
for line in invalid.split /\n/
|
for line in invalid.split /\n/
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync(line)).toBeNull()
|
||||||
|
|
||||||
it "recognises Emacs modelines", ->
|
it "recognises Emacs modelines", ->
|
||||||
valid = """
|
valid = """
|
||||||
@ -56,7 +56,7 @@ describe "CoffeeScript (Literate) grammar", ->
|
|||||||
"-*- font:x;foo : bar ; mode : LiTcOFFEe ; bar : foo ; foooooo:baaaaar;fo:ba-*-";
|
"-*- font:x;foo : bar ; mode : LiTcOFFEe ; bar : foo ; foooooo:baaaaar;fo:ba-*-";
|
||||||
"""
|
"""
|
||||||
for line in valid.split /\n/
|
for line in valid.split /\n/
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).not.toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync(line)).not.toBeNull()
|
||||||
|
|
||||||
invalid = """
|
invalid = """
|
||||||
/* --*litcoffee-*- */
|
/* --*litcoffee-*- */
|
||||||
@ -74,7 +74,7 @@ describe "CoffeeScript (Literate) grammar", ->
|
|||||||
// -*-font:mode;mode:litcoffee--*-
|
// -*-font:mode;mode:litcoffee--*-
|
||||||
"""
|
"""
|
||||||
for line in invalid.split /\n/
|
for line in invalid.split /\n/
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync(line)).toBeNull()
|
||||||
|
|
||||||
it "recognises Vim modelines", ->
|
it "recognises Vim modelines", ->
|
||||||
valid = """
|
valid = """
|
||||||
@ -101,7 +101,7 @@ describe "CoffeeScript (Literate) grammar", ->
|
|||||||
# vim:noexpandtab titlestring=hi\|there\\\\ ft=litcoffee ts=4
|
# vim:noexpandtab titlestring=hi\|there\\\\ ft=litcoffee ts=4
|
||||||
"""
|
"""
|
||||||
for line in valid.split /\n/
|
for line in valid.split /\n/
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).not.toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync(line)).not.toBeNull()
|
||||||
|
|
||||||
invalid = """
|
invalid = """
|
||||||
ex: se filetype=litcoffee:
|
ex: se filetype=litcoffee:
|
||||||
@ -119,4 +119,4 @@ describe "CoffeeScript (Literate) grammar", ->
|
|||||||
# vim:noexpandtab titlestring=hi\\|there\\\\\\ ft=litcoffee ts=4
|
# vim:noexpandtab titlestring=hi\\|there\\\\\\ ft=litcoffee ts=4
|
||||||
"""
|
"""
|
||||||
for line in invalid.split /\n/
|
for line in invalid.split /\n/
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync(line)).toBeNull()
|
||||||
|
@ -1393,7 +1393,7 @@ describe "CoffeeScript grammar", ->
|
|||||||
#!/usr/bin/env coffee
|
#!/usr/bin/env coffee
|
||||||
"""
|
"""
|
||||||
for line in valid.split /\n/
|
for line in valid.split /\n/
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).not.toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync(line)).not.toBeNull()
|
||||||
|
|
||||||
invalid = """
|
invalid = """
|
||||||
\x20#!/usr/sbin/coffee
|
\x20#!/usr/sbin/coffee
|
||||||
@ -1404,7 +1404,7 @@ describe "CoffeeScript grammar", ->
|
|||||||
#!\t/usr/bin/env --coffee=bar
|
#!\t/usr/bin/env --coffee=bar
|
||||||
"""
|
"""
|
||||||
for line in invalid.split /\n/
|
for line in invalid.split /\n/
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync(line)).toBeNull()
|
||||||
|
|
||||||
it "recognises Emacs modelines", ->
|
it "recognises Emacs modelines", ->
|
||||||
valid = """
|
valid = """
|
||||||
@ -1423,7 +1423,7 @@ describe "CoffeeScript grammar", ->
|
|||||||
"-*- font:x;foo : bar ; mode : Coffee ; bar : foo ; foooooo:baaaaar;fo:ba-*-";
|
"-*- font:x;foo : bar ; mode : Coffee ; bar : foo ; foooooo:baaaaar;fo:ba-*-";
|
||||||
"""
|
"""
|
||||||
for line in valid.split /\n/
|
for line in valid.split /\n/
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).not.toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync(line)).not.toBeNull()
|
||||||
|
|
||||||
invalid = """
|
invalid = """
|
||||||
/* --*coffee-*- */
|
/* --*coffee-*- */
|
||||||
@ -1441,7 +1441,7 @@ describe "CoffeeScript grammar", ->
|
|||||||
// -*-font:mode;mode:coffee--*-
|
// -*-font:mode;mode:coffee--*-
|
||||||
"""
|
"""
|
||||||
for line in invalid.split /\n/
|
for line in invalid.split /\n/
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync(line)).toBeNull()
|
||||||
|
|
||||||
it "recognises Vim modelines", ->
|
it "recognises Vim modelines", ->
|
||||||
valid = """
|
valid = """
|
||||||
@ -1468,7 +1468,7 @@ describe "CoffeeScript grammar", ->
|
|||||||
# vim:noexpandtab titlestring=hi\|there\\\\ ft=cOFFEe ts=4
|
# vim:noexpandtab titlestring=hi\|there\\\\ ft=cOFFEe ts=4
|
||||||
"""
|
"""
|
||||||
for line in valid.split /\n/
|
for line in valid.split /\n/
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).not.toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync(line)).not.toBeNull()
|
||||||
|
|
||||||
invalid = """
|
invalid = """
|
||||||
ex: se filetype=coffee:
|
ex: se filetype=coffee:
|
||||||
@ -1486,4 +1486,4 @@ describe "CoffeeScript grammar", ->
|
|||||||
# vim:noexpandtab titlestring=hi\\|there\\\\\\ ft=coffee ts=4
|
# vim:noexpandtab titlestring=hi\\|there\\\\\\ ft=coffee ts=4
|
||||||
"""
|
"""
|
||||||
for line in invalid.split /\n/
|
for line in invalid.split /\n/
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync(line)).toBeNull()
|
||||||
|
@ -3508,7 +3508,7 @@ describe 'CSS grammar', ->
|
|||||||
"-*- font:x;foo : bar ; mode : cSS ; bar : foo ; foooooo:baaaaar;fo:ba-*-";
|
"-*- font:x;foo : bar ; mode : cSS ; bar : foo ; foooooo:baaaaar;fo:ba-*-";
|
||||||
"""
|
"""
|
||||||
for line in valid.split /\n/
|
for line in valid.split /\n/
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).not.toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync(line)).not.toBeNull()
|
||||||
|
|
||||||
invalid = """
|
invalid = """
|
||||||
/* --*css-*- */
|
/* --*css-*- */
|
||||||
@ -3526,7 +3526,7 @@ describe 'CSS grammar', ->
|
|||||||
// -*-font:mode;mode:css--*-
|
// -*-font:mode;mode:css--*-
|
||||||
"""
|
"""
|
||||||
for line in invalid.split /\n/
|
for line in invalid.split /\n/
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync(line)).toBeNull()
|
||||||
|
|
||||||
it "recognises Vim modelines", ->
|
it "recognises Vim modelines", ->
|
||||||
valid = """
|
valid = """
|
||||||
@ -3553,7 +3553,7 @@ describe 'CSS grammar', ->
|
|||||||
# vim:noexpandtab titlestring=hi\|there\\\\ ft=css ts=4
|
# vim:noexpandtab titlestring=hi\|there\\\\ ft=css ts=4
|
||||||
"""
|
"""
|
||||||
for line in valid.split /\n/
|
for line in valid.split /\n/
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).not.toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync(line)).not.toBeNull()
|
||||||
|
|
||||||
invalid = """
|
invalid = """
|
||||||
ex: se filetype=css:
|
ex: se filetype=css:
|
||||||
@ -3571,7 +3571,7 @@ describe 'CSS grammar', ->
|
|||||||
# vim:noexpandtab titlestring=hi\\|there\\\\\\ ft=CSS ts=4
|
# vim:noexpandtab titlestring=hi\\|there\\\\\\ ft=CSS ts=4
|
||||||
"""
|
"""
|
||||||
for line in invalid.split /\n/
|
for line in invalid.split /\n/
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync(line)).toBeNull()
|
||||||
|
|
||||||
describe "Missing supported properties regressions", ->
|
describe "Missing supported properties regressions", ->
|
||||||
it "recognises place-items property as supported", ->
|
it "recognises place-items property as supported", ->
|
||||||
|
@ -18,45 +18,45 @@ describe 'Go settings', ->
|
|||||||
it 'matches lines correctly using the increaseIndentPattern', ->
|
it 'matches lines correctly using the increaseIndentPattern', ->
|
||||||
increaseIndentRegex = languageMode.increaseIndentRegexForScopeDescriptor(['source.go'])
|
increaseIndentRegex = languageMode.increaseIndentRegexForScopeDescriptor(['source.go'])
|
||||||
|
|
||||||
expect(increaseIndentRegex.testSync(' case true:')).toBeTruthy()
|
expect(increaseIndentRegex.findNextMatchSync(' case true:')).toBeTruthy()
|
||||||
expect(increaseIndentRegex.testSync(' default:')).toBeTruthy()
|
expect(increaseIndentRegex.findNextMatchSync(' default:')).toBeTruthy()
|
||||||
expect(increaseIndentRegex.testSync('func something() {')).toBeTruthy()
|
expect(increaseIndentRegex.findNextMatchSync('func something() {')).toBeTruthy()
|
||||||
expect(increaseIndentRegex.testSync(' if true {')).toBeTruthy()
|
expect(increaseIndentRegex.findNextMatchSync(' if true {')).toBeTruthy()
|
||||||
expect(increaseIndentRegex.testSync(' else {')).toBeTruthy()
|
expect(increaseIndentRegex.findNextMatchSync(' else {')).toBeTruthy()
|
||||||
expect(increaseIndentRegex.testSync(' switch {')).toBeTruthy()
|
expect(increaseIndentRegex.findNextMatchSync(' switch {')).toBeTruthy()
|
||||||
expect(increaseIndentRegex.testSync(' switch true {')).toBeTruthy()
|
expect(increaseIndentRegex.findNextMatchSync(' switch true {')).toBeTruthy()
|
||||||
expect(increaseIndentRegex.testSync(' select {')).toBeTruthy()
|
expect(increaseIndentRegex.findNextMatchSync(' select {')).toBeTruthy()
|
||||||
expect(increaseIndentRegex.testSync(' select true {')).toBeTruthy()
|
expect(increaseIndentRegex.findNextMatchSync(' select true {')).toBeTruthy()
|
||||||
expect(increaseIndentRegex.testSync(' for v := range val {')).toBeTruthy()
|
expect(increaseIndentRegex.findNextMatchSync(' for v := range val {')).toBeTruthy()
|
||||||
expect(increaseIndentRegex.testSync(' for i := 0; i < 10; i++ {')).toBeTruthy()
|
expect(increaseIndentRegex.findNextMatchSync(' for i := 0; i < 10; i++ {')).toBeTruthy()
|
||||||
expect(increaseIndentRegex.testSync(' for i := 0; i < 10; i++ {')).toBeTruthy()
|
expect(increaseIndentRegex.findNextMatchSync(' for i := 0; i < 10; i++ {')).toBeTruthy()
|
||||||
expect(increaseIndentRegex.testSync(' type something struct {')).toBeTruthy()
|
expect(increaseIndentRegex.findNextMatchSync(' type something struct {')).toBeTruthy()
|
||||||
expect(increaseIndentRegex.testSync(' fmt.Printf("some%s",')).toBeTruthy()
|
expect(increaseIndentRegex.findNextMatchSync(' fmt.Printf("some%s",')).toBeTruthy()
|
||||||
expect(increaseIndentRegex.testSync(' aSlice := []string{}{')).toBeTruthy()
|
expect(increaseIndentRegex.findNextMatchSync(' aSlice := []string{}{')).toBeTruthy()
|
||||||
|
|
||||||
it 'matches lines correctly using the decreaseIndentPattern', ->
|
it 'matches lines correctly using the decreaseIndentPattern', ->
|
||||||
decreaseIndentRegex = languageMode.decreaseIndentRegexForScopeDescriptor(['source.go'])
|
decreaseIndentRegex = languageMode.decreaseIndentRegexForScopeDescriptor(['source.go'])
|
||||||
|
|
||||||
expect(decreaseIndentRegex.testSync(' case true:')).toBeTruthy()
|
expect(decreaseIndentRegex.findNextMatchSync(' case true:')).toBeTruthy()
|
||||||
expect(decreaseIndentRegex.testSync(' default:')).toBeTruthy()
|
expect(decreaseIndentRegex.findNextMatchSync(' default:')).toBeTruthy()
|
||||||
expect(decreaseIndentRegex.testSync(' }')).toBeTruthy()
|
expect(decreaseIndentRegex.findNextMatchSync(' }')).toBeTruthy()
|
||||||
expect(decreaseIndentRegex.testSync(' },')).toBeTruthy()
|
expect(decreaseIndentRegex.findNextMatchSync(' },')).toBeTruthy()
|
||||||
expect(decreaseIndentRegex.testSync(' )')).toBeTruthy()
|
expect(decreaseIndentRegex.findNextMatchSync(' )')).toBeTruthy()
|
||||||
expect(decreaseIndentRegex.testSync(' ),')).toBeTruthy()
|
expect(decreaseIndentRegex.findNextMatchSync(' ),')).toBeTruthy()
|
||||||
|
|
||||||
it 'matches lines correctly using the decreaseNextIndentPattern', ->
|
it 'matches lines correctly using the decreaseNextIndentPattern', ->
|
||||||
decreaseNextIndentRegex = languageMode.decreaseNextIndentRegexForScopeDescriptor(['source.go'])
|
decreaseNextIndentRegex = languageMode.decreaseNextIndentRegexForScopeDescriptor(['source.go'])
|
||||||
|
|
||||||
expect(decreaseNextIndentRegex.testSync(' fmt.Println("something"))')).toBeTruthy()
|
expect(decreaseNextIndentRegex.findNextMatchSync(' fmt.Println("something"))')).toBeTruthy()
|
||||||
expect(decreaseNextIndentRegex.testSync(' fmt.Println("something")),')).toBeTruthy()
|
expect(decreaseNextIndentRegex.findNextMatchSync(' fmt.Println("something")),')).toBeTruthy()
|
||||||
expect(decreaseNextIndentRegex.testSync(' fmt.Println("something"), "x"),')).toBeTruthy()
|
expect(decreaseNextIndentRegex.findNextMatchSync(' fmt.Println("something"), "x"),')).toBeTruthy()
|
||||||
expect(decreaseNextIndentRegex.testSync(' fmt.Println(fmt.Sprint("something"))),')).toBeTruthy()
|
expect(decreaseNextIndentRegex.findNextMatchSync(' fmt.Println(fmt.Sprint("something"))),')).toBeTruthy()
|
||||||
expect(decreaseNextIndentRegex.testSync(' fmt.Println(fmt.Sprint("something"), "x")),')).toBeTruthy()
|
expect(decreaseNextIndentRegex.findNextMatchSync(' fmt.Println(fmt.Sprint("something"), "x")),')).toBeTruthy()
|
||||||
|
|
||||||
expect(decreaseNextIndentRegex.testSync(' fmt.Println("something")')).toBeFalsy()
|
expect(decreaseNextIndentRegex.findNextMatchSync(' fmt.Println("something")')).toBeFalsy()
|
||||||
expect(decreaseNextIndentRegex.testSync(' fmt.Println("something"),')).toBeFalsy()
|
expect(decreaseNextIndentRegex.findNextMatchSync(' fmt.Println("something"),')).toBeFalsy()
|
||||||
|
|
||||||
# a line with many (), testing for catastrophic backtracking.
|
# a line with many (), testing for catastrophic backtracking.
|
||||||
# see https://github.com/atom/language-go/issues/78
|
# see https://github.com/atom/language-go/issues/78
|
||||||
longLine = 'first.second().third().fourth().fifth().sixth().seventh().eighth().ninth().tenth()'
|
longLine = 'first.second().third().fourth().fifth().sixth().seventh().eighth().ninth().tenth()'
|
||||||
expect(decreaseNextIndentRegex.testSync(longLine)).toBeFalsy()
|
expect(decreaseNextIndentRegex.findNextMatchSync(longLine)).toBeFalsy()
|
||||||
|
@ -601,8 +601,8 @@ describe 'TextMate HTML grammar', ->
|
|||||||
|
|
||||||
describe 'firstLineMatch', ->
|
describe 'firstLineMatch', ->
|
||||||
it 'recognises HTML5 doctypes', ->
|
it 'recognises HTML5 doctypes', ->
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync('<!DOCTYPE html>')).not.toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync('<!DOCTYPE html>')).not.toBeNull()
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync('<!doctype HTML>')).not.toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync('<!doctype HTML>')).not.toBeNull()
|
||||||
|
|
||||||
it 'recognises Emacs modelines', ->
|
it 'recognises Emacs modelines', ->
|
||||||
valid = '''
|
valid = '''
|
||||||
@ -621,7 +621,7 @@ describe 'TextMate HTML grammar', ->
|
|||||||
"-*- font:x;foo : bar ; mode : HtML ; bar : foo ; foooooo:baaaaar;fo:ba-*-";
|
"-*- font:x;foo : bar ; mode : HtML ; bar : foo ; foooooo:baaaaar;fo:ba-*-";
|
||||||
'''
|
'''
|
||||||
for line in valid.split /\n/
|
for line in valid.split /\n/
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).not.toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync(line)).not.toBeNull()
|
||||||
|
|
||||||
invalid = '''
|
invalid = '''
|
||||||
/* --*html-*- */
|
/* --*html-*- */
|
||||||
@ -640,7 +640,7 @@ describe 'TextMate HTML grammar', ->
|
|||||||
// -*-font:mode;mode:html--*-
|
// -*-font:mode;mode:html--*-
|
||||||
'''
|
'''
|
||||||
for line in invalid.split /\n/
|
for line in invalid.split /\n/
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync(line)).toBeNull()
|
||||||
|
|
||||||
it 'recognises Vim modelines', ->
|
it 'recognises Vim modelines', ->
|
||||||
valid = '''
|
valid = '''
|
||||||
@ -667,7 +667,7 @@ describe 'TextMate HTML grammar', ->
|
|||||||
# vim:noexpandtab titlestring=hi\|there\\\\ ft=html ts=4
|
# vim:noexpandtab titlestring=hi\|there\\\\ ft=html ts=4
|
||||||
'''
|
'''
|
||||||
for line in valid.split /\n/
|
for line in valid.split /\n/
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).not.toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync(line)).not.toBeNull()
|
||||||
|
|
||||||
invalid = '''
|
invalid = '''
|
||||||
ex: se filetype=html:
|
ex: se filetype=html:
|
||||||
@ -685,7 +685,7 @@ describe 'TextMate HTML grammar', ->
|
|||||||
# vim:noexpandtab titlestring=hi\\|there\\\\\\ ft=HTML ts=4
|
# vim:noexpandtab titlestring=hi\\|there\\\\\\ ft=HTML ts=4
|
||||||
'''
|
'''
|
||||||
for line in invalid.split /\n/
|
for line in invalid.split /\n/
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync(line)).toBeNull()
|
||||||
|
|
||||||
describe 'tags', ->
|
describe 'tags', ->
|
||||||
it 'tokenizes style tags as such', ->
|
it 'tokenizes style tags as such', ->
|
||||||
|
@ -2400,7 +2400,7 @@ describe "JavaScript grammar", ->
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
"""
|
"""
|
||||||
for line in valid.split /\n/
|
for line in valid.split /\n/
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).not.toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync(line)).not.toBeNull()
|
||||||
|
|
||||||
invalid = """
|
invalid = """
|
||||||
\x20#!/usr/sbin/node
|
\x20#!/usr/sbin/node
|
||||||
@ -2411,7 +2411,7 @@ describe "JavaScript grammar", ->
|
|||||||
#!\t/usr/bin/env --node=bar
|
#!\t/usr/bin/env --node=bar
|
||||||
"""
|
"""
|
||||||
for line in invalid.split /\n/
|
for line in invalid.split /\n/
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync(line)).toBeNull()
|
||||||
|
|
||||||
it "recognises Emacs modelines", ->
|
it "recognises Emacs modelines", ->
|
||||||
valid = """
|
valid = """
|
||||||
@ -2430,7 +2430,7 @@ describe "JavaScript grammar", ->
|
|||||||
"-*- font:x;foo : bar ; mode : jS ; bar : foo ; foooooo:baaaaar;fo:ba-*-";
|
"-*- font:x;foo : bar ; mode : jS ; bar : foo ; foooooo:baaaaar;fo:ba-*-";
|
||||||
"""
|
"""
|
||||||
for line in valid.split /\n/
|
for line in valid.split /\n/
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).not.toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync(line)).not.toBeNull()
|
||||||
|
|
||||||
invalid = """
|
invalid = """
|
||||||
/* --*js-*- */
|
/* --*js-*- */
|
||||||
@ -2448,7 +2448,7 @@ describe "JavaScript grammar", ->
|
|||||||
// -*-font:mode;mode:js--*-
|
// -*-font:mode;mode:js--*-
|
||||||
"""
|
"""
|
||||||
for line in invalid.split /\n/
|
for line in invalid.split /\n/
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync(line)).toBeNull()
|
||||||
|
|
||||||
it "recognises Vim modelines", ->
|
it "recognises Vim modelines", ->
|
||||||
valid = """
|
valid = """
|
||||||
@ -2475,7 +2475,7 @@ describe "JavaScript grammar", ->
|
|||||||
# vim:noexpandtab titlestring=hi\|there\\\\ ft=javascript ts=4
|
# vim:noexpandtab titlestring=hi\|there\\\\ ft=javascript ts=4
|
||||||
"""
|
"""
|
||||||
for line in valid.split /\n/
|
for line in valid.split /\n/
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).not.toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync(line)).not.toBeNull()
|
||||||
|
|
||||||
invalid = """
|
invalid = """
|
||||||
ex: se filetype=javascript:
|
ex: se filetype=javascript:
|
||||||
@ -2493,4 +2493,4 @@ describe "JavaScript grammar", ->
|
|||||||
# vim:noexpandtab titlestring=hi\\|there\\\\\\ ft=javascript ts=4
|
# vim:noexpandtab titlestring=hi\\|there\\\\\\ ft=javascript ts=4
|
||||||
"""
|
"""
|
||||||
for line in invalid.split /\n/
|
for line in invalid.split /\n/
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync(line)).toBeNull()
|
||||||
|
@ -721,9 +721,6 @@
|
|||||||
'match': '^\\s*(package)\\s+([^\\s;]+)'
|
'match': '^\\s*(package)\\s+([^\\s;]+)'
|
||||||
'name': 'meta.class.perl'
|
'name': 'meta.class.perl'
|
||||||
}
|
}
|
||||||
{
|
|
||||||
'include: "#sub'
|
|
||||||
}
|
|
||||||
{
|
{
|
||||||
'captures':
|
'captures':
|
||||||
'1':
|
'1':
|
||||||
|
@ -183,7 +183,7 @@ describe "Perl 6 grammar", ->
|
|||||||
#!/usr/bin/env perl6
|
#!/usr/bin/env perl6
|
||||||
"""
|
"""
|
||||||
for line in valid.split /\n/
|
for line in valid.split /\n/
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).not.toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync(line)).not.toBeNull()
|
||||||
|
|
||||||
invalid = """
|
invalid = """
|
||||||
#! pearl6
|
#! pearl6
|
||||||
@ -200,11 +200,11 @@ describe "Perl 6 grammar", ->
|
|||||||
#!\t/usr/bin/env --perl6=bar
|
#!\t/usr/bin/env --perl6=bar
|
||||||
"""
|
"""
|
||||||
for line in invalid.split /\n/
|
for line in invalid.split /\n/
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync(line)).toBeNull()
|
||||||
|
|
||||||
it "recognises the Perl6 pragma", ->
|
it "recognises the Perl6 pragma", ->
|
||||||
line = "use v6;"
|
line = "use v6;"
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).not.toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync(line)).not.toBeNull()
|
||||||
|
|
||||||
it "recognises Emacs modelines", ->
|
it "recognises Emacs modelines", ->
|
||||||
modelines = """
|
modelines = """
|
||||||
@ -222,7 +222,7 @@ describe "Perl 6 grammar", ->
|
|||||||
"-*- font:x;foo : bar ; mode : pErL6 ; bar : foo ; foooooo:baaaaar;fo:ba-*-";
|
"-*- font:x;foo : bar ; mode : pErL6 ; bar : foo ; foooooo:baaaaar;fo:ba-*-";
|
||||||
"""
|
"""
|
||||||
for line in modelines.split /\n/
|
for line in modelines.split /\n/
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).not.toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync(line)).not.toBeNull()
|
||||||
|
|
||||||
invalid = """
|
invalid = """
|
||||||
/* --*perl6-*- */
|
/* --*perl6-*- */
|
||||||
@ -241,7 +241,7 @@ describe "Perl 6 grammar", ->
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
for line in invalid.split /\n/
|
for line in invalid.split /\n/
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync(line)).toBeNull()
|
||||||
|
|
||||||
it "recognises Vim modelines", ->
|
it "recognises Vim modelines", ->
|
||||||
valid = """
|
valid = """
|
||||||
@ -268,7 +268,7 @@ describe "Perl 6 grammar", ->
|
|||||||
# vim:noexpandtab titlestring=hi\|there\\\\ ft=perl6 ts=4
|
# vim:noexpandtab titlestring=hi\|there\\\\ ft=perl6 ts=4
|
||||||
"""
|
"""
|
||||||
for line in valid.split /\n/
|
for line in valid.split /\n/
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).not.toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync(line)).not.toBeNull()
|
||||||
|
|
||||||
invalid = """
|
invalid = """
|
||||||
ex: se filetype=perl6:
|
ex: se filetype=perl6:
|
||||||
@ -286,7 +286,7 @@ describe "Perl 6 grammar", ->
|
|||||||
# vim:noexpandtab titlestring=hi\\|there\\\\\\ ft=perl6 ts=4
|
# vim:noexpandtab titlestring=hi\\|there\\\\\\ ft=perl6 ts=4
|
||||||
"""
|
"""
|
||||||
for line in invalid.split /\n/
|
for line in invalid.split /\n/
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync(line)).toBeNull()
|
||||||
|
|
||||||
# Local variables:
|
# Local variables:
|
||||||
# mode: CoffeeScript
|
# mode: CoffeeScript
|
||||||
|
@ -1439,7 +1439,7 @@ Assigned to: @<<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<
|
|||||||
#!/usr/bin/env perl
|
#!/usr/bin/env perl
|
||||||
"""
|
"""
|
||||||
for line in valid.split /\n/
|
for line in valid.split /\n/
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).not.toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync(line)).not.toBeNull()
|
||||||
|
|
||||||
invalid = """
|
invalid = """
|
||||||
#! pearl
|
#! pearl
|
||||||
@ -1456,7 +1456,7 @@ Assigned to: @<<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<
|
|||||||
#!\t/usr/bin/env --perl=bar
|
#!\t/usr/bin/env --perl=bar
|
||||||
"""
|
"""
|
||||||
for line in invalid.split /\n/
|
for line in invalid.split /\n/
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync(line)).toBeNull()
|
||||||
|
|
||||||
it "recognises Emacs modelines", ->
|
it "recognises Emacs modelines", ->
|
||||||
valid = """
|
valid = """
|
||||||
@ -1474,7 +1474,7 @@ Assigned to: @<<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<
|
|||||||
"-*- font:x;foo : bar ; mode : pErL ; bar : foo ; foooooo:baaaaar;fo:ba-*-";
|
"-*- font:x;foo : bar ; mode : pErL ; bar : foo ; foooooo:baaaaar;fo:ba-*-";
|
||||||
"""
|
"""
|
||||||
for line in valid.split /\n/
|
for line in valid.split /\n/
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).not.toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync(line)).not.toBeNull()
|
||||||
|
|
||||||
invalid = """
|
invalid = """
|
||||||
/* --*perl-*- */
|
/* --*perl-*- */
|
||||||
@ -1491,7 +1491,7 @@ Assigned to: @<<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<
|
|||||||
// -*-font:mode;mode:perl--*-
|
// -*-font:mode;mode:perl--*-
|
||||||
"""
|
"""
|
||||||
for line in invalid.split /\n/
|
for line in invalid.split /\n/
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync(line)).toBeNull()
|
||||||
|
|
||||||
it "recognises Vim modelines", ->
|
it "recognises Vim modelines", ->
|
||||||
valid = """
|
valid = """
|
||||||
@ -1518,7 +1518,7 @@ Assigned to: @<<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<
|
|||||||
# vim:noexpandtab titlestring=hi\|there\\\\ ft=perl ts=4
|
# vim:noexpandtab titlestring=hi\|there\\\\ ft=perl ts=4
|
||||||
"""
|
"""
|
||||||
for line in valid.split /\n/
|
for line in valid.split /\n/
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).not.toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync(line)).not.toBeNull()
|
||||||
|
|
||||||
invalid = """
|
invalid = """
|
||||||
ex: se filetype=perl:
|
ex: se filetype=perl:
|
||||||
@ -1536,7 +1536,7 @@ Assigned to: @<<<<<<<<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<
|
|||||||
# vim:noexpandtab titlestring=hi\\|there\\\\\\ ft=perl ts=4
|
# vim:noexpandtab titlestring=hi\\|there\\\\\\ ft=perl ts=4
|
||||||
"""
|
"""
|
||||||
for line in invalid.split /\n/
|
for line in invalid.split /\n/
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync(line)).toBeNull()
|
||||||
|
|
||||||
# Local variables:
|
# Local variables:
|
||||||
# mode: CoffeeScript
|
# mode: CoffeeScript
|
||||||
|
@ -56,7 +56,7 @@
|
|||||||
'body': 'elseif (${1:condition}) {\n\t${0:// code...}\n}'
|
'body': 'elseif (${1:condition}) {\n\t${0:// code...}\n}'
|
||||||
'for …':
|
'for …':
|
||||||
'prefix': 'for'
|
'prefix': 'for'
|
||||||
'body': 'for ($${1:i}=${2:0}; $${1:i} < $3; $${1:i}++) {\n\t${0:// code...}\n}'
|
'body': 'for ($${1:i} = ${2:0}; $${1:i} < $3; $${1:i}++) {\n\t${0:// code...}\n}'
|
||||||
'foreach …':
|
'foreach …':
|
||||||
'prefix': 'foreach'
|
'prefix': 'foreach'
|
||||||
'body': 'foreach ($${1:variable} as $${2:key} ${3:=> $${4:value}}) {\n\t${0:// code...}\n}'
|
'body': 'foreach ($${1:variable} as $${2:key} ${3:=> $${4:value}}) {\n\t${0:// code...}\n}'
|
||||||
|
@ -161,11 +161,11 @@ describe 'PHP in HTML', ->
|
|||||||
<?php namespace foo;
|
<?php namespace foo;
|
||||||
'''
|
'''
|
||||||
for line in valid.split /\n/
|
for line in valid.split /\n/
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).not.toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync(line)).not.toBeNull()
|
||||||
|
|
||||||
# Do not allow matching XML declaration until the grammar scoring system takes into account
|
# Do not allow matching XML declaration until the grammar scoring system takes into account
|
||||||
# the length of the first line match so that longer matches get the priority over shorter matches.
|
# the length of the first line match so that longer matches get the priority over shorter matches.
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync('<?xml version="1.0" encoding="UTF-8"?>')).toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync('<?xml version="1.0" encoding="UTF-8"?>')).toBeNull()
|
||||||
|
|
||||||
it 'recognises interpreter directives', ->
|
it 'recognises interpreter directives', ->
|
||||||
valid = '''
|
valid = '''
|
||||||
@ -184,7 +184,7 @@ describe 'PHP in HTML', ->
|
|||||||
#!/usr/bin/env php
|
#!/usr/bin/env php
|
||||||
'''
|
'''
|
||||||
for line in valid.split /\n/
|
for line in valid.split /\n/
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).not.toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync(line)).not.toBeNull()
|
||||||
|
|
||||||
invalid = '''
|
invalid = '''
|
||||||
\x20#!/usr/sbin/php
|
\x20#!/usr/sbin/php
|
||||||
@ -196,7 +196,7 @@ describe 'PHP in HTML', ->
|
|||||||
#!\t/usr/bin/env --php=bar
|
#!\t/usr/bin/env --php=bar
|
||||||
'''
|
'''
|
||||||
for line in invalid.split /\n/
|
for line in invalid.split /\n/
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync(line)).toBeNull()
|
||||||
|
|
||||||
it 'recognises Emacs modelines', ->
|
it 'recognises Emacs modelines', ->
|
||||||
valid = '''
|
valid = '''
|
||||||
@ -215,7 +215,7 @@ describe 'PHP in HTML', ->
|
|||||||
"-*- font:x;foo : bar ; mode : php ; bar : foo ; foooooo:baaaaar;fo:ba-*-";
|
"-*- font:x;foo : bar ; mode : php ; bar : foo ; foooooo:baaaaar;fo:ba-*-";
|
||||||
'''
|
'''
|
||||||
for line in valid.split /\n/
|
for line in valid.split /\n/
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).not.toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync(line)).not.toBeNull()
|
||||||
|
|
||||||
invalid = '''
|
invalid = '''
|
||||||
/* --*php-*- */
|
/* --*php-*- */
|
||||||
@ -233,7 +233,7 @@ describe 'PHP in HTML', ->
|
|||||||
// -*-font:mode;mode:php--*-
|
// -*-font:mode;mode:php--*-
|
||||||
'''
|
'''
|
||||||
for line in invalid.split /\n/
|
for line in invalid.split /\n/
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync(line)).toBeNull()
|
||||||
|
|
||||||
it 'recognises Vim modelines', ->
|
it 'recognises Vim modelines', ->
|
||||||
valid = '''
|
valid = '''
|
||||||
@ -260,7 +260,7 @@ describe 'PHP in HTML', ->
|
|||||||
# vim:noexpandtab titlestring=hi\|there\\\\ ft=phtml ts=4
|
# vim:noexpandtab titlestring=hi\|there\\\\ ft=phtml ts=4
|
||||||
'''
|
'''
|
||||||
for line in valid.split /\n/
|
for line in valid.split /\n/
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).not.toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync(line)).not.toBeNull()
|
||||||
|
|
||||||
invalid = '''
|
invalid = '''
|
||||||
ex: se filetype=php:
|
ex: se filetype=php:
|
||||||
@ -278,7 +278,7 @@ describe 'PHP in HTML', ->
|
|||||||
# vim:noexpandtab titlestring=hi\\|there\\\\\\ ft=php ts=4
|
# vim:noexpandtab titlestring=hi\\|there\\\\\\ ft=php ts=4
|
||||||
'''
|
'''
|
||||||
for line in invalid.split /\n/
|
for line in invalid.split /\n/
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync(line)).toBeNull()
|
||||||
|
|
||||||
it 'should tokenize <?php use Some\\Name ?>', ->
|
it 'should tokenize <?php use Some\\Name ?>', ->
|
||||||
lines = grammar.tokenizeLines '''
|
lines = grammar.tokenizeLines '''
|
||||||
|
@ -18,66 +18,66 @@ describe 'Python settings', ->
|
|||||||
it 'matches lines correctly using the increaseIndentPattern', ->
|
it 'matches lines correctly using the increaseIndentPattern', ->
|
||||||
increaseIndentRegex = languageMode.increaseIndentRegexForScopeDescriptor(['source.python'])
|
increaseIndentRegex = languageMode.increaseIndentRegexForScopeDescriptor(['source.python'])
|
||||||
|
|
||||||
expect(increaseIndentRegex.testSync('for i in range(n):')).toBeTruthy()
|
expect(increaseIndentRegex.findNextMatchSync('for i in range(n):')).toBeTruthy()
|
||||||
expect(increaseIndentRegex.testSync(' for i in range(n):')).toBeTruthy()
|
expect(increaseIndentRegex.findNextMatchSync(' for i in range(n):')).toBeTruthy()
|
||||||
expect(increaseIndentRegex.testSync('async for i in range(n):')).toBeTruthy()
|
expect(increaseIndentRegex.findNextMatchSync('async for i in range(n):')).toBeTruthy()
|
||||||
expect(increaseIndentRegex.testSync(' async for i in range(n):')).toBeTruthy()
|
expect(increaseIndentRegex.findNextMatchSync(' async for i in range(n):')).toBeTruthy()
|
||||||
expect(increaseIndentRegex.testSync('class TheClass(Object):')).toBeTruthy()
|
expect(increaseIndentRegex.findNextMatchSync('class TheClass(Object):')).toBeTruthy()
|
||||||
expect(increaseIndentRegex.testSync(' class TheClass(Object):')).toBeTruthy()
|
expect(increaseIndentRegex.findNextMatchSync(' class TheClass(Object):')).toBeTruthy()
|
||||||
expect(increaseIndentRegex.testSync('def f(x):')).toBeTruthy()
|
expect(increaseIndentRegex.findNextMatchSync('def f(x):')).toBeTruthy()
|
||||||
expect(increaseIndentRegex.testSync(' def f(x):')).toBeTruthy()
|
expect(increaseIndentRegex.findNextMatchSync(' def f(x):')).toBeTruthy()
|
||||||
expect(increaseIndentRegex.testSync('async def f(x):')).toBeTruthy()
|
expect(increaseIndentRegex.findNextMatchSync('async def f(x):')).toBeTruthy()
|
||||||
expect(increaseIndentRegex.testSync(' async def f(x):')).toBeTruthy()
|
expect(increaseIndentRegex.findNextMatchSync(' async def f(x):')).toBeTruthy()
|
||||||
expect(increaseIndentRegex.testSync('if this_var == that_var:')).toBeTruthy()
|
expect(increaseIndentRegex.findNextMatchSync('if this_var == that_var:')).toBeTruthy()
|
||||||
expect(increaseIndentRegex.testSync(' if this_var == that_var:')).toBeTruthy()
|
expect(increaseIndentRegex.findNextMatchSync(' if this_var == that_var:')).toBeTruthy()
|
||||||
expect(increaseIndentRegex.testSync('elif this_var == that_var:')).toBeTruthy()
|
expect(increaseIndentRegex.findNextMatchSync('elif this_var == that_var:')).toBeTruthy()
|
||||||
expect(increaseIndentRegex.testSync(' elif this_var == that_var:')).toBeTruthy()
|
expect(increaseIndentRegex.findNextMatchSync(' elif this_var == that_var:')).toBeTruthy()
|
||||||
expect(increaseIndentRegex.testSync('else:')).toBeTruthy()
|
expect(increaseIndentRegex.findNextMatchSync('else:')).toBeTruthy()
|
||||||
expect(increaseIndentRegex.testSync(' else:')).toBeTruthy()
|
expect(increaseIndentRegex.findNextMatchSync(' else:')).toBeTruthy()
|
||||||
expect(increaseIndentRegex.testSync('except Exception:')).toBeTruthy()
|
expect(increaseIndentRegex.findNextMatchSync('except Exception:')).toBeTruthy()
|
||||||
expect(increaseIndentRegex.testSync(' except Exception:')).toBeTruthy()
|
expect(increaseIndentRegex.findNextMatchSync(' except Exception:')).toBeTruthy()
|
||||||
expect(increaseIndentRegex.testSync('except Exception as e:')).toBeTruthy()
|
expect(increaseIndentRegex.findNextMatchSync('except Exception as e:')).toBeTruthy()
|
||||||
expect(increaseIndentRegex.testSync(' except Exception as e:')).toBeTruthy()
|
expect(increaseIndentRegex.findNextMatchSync(' except Exception as e:')).toBeTruthy()
|
||||||
expect(increaseIndentRegex.testSync('finally:')).toBeTruthy()
|
expect(increaseIndentRegex.findNextMatchSync('finally:')).toBeTruthy()
|
||||||
expect(increaseIndentRegex.testSync(' finally:')).toBeTruthy()
|
expect(increaseIndentRegex.findNextMatchSync(' finally:')).toBeTruthy()
|
||||||
expect(increaseIndentRegex.testSync('with open("filename") as f:')).toBeTruthy()
|
expect(increaseIndentRegex.findNextMatchSync('with open("filename") as f:')).toBeTruthy()
|
||||||
expect(increaseIndentRegex.testSync(' with open("filename") as f:')).toBeTruthy()
|
expect(increaseIndentRegex.findNextMatchSync(' with open("filename") as f:')).toBeTruthy()
|
||||||
expect(increaseIndentRegex.testSync('async with open("filename") as f:')).toBeTruthy()
|
expect(increaseIndentRegex.findNextMatchSync('async with open("filename") as f:')).toBeTruthy()
|
||||||
expect(increaseIndentRegex.testSync(' async with open("filename") as f:')).toBeTruthy()
|
expect(increaseIndentRegex.findNextMatchSync(' async with open("filename") as f:')).toBeTruthy()
|
||||||
expect(increaseIndentRegex.testSync('while True:')).toBeTruthy()
|
expect(increaseIndentRegex.findNextMatchSync('while True:')).toBeTruthy()
|
||||||
expect(increaseIndentRegex.testSync(' while True:')).toBeTruthy()
|
expect(increaseIndentRegex.findNextMatchSync(' while True:')).toBeTruthy()
|
||||||
expect(increaseIndentRegex.testSync('\t\t while True:')).toBeTruthy()
|
expect(increaseIndentRegex.findNextMatchSync('\t\t while True:')).toBeTruthy()
|
||||||
|
|
||||||
it 'does not match lines incorrectly using the increaseIndentPattern', ->
|
it 'does not match lines incorrectly using the increaseIndentPattern', ->
|
||||||
increaseIndentRegex = languageMode.increaseIndentRegexForScopeDescriptor(['source.python'])
|
increaseIndentRegex = languageMode.increaseIndentRegexForScopeDescriptor(['source.python'])
|
||||||
|
|
||||||
expect(increaseIndentRegex.testSync('for i in range(n)')).toBeFalsy()
|
expect(increaseIndentRegex.findNextMatchSync('for i in range(n)')).toBeFalsy()
|
||||||
expect(increaseIndentRegex.testSync('class TheClass(Object)')).toBeFalsy()
|
expect(increaseIndentRegex.findNextMatchSync('class TheClass(Object)')).toBeFalsy()
|
||||||
expect(increaseIndentRegex.testSync('def f(x)')).toBeFalsy()
|
expect(increaseIndentRegex.findNextMatchSync('def f(x)')).toBeFalsy()
|
||||||
expect(increaseIndentRegex.testSync('if this_var == that_var')).toBeFalsy()
|
expect(increaseIndentRegex.findNextMatchSync('if this_var == that_var')).toBeFalsy()
|
||||||
expect(increaseIndentRegex.testSync('"for i in range(n):"')).toBeFalsy()
|
expect(increaseIndentRegex.findNextMatchSync('"for i in range(n):"')).toBeFalsy()
|
||||||
|
|
||||||
it 'matches lines correctly using the decreaseIndentPattern', ->
|
it 'matches lines correctly using the decreaseIndentPattern', ->
|
||||||
decreaseIndentRegex = languageMode.decreaseIndentRegexForScopeDescriptor(['source.python'])
|
decreaseIndentRegex = languageMode.decreaseIndentRegexForScopeDescriptor(['source.python'])
|
||||||
|
|
||||||
expect(decreaseIndentRegex.testSync('elif this_var == that_var:')).toBeTruthy()
|
expect(decreaseIndentRegex.findNextMatchSync('elif this_var == that_var:')).toBeTruthy()
|
||||||
expect(decreaseIndentRegex.testSync(' elif this_var == that_var:')).toBeTruthy()
|
expect(decreaseIndentRegex.findNextMatchSync(' elif this_var == that_var:')).toBeTruthy()
|
||||||
expect(decreaseIndentRegex.testSync('else:')).toBeTruthy()
|
expect(decreaseIndentRegex.findNextMatchSync('else:')).toBeTruthy()
|
||||||
expect(decreaseIndentRegex.testSync(' else:')).toBeTruthy()
|
expect(decreaseIndentRegex.findNextMatchSync(' else:')).toBeTruthy()
|
||||||
expect(decreaseIndentRegex.testSync('except Exception:')).toBeTruthy()
|
expect(decreaseIndentRegex.findNextMatchSync('except Exception:')).toBeTruthy()
|
||||||
expect(decreaseIndentRegex.testSync(' except Exception:')).toBeTruthy()
|
expect(decreaseIndentRegex.findNextMatchSync(' except Exception:')).toBeTruthy()
|
||||||
expect(decreaseIndentRegex.testSync('except Exception as e:')).toBeTruthy()
|
expect(decreaseIndentRegex.findNextMatchSync('except Exception as e:')).toBeTruthy()
|
||||||
expect(decreaseIndentRegex.testSync(' except Exception as e:')).toBeTruthy()
|
expect(decreaseIndentRegex.findNextMatchSync(' except Exception as e:')).toBeTruthy()
|
||||||
expect(decreaseIndentRegex.testSync('finally:')).toBeTruthy()
|
expect(decreaseIndentRegex.findNextMatchSync('finally:')).toBeTruthy()
|
||||||
expect(decreaseIndentRegex.testSync(' finally:')).toBeTruthy()
|
expect(decreaseIndentRegex.findNextMatchSync(' finally:')).toBeTruthy()
|
||||||
expect(decreaseIndentRegex.testSync('\t\t finally:')).toBeTruthy()
|
expect(decreaseIndentRegex.findNextMatchSync('\t\t finally:')).toBeTruthy()
|
||||||
|
|
||||||
it 'does not match lines incorrectly using the decreaseIndentPattern', ->
|
it 'does not match lines incorrectly using the decreaseIndentPattern', ->
|
||||||
decreaseIndentRegex = languageMode.decreaseIndentRegexForScopeDescriptor(['source.python'])
|
decreaseIndentRegex = languageMode.decreaseIndentRegexForScopeDescriptor(['source.python'])
|
||||||
|
|
||||||
# NOTE! This first one is different from most other rote tests here.
|
# NOTE! This first one is different from most other rote tests here.
|
||||||
expect(decreaseIndentRegex.testSync('else: expression()')).toBeFalsy()
|
expect(decreaseIndentRegex.findNextMatchSync('else: expression()')).toBeFalsy()
|
||||||
expect(decreaseIndentRegex.testSync('elif this_var == that_var')).toBeFalsy()
|
expect(decreaseIndentRegex.findNextMatchSync('elif this_var == that_var')).toBeFalsy()
|
||||||
expect(decreaseIndentRegex.testSync(' elif this_var == that_var')).toBeFalsy()
|
expect(decreaseIndentRegex.findNextMatchSync(' elif this_var == that_var')).toBeFalsy()
|
||||||
expect(decreaseIndentRegex.testSync('else')).toBeFalsy()
|
expect(decreaseIndentRegex.findNextMatchSync('else')).toBeFalsy()
|
||||||
expect(decreaseIndentRegex.testSync(' "finally:"')).toBeFalsy()
|
expect(decreaseIndentRegex.findNextMatchSync(' "finally:"')).toBeFalsy()
|
||||||
|
@ -14,8 +14,8 @@ describe "Python grammar", ->
|
|||||||
grammar = atom.grammars.grammarForScopeName("source.python")
|
grammar = atom.grammars.grammarForScopeName("source.python")
|
||||||
|
|
||||||
it "recognises shebang on firstline", ->
|
it "recognises shebang on firstline", ->
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync("#!/usr/bin/env python")).not.toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync("#!/usr/bin/env python")).not.toBeNull()
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync("#! /usr/bin/env python")).not.toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync("#! /usr/bin/env python")).not.toBeNull()
|
||||||
|
|
||||||
it "parses the grammar", ->
|
it "parses the grammar", ->
|
||||||
expect(grammar).toBeDefined()
|
expect(grammar).toBeDefined()
|
||||||
|
@ -964,7 +964,7 @@ describe "TextMate Ruby grammar", ->
|
|||||||
#!/usr/bin/env ruby
|
#!/usr/bin/env ruby
|
||||||
"""
|
"""
|
||||||
for line in valid.split /\n/
|
for line in valid.split /\n/
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).not.toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync(line)).not.toBeNull()
|
||||||
|
|
||||||
invalid = """
|
invalid = """
|
||||||
\x20#!/usr/sbin/ruby
|
\x20#!/usr/sbin/ruby
|
||||||
@ -975,7 +975,7 @@ describe "TextMate Ruby grammar", ->
|
|||||||
#!\t/usr/bin/env --ruby=bar
|
#!\t/usr/bin/env --ruby=bar
|
||||||
"""
|
"""
|
||||||
for line in invalid.split /\n/
|
for line in invalid.split /\n/
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync(line)).toBeNull()
|
||||||
|
|
||||||
it "recognises Emacs modelines", ->
|
it "recognises Emacs modelines", ->
|
||||||
valid = """
|
valid = """
|
||||||
@ -994,7 +994,7 @@ describe "TextMate Ruby grammar", ->
|
|||||||
"-*- font:x;foo : bar ; mode : RUBY ; bar : foo ; foooooo:baaaaar;fo:ba-*-";
|
"-*- font:x;foo : bar ; mode : RUBY ; bar : foo ; foooooo:baaaaar;fo:ba-*-";
|
||||||
"""
|
"""
|
||||||
for line in valid.split /\n/
|
for line in valid.split /\n/
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).not.toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync(line)).not.toBeNull()
|
||||||
|
|
||||||
invalid = """
|
invalid = """
|
||||||
/* --*ruby-*- */
|
/* --*ruby-*- */
|
||||||
@ -1012,7 +1012,7 @@ describe "TextMate Ruby grammar", ->
|
|||||||
// -*-font:mode;mode:ruby--*-
|
// -*-font:mode;mode:ruby--*-
|
||||||
"""
|
"""
|
||||||
for line in invalid.split /\n/
|
for line in invalid.split /\n/
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync(line)).toBeNull()
|
||||||
|
|
||||||
it "recognises Vim modelines", ->
|
it "recognises Vim modelines", ->
|
||||||
valid = """
|
valid = """
|
||||||
@ -1039,7 +1039,7 @@ describe "TextMate Ruby grammar", ->
|
|||||||
# vim:noexpandtab titlestring=hi\|there\\\\ ft=ruby ts=4
|
# vim:noexpandtab titlestring=hi\|there\\\\ ft=ruby ts=4
|
||||||
"""
|
"""
|
||||||
for line in valid.split /\n/
|
for line in valid.split /\n/
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).not.toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync(line)).not.toBeNull()
|
||||||
|
|
||||||
invalid = """
|
invalid = """
|
||||||
ex: se filetype=ruby:
|
ex: se filetype=ruby:
|
||||||
@ -1057,4 +1057,4 @@ describe "TextMate Ruby grammar", ->
|
|||||||
# vim:noexpandtab titlestring=hi\\|there\\\\\\ ft=ruby ts=4
|
# vim:noexpandtab titlestring=hi\\|there\\\\\\ ft=ruby ts=4
|
||||||
"""
|
"""
|
||||||
for line in invalid.split /\n/
|
for line in invalid.split /\n/
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync(line)).toBeNull()
|
||||||
|
@ -361,7 +361,7 @@ describe "Shell script grammar", ->
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
"""
|
"""
|
||||||
for line in valid.split /\n/
|
for line in valid.split /\n/
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).not.toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync(line)).not.toBeNull()
|
||||||
|
|
||||||
invalid = """
|
invalid = """
|
||||||
\x20#!/usr/sbin/bash
|
\x20#!/usr/sbin/bash
|
||||||
@ -373,7 +373,7 @@ describe "Shell script grammar", ->
|
|||||||
#!\t/usr/bin/env --bash=bar
|
#!\t/usr/bin/env --bash=bar
|
||||||
"""
|
"""
|
||||||
for line in invalid.split /\n/
|
for line in invalid.split /\n/
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync(line)).toBeNull()
|
||||||
|
|
||||||
it "recognises Emacs modelines", ->
|
it "recognises Emacs modelines", ->
|
||||||
valid = """
|
valid = """
|
||||||
@ -392,7 +392,7 @@ describe "Shell script grammar", ->
|
|||||||
"-*- font:x;foo : bar ; mode : sH ; bar : foo ; foooooo:baaaaar;fo:ba-*-";
|
"-*- font:x;foo : bar ; mode : sH ; bar : foo ; foooooo:baaaaar;fo:ba-*-";
|
||||||
"""
|
"""
|
||||||
for line in valid.split /\n/
|
for line in valid.split /\n/
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).not.toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync(line)).not.toBeNull()
|
||||||
|
|
||||||
invalid = """
|
invalid = """
|
||||||
/* --*sh-*- */
|
/* --*sh-*- */
|
||||||
@ -410,7 +410,7 @@ describe "Shell script grammar", ->
|
|||||||
// -*-font:mode;mode:sh--*-
|
// -*-font:mode;mode:sh--*-
|
||||||
"""
|
"""
|
||||||
for line in invalid.split /\n/
|
for line in invalid.split /\n/
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync(line)).toBeNull()
|
||||||
|
|
||||||
it "recognises Vim modelines", ->
|
it "recognises Vim modelines", ->
|
||||||
valid = """
|
valid = """
|
||||||
@ -437,7 +437,7 @@ describe "Shell script grammar", ->
|
|||||||
# vim:noexpandtab titlestring=hi\|there\\\\ ft=sh ts=4
|
# vim:noexpandtab titlestring=hi\|there\\\\ ft=sh ts=4
|
||||||
"""
|
"""
|
||||||
for line in valid.split /\n/
|
for line in valid.split /\n/
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).not.toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync(line)).not.toBeNull()
|
||||||
|
|
||||||
invalid = """
|
invalid = """
|
||||||
ex: se filetype=sh:
|
ex: se filetype=sh:
|
||||||
@ -455,4 +455,4 @@ describe "Shell script grammar", ->
|
|||||||
# vim:noexpandtab titlestring=hi\\|there\\\\\\ ft=sh ts=4
|
# vim:noexpandtab titlestring=hi\\|there\\\\\\ ft=sh ts=4
|
||||||
"""
|
"""
|
||||||
for line in invalid.split /\n/
|
for line in invalid.split /\n/
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync(line)).toBeNull()
|
||||||
|
@ -122,7 +122,7 @@ attrName="attrValue">
|
|||||||
"-*- font:x;foo : bar ; mode : xMl ; bar : foo ; foooooo:baaaaar;fo:ba-*-";
|
"-*- font:x;foo : bar ; mode : xMl ; bar : foo ; foooooo:baaaaar;fo:ba-*-";
|
||||||
"""
|
"""
|
||||||
for line in valid.split /\n/
|
for line in valid.split /\n/
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).not.toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync(line)).not.toBeNull()
|
||||||
|
|
||||||
invalid = """
|
invalid = """
|
||||||
/* --*XML-*- */
|
/* --*XML-*- */
|
||||||
@ -140,7 +140,7 @@ attrName="attrValue">
|
|||||||
// -*-font:mode;mode:xml--*-
|
// -*-font:mode;mode:xml--*-
|
||||||
"""
|
"""
|
||||||
for line in invalid.split /\n/
|
for line in invalid.split /\n/
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync(line)).toBeNull()
|
||||||
|
|
||||||
it "recognises Vim modelines", ->
|
it "recognises Vim modelines", ->
|
||||||
valid = """
|
valid = """
|
||||||
@ -167,7 +167,7 @@ attrName="attrValue">
|
|||||||
# vim:noexpandtab titlestring=hi\|there\\\\ ft=xml ts=4
|
# vim:noexpandtab titlestring=hi\|there\\\\ ft=xml ts=4
|
||||||
"""
|
"""
|
||||||
for line in valid.split /\n/
|
for line in valid.split /\n/
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).not.toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync(line)).not.toBeNull()
|
||||||
|
|
||||||
invalid = """
|
invalid = """
|
||||||
ex: se filetype=xml:
|
ex: se filetype=xml:
|
||||||
@ -185,7 +185,7 @@ attrName="attrValue">
|
|||||||
# vim:noexpandtab titlestring=hi\\|there\\\\\\ ft=xml ts=4
|
# vim:noexpandtab titlestring=hi\\|there\\\\\\ ft=xml ts=4
|
||||||
"""
|
"""
|
||||||
for line in invalid.split /\n/
|
for line in invalid.split /\n/
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync(line)).toBeNull()
|
||||||
|
|
||||||
it "recognises a valid XML declaration", ->
|
it "recognises a valid XML declaration", ->
|
||||||
valid = """
|
valid = """
|
||||||
@ -196,7 +196,7 @@ attrName="attrValue">
|
|||||||
<?xml version="1.0" encoding='UTF-8' standalone='no' ?>
|
<?xml version="1.0" encoding='UTF-8' standalone='no' ?>
|
||||||
"""
|
"""
|
||||||
for line in valid.split /\n/
|
for line in valid.split /\n/
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).not.toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync(line)).not.toBeNull()
|
||||||
|
|
||||||
invalid = """
|
invalid = """
|
||||||
<?XML version="1.0"?>
|
<?XML version="1.0"?>
|
||||||
@ -209,4 +209,4 @@ attrName="attrValue">
|
|||||||
<?xml version="1.0">
|
<?xml version="1.0">
|
||||||
"""
|
"""
|
||||||
for line in invalid.split /\n/
|
for line in invalid.split /\n/
|
||||||
expect(grammar.firstLineRegex.scanner.findNextMatchSync(line)).toBeNull()
|
expect(grammar.firstLineRegex.findNextMatchSync(line)).toBeNull()
|
||||||
|
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';
|
import WelcomePackage from './welcome-package';
|
||||||
|
|
||||||
|
export const t = atom.i18n.getT("welcome");
|
||||||
|
|
||||||
export default new WelcomePackage();
|
export default new WelcomePackage();
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
import etch from 'etch';
|
import etch from 'etch';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
import { t } from "./main";
|
||||||
|
|
||||||
export default class WelcomeView {
|
export default class WelcomeView {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@ -126,7 +127,7 @@ export default class WelcomeView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getTitle() {
|
getTitle() {
|
||||||
return 'Welcome';
|
return t("title");
|
||||||
}
|
}
|
||||||
|
|
||||||
isEqual(other) {
|
isEqual(other) {
|
||||||
|
@ -99,7 +99,7 @@ if [ $OS == 'Mac' ]; then
|
|||||||
PULSAR_PATH="$HOME/Applications"
|
PULSAR_PATH="$HOME/Applications"
|
||||||
else
|
else
|
||||||
# We haven't found an Pulsar.app, use spotlight to search for Pulsar
|
# We haven't found an Pulsar.app, use spotlight to search for Pulsar
|
||||||
PULSAR_PATH="$(mdfind "kMDItemCFBundleIdentifier == 'com.github.pulsar'" | grep -v ShipIt | head -1 | xargs -0 dirname)"
|
PULSAR_PATH="$(mdfind "kMDItemCFBundleIdentifier == 'dev.pulsar-edit.pulsar'" | grep -v ShipIt | head -1 | xargs -0 dirname)"
|
||||||
|
|
||||||
# Exit if Pulsar can't be found
|
# Exit if Pulsar can't be found
|
||||||
if [ ! -x "$PULSAR_PATH/$ATOM_APP_NAME" ]; then
|
if [ ! -x "$PULSAR_PATH/$ATOM_APP_NAME" ]; then
|
||||||
|
@ -7,6 +7,6 @@ Section: devel
|
|||||||
Priority: optional
|
Priority: optional
|
||||||
Architecture: <%= arch %>
|
Architecture: <%= arch %>
|
||||||
Installed-Size: <%= installedSize %>
|
Installed-Size: <%= installedSize %>
|
||||||
Maintainer: GitHub <atom@github.com>
|
Maintainer: Pulsar-Edit <admin@pulsar-edit.dev>
|
||||||
Description: <%= description %>
|
Description: <%= description %>
|
||||||
Pulsar is a free and open source text editor that is modern, approachable, and hackable to the core.
|
Pulsar is a free and open source text editor that is modern, approachable, and hackable to the core.
|
||||||
|
@ -2,10 +2,10 @@
|
|||||||
Name=<%= appName %>
|
Name=<%= appName %>
|
||||||
Comment=<%= description %>
|
Comment=<%= description %>
|
||||||
GenericName=Text Editor
|
GenericName=Text Editor
|
||||||
Exec=env ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT=false <%= installDir %>/bin/<%= appFileName %> %F
|
Exec=env ATOM_DISABLE_SHELLING_OUT_FOR_ENVIRONMENT=false <%= installDir %>/bin/<%= appFileName %> --no-sandbox %F
|
||||||
Icon=<%= iconPath %>
|
Icon=<%= iconPath %>
|
||||||
Type=Application
|
Type=Application
|
||||||
StartupNotify=true
|
StartupNotify=true
|
||||||
Categories=GTK;Utility;TextEditor;Development;
|
Categories=GTK;Utility;TextEditor;Development;
|
||||||
MimeType=application/javascript;application/json;application/x-httpd-eruby;application/x-httpd-php;application/x-httpd-php3;application/x-httpd-php4;application/x-httpd-php5;application/x-ruby;application/x-bash;application/x-csh;application/x-sh;application/x-zsh;application/x-shellscript;application/x-sql;application/x-tcl;application/xhtml+xml;application/xml;application/xml-dtd;application/xslt+xml;text/coffeescript;text/css;text/html;text/plain;text/xml;text/xml-dtd;text/x-bash;text/x-c++;text/x-c++hdr;text/x-c++src;text/x-c;text/x-chdr;text/x-csh;text/x-csrc;text/x-dsrc;text/x-diff;text/x-go;text/x-java;text/x-java-source;text/x-makefile;text/x-markdown;text/x-objc;text/x-perl;text/x-php;text/x-python;text/x-ruby;text/x-sh;text/x-zsh;text/yaml;inode/directory
|
MimeType=application/javascript;application/json;application/x-httpd-eruby;application/x-httpd-php;application/x-httpd-php3;application/x-httpd-php4;application/x-httpd-php5;application/x-ruby;application/x-bash;application/x-csh;application/x-sh;application/x-zsh;application/x-shellscript;application/x-sql;application/x-tcl;application/xhtml+xml;application/xml;application/xml-dtd;application/xslt+xml;text/coffeescript;text/css;text/html;text/plain;text/xml;text/xml-dtd;text/x-bash;text/x-c++;text/x-c++hdr;text/x-c++src;text/x-c;text/x-chdr;text/x-csh;text/x-csrc;text/x-dsrc;text/x-diff;text/x-go;text/x-java;text/x-java-source;text/x-makefile;text/x-markdown;text/x-objc;text/x-perl;text/x-php;text/x-python;text/x-ruby;text/x-sh;text/x-zsh;text/yaml;inode/directory
|
||||||
StartupWMClass=atom
|
StartupWMClass=pulsar
|
||||||
|
@ -4,9 +4,9 @@
|
|||||||
"http://www.freedesktop.org/standards/PolicyKit/1.0/policyconfig.dtd">
|
"http://www.freedesktop.org/standards/PolicyKit/1.0/policyconfig.dtd">
|
||||||
<policyconfig>
|
<policyconfig>
|
||||||
<vendor>Pulsar</vendor>
|
<vendor>Pulsar</vendor>
|
||||||
<action id="atom.pkexec.dd">
|
<action id="pulsar.pkexec.dd">
|
||||||
<description gettext-domain="atom">Admin privileges required</description>
|
<description gettext-domain="pulsar">Admin privileges required</description>
|
||||||
<message gettext-domain="atom">Please enter your password to save this file</message>
|
<message gettext-domain="pulsar">Please enter your password to save this file</message>
|
||||||
<annotate key="org.freedesktop.policykit.exec.path">/bin/dd</annotate>
|
<annotate key="org.freedesktop.policykit.exec.path">/bin/dd</annotate>
|
||||||
<annotate key="org.freedesktop.policykit.exec.allow_gui">true</annotate>
|
<annotate key="org.freedesktop.policykit.exec.allow_gui">true</annotate>
|
||||||
<defaults>
|
<defaults>
|
||||||
|
@ -3,7 +3,7 @@ Version: <%= version %>
|
|||||||
Release: 0.1%{?dist}
|
Release: 0.1%{?dist}
|
||||||
Summary: <%= description %>
|
Summary: <%= description %>
|
||||||
License: MIT
|
License: MIT
|
||||||
URL: https://atom.io/
|
URL: https://pulsar-edit.dev
|
||||||
AutoReqProv: no # Avoid libchromiumcontent.so missing dependency
|
AutoReqProv: no # Avoid libchromiumcontent.so missing dependency
|
||||||
Prefix: <%= installDir %>
|
Prefix: <%= installDir %>
|
||||||
|
|
||||||
@ -25,7 +25,7 @@ Requires: alsa-lib, git-core, (glib2 or kde-cli-tools or xdg-utils), lsb-core-no
|
|||||||
mkdir -p "%{buildroot}/<%= installDir %>/share/<%= appFileName %>/"
|
mkdir -p "%{buildroot}/<%= installDir %>/share/<%= appFileName %>/"
|
||||||
cp -r "<%= appName %>"/* "%{buildroot}/<%= installDir %>/share/<%= appFileName %>/"
|
cp -r "<%= appName %>"/* "%{buildroot}/<%= installDir %>/share/<%= appFileName %>/"
|
||||||
mkdir -p "%{buildroot}/<%= installDir %>/bin/"
|
mkdir -p "%{buildroot}/<%= installDir %>/bin/"
|
||||||
ln -sf "../share/<%= appFileName %>/resources/app/apm/node_modules/.bin/apm" "%{buildroot}/<%= installDir %>/bin/<%= apmFileName %>"
|
ln -sf "../share/<%= appFileName %>/resources/app/ppm/node_modules/.bin/apm" "%{buildroot}/<%= installDir %>/bin/<%= apmFileName %>"
|
||||||
cp pulsar.sh "%{buildroot}/<%= installDir %>/bin/<%= appFileName %>"
|
cp pulsar.sh "%{buildroot}/<%= installDir %>/bin/<%= appFileName %>"
|
||||||
chmod 755 "%{buildroot}/<%= installDir %>/bin/<%= appFileName %>"
|
chmod 755 "%{buildroot}/<%= installDir %>/bin/<%= appFileName %>"
|
||||||
mkdir -p "%{buildroot}/<%= installDir %>/share/applications/"
|
mkdir -p "%{buildroot}/<%= installDir %>/share/applications/"
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
<string>Pulsar Helper</string>
|
<string>Pulsar Helper</string>
|
||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
<string>com.github.pulsar.helper</string>
|
<string>dev.pulsar-edit.pulsar.helper</string>
|
||||||
<key>CFBundleInfoDictionaryVersion</key>
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
<string>6.0</string>
|
<string>6.0</string>
|
||||||
<key>CFBundleName</key>
|
<key>CFBundleName</key>
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
<key>CFBundleIconFile</key>
|
<key>CFBundleIconFile</key>
|
||||||
<string>pulsar.icns</string>
|
<string>pulsar.icns</string>
|
||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
<string>com.github.pulsar</string>
|
<string>dev.pulsar-edit.pulsar</string>
|
||||||
<key>CFBundleInfoDictionaryVersion</key>
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
<string>6.0</string>
|
<string>6.0</string>
|
||||||
<key>CFBundleName</key>
|
<key>CFBundleName</key>
|
||||||
|
@ -55,6 +55,7 @@ let options = {
|
|||||||
"package.json",
|
"package.json",
|
||||||
"dot-atom/**/*",
|
"dot-atom/**/*",
|
||||||
"exports/**/*",
|
"exports/**/*",
|
||||||
|
"i18n/**/*",
|
||||||
"resources/**/*",
|
"resources/**/*",
|
||||||
"src/**/*",
|
"src/**/*",
|
||||||
"static/**/*",
|
"static/**/*",
|
||||||
|
@ -5,13 +5,14 @@ const temp = require('temp').track();
|
|||||||
const TextBuffer = require('text-buffer');
|
const TextBuffer = require('text-buffer');
|
||||||
const GrammarRegistry = require('../src/grammar-registry');
|
const GrammarRegistry = require('../src/grammar-registry');
|
||||||
const TreeSitterGrammar = require('../src/tree-sitter-grammar');
|
const TreeSitterGrammar = require('../src/tree-sitter-grammar');
|
||||||
const FirstMate = require('first-mate');
|
const SecondMate = require('second-mate');
|
||||||
const { OnigRegExp } = require('oniguruma');
|
const { OnigScanner } = SecondMate;
|
||||||
|
|
||||||
describe('GrammarRegistry', () => {
|
describe('GrammarRegistry', () => {
|
||||||
let grammarRegistry;
|
let grammarRegistry;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(async () => {
|
||||||
|
await SecondMate.ready
|
||||||
grammarRegistry = new GrammarRegistry({ config: atom.config });
|
grammarRegistry = new GrammarRegistry({ config: atom.config });
|
||||||
expect(subscriptionCount(grammarRegistry)).toBe(1);
|
expect(subscriptionCount(grammarRegistry)).toBe(1);
|
||||||
});
|
});
|
||||||
@ -102,7 +103,7 @@ describe('GrammarRegistry', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const grammar = grammarRegistry.grammarForId('source.js');
|
const grammar = grammarRegistry.grammarForId('source.js');
|
||||||
expect(grammar instanceof FirstMate.Grammar).toBe(true);
|
expect(grammar instanceof SecondMate.Grammar).toBe(true);
|
||||||
expect(grammar.scopeName).toBe('source.js');
|
expect(grammar.scopeName).toBe('source.js');
|
||||||
|
|
||||||
grammarRegistry.removeGrammar(grammar);
|
grammarRegistry.removeGrammar(grammar);
|
||||||
@ -127,7 +128,7 @@ describe('GrammarRegistry', () => {
|
|||||||
|
|
||||||
grammarRegistry.removeGrammar(grammar);
|
grammarRegistry.removeGrammar(grammar);
|
||||||
expect(
|
expect(
|
||||||
grammarRegistry.grammarForId('source.js') instanceof FirstMate.Grammar
|
grammarRegistry.grammarForId('source.js') instanceof SecondMate.Grammar
|
||||||
).toBe(true);
|
).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -560,7 +561,7 @@ describe('GrammarRegistry', () => {
|
|||||||
|
|
||||||
const grammar = grammarRegistry.selectGrammar('test.js');
|
const grammar = grammarRegistry.selectGrammar('test.js');
|
||||||
expect(grammar.scopeName).toBe('source.js');
|
expect(grammar.scopeName).toBe('source.js');
|
||||||
expect(grammar instanceof FirstMate.Grammar).toBe(true);
|
expect(grammar instanceof SecondMate.Grammar).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('favors a tree-sitter grammar over a text-mate grammar when `core.useTreeSitterParsers` is true', () => {
|
it('favors a tree-sitter grammar over a text-mate grammar when `core.useTreeSitterParsers` is true', () => {
|
||||||
@ -766,7 +767,7 @@ describe('GrammarRegistry', () => {
|
|||||||
grammarRegistry.addGrammar(grammar1);
|
grammarRegistry.addGrammar(grammar1);
|
||||||
const grammar2 = {
|
const grammar2 = {
|
||||||
name: 'foo++',
|
name: 'foo++',
|
||||||
contentRegex: new OnigRegExp('.*bar'),
|
contentRegex: new OnigScanner(['.*bar']),
|
||||||
fileTypes: ['foo']
|
fileTypes: ['foo']
|
||||||
};
|
};
|
||||||
grammarRegistry.addGrammar(grammar2);
|
grammarRegistry.addGrammar(grammar2);
|
||||||
|
@ -46,6 +46,7 @@ const TextEditorRegistry = require('./text-editor-registry');
|
|||||||
const AutoUpdateManager = require('./auto-update-manager');
|
const AutoUpdateManager = require('./auto-update-manager');
|
||||||
const StartupTime = require('./startup-time');
|
const StartupTime = require('./startup-time');
|
||||||
const getReleaseChannel = require('./get-release-channel');
|
const getReleaseChannel = require('./get-release-channel');
|
||||||
|
const I18n = require("./i18n");
|
||||||
const packagejson = require("../package.json");
|
const packagejson = require("../package.json");
|
||||||
|
|
||||||
const stat = util.promisify(fs.stat);
|
const stat = util.promisify(fs.stat);
|
||||||
@ -125,6 +126,11 @@ class AtomEnvironment {
|
|||||||
/** @type {StyleManager} */
|
/** @type {StyleManager} */
|
||||||
this.styles = new StyleManager();
|
this.styles = new StyleManager();
|
||||||
|
|
||||||
|
this.i18n = new I18n({
|
||||||
|
notificationManager: this.notifications,
|
||||||
|
config: this.config
|
||||||
|
});
|
||||||
|
|
||||||
/** @type {PackageManager} */
|
/** @type {PackageManager} */
|
||||||
this.packages = new PackageManager({
|
this.packages = new PackageManager({
|
||||||
config: this.config,
|
config: this.config,
|
||||||
@ -135,7 +141,8 @@ class AtomEnvironment {
|
|||||||
grammarRegistry: this.grammars,
|
grammarRegistry: this.grammars,
|
||||||
deserializerManager: this.deserializers,
|
deserializerManager: this.deserializers,
|
||||||
viewRegistry: this.views,
|
viewRegistry: this.views,
|
||||||
uriHandlerRegistry: this.uriHandlerRegistry
|
uriHandlerRegistry: this.uriHandlerRegistry,
|
||||||
|
i18n: this.i18n
|
||||||
});
|
});
|
||||||
|
|
||||||
/** @type {ThemeManager} */
|
/** @type {ThemeManager} */
|
||||||
@ -149,6 +156,7 @@ class AtomEnvironment {
|
|||||||
|
|
||||||
/** @type {MenuManager} */
|
/** @type {MenuManager} */
|
||||||
this.menu = new MenuManager({
|
this.menu = new MenuManager({
|
||||||
|
i18n: this.i18n,
|
||||||
keymapManager: this.keymaps,
|
keymapManager: this.keymaps,
|
||||||
packageManager: this.packages
|
packageManager: this.packages
|
||||||
});
|
});
|
||||||
@ -274,6 +282,19 @@ class AtomEnvironment {
|
|||||||
this.project.replace(projectSpecification);
|
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.menu.initialize({ resourcePath });
|
||||||
this.contextMenu.initialize({ resourcePath, devMode });
|
this.contextMenu.initialize({ resourcePath, devMode });
|
||||||
|
|
||||||
@ -287,12 +308,6 @@ class AtomEnvironment {
|
|||||||
this.commands.attach(this.window);
|
this.commands.attach(this.window);
|
||||||
|
|
||||||
this.styles.initialize({ configDirPath: this.configDirPath });
|
this.styles.initialize({ configDirPath: this.configDirPath });
|
||||||
this.packages.initialize({
|
|
||||||
devMode,
|
|
||||||
configDirPath: this.configDirPath,
|
|
||||||
resourcePath,
|
|
||||||
safeMode
|
|
||||||
});
|
|
||||||
this.themes.initialize({
|
this.themes.initialize({
|
||||||
configDirPath: this.configDirPath,
|
configDirPath: this.configDirPath,
|
||||||
resourcePath,
|
resourcePath,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
const _ = require('underscore-plus');
|
const _ = require('underscore-plus');
|
||||||
const Grim = require('grim');
|
const Grim = require('grim');
|
||||||
const CSON = require('season');
|
const CSON = require('season');
|
||||||
const FirstMate = require('first-mate');
|
const SecondMate = require('second-mate');
|
||||||
const { Disposable, CompositeDisposable } = require('event-kit');
|
const { Disposable, CompositeDisposable } = require('event-kit');
|
||||||
const TextMateLanguageMode = require('./text-mate-language-mode');
|
const TextMateLanguageMode = require('./text-mate-language-mode');
|
||||||
const TreeSitterLanguageMode = require('./tree-sitter-language-mode');
|
const TreeSitterLanguageMode = require('./tree-sitter-language-mode');
|
||||||
@ -20,7 +20,7 @@ module.exports = class GrammarRegistry {
|
|||||||
constructor({ config } = {}) {
|
constructor({ config } = {}) {
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.subscriptions = new CompositeDisposable();
|
this.subscriptions = new CompositeDisposable();
|
||||||
this.textmateRegistry = new FirstMate.GrammarRegistry({
|
this.textmateRegistry = new SecondMate.GrammarRegistry({
|
||||||
maxTokensPerLine: 100,
|
maxTokensPerLine: 100,
|
||||||
maxLineLength: 1000
|
maxLineLength: 1000
|
||||||
});
|
});
|
||||||
@ -264,7 +264,7 @@ module.exports = class GrammarRegistry {
|
|||||||
if (grammar.contentRegex) {
|
if (grammar.contentRegex) {
|
||||||
const contentMatch = isTreeSitter
|
const contentMatch = isTreeSitter
|
||||||
? grammar.contentRegex.test(contents)
|
? grammar.contentRegex.test(contents)
|
||||||
: grammar.contentRegex.testSync(contents);
|
: grammar.contentRegex.findNextMatchSync(contents);
|
||||||
if (contentMatch) {
|
if (contentMatch) {
|
||||||
score += 0.05;
|
score += 0.05;
|
||||||
} else {
|
} else {
|
||||||
@ -339,8 +339,8 @@ module.exports = class GrammarRegistry {
|
|||||||
.split('\n')
|
.split('\n')
|
||||||
.slice(0, numberOfNewlinesInRegex + 1)
|
.slice(0, numberOfNewlinesInRegex + 1)
|
||||||
.join('\n');
|
.join('\n');
|
||||||
if (grammar.firstLineRegex.testSync) {
|
if (grammar.firstLineRegex.findNextMatchSync) {
|
||||||
return grammar.firstLineRegex.testSync(prefix);
|
return grammar.firstLineRegex.findNextMatchSync(prefix);
|
||||||
} else {
|
} else {
|
||||||
return grammar.firstLineRegex.test(prefix);
|
return grammar.firstLineRegex.test(prefix);
|
||||||
}
|
}
|
||||||
|
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.
|
// Replaces VERSION with the current version.
|
||||||
substituteVersion(template) {
|
substituteVersion(template) {
|
||||||
let item = this.flattenMenuTemplate(template).find(
|
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
|
// Sets the proper visible state the update menu items
|
||||||
|
@ -12,8 +12,8 @@ function addItemToMenu(item, menu) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function merge(menu, item, itemSpecificity = Infinity) {
|
function merge(menu, item, t, itemSpecificity = Infinity) {
|
||||||
item = cloneMenuItem(item);
|
item = cloneAndLocaliseMenuItem(item, t);
|
||||||
ItemSpecificities.set(item, itemSpecificity);
|
ItemSpecificities.set(item, itemSpecificity);
|
||||||
const matchingItemIndex = findMatchingItemIndex(menu, item);
|
const matchingItemIndex = findMatchingItemIndex(menu, item);
|
||||||
|
|
||||||
@ -25,7 +25,7 @@ function merge(menu, item, itemSpecificity = Infinity) {
|
|||||||
const matchingItem = menu[matchingItemIndex];
|
const matchingItem = menu[matchingItemIndex];
|
||||||
if (item.submenu != null) {
|
if (item.submenu != null) {
|
||||||
for (let submenuItem of item.submenu) {
|
for (let submenuItem of item.submenu) {
|
||||||
merge(matchingItem.submenu, submenuItem, itemSpecificity);
|
merge(matchingItem.submenu, submenuItem, t, itemSpecificity);
|
||||||
}
|
}
|
||||||
} else if (
|
} else if (
|
||||||
itemSpecificity &&
|
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);
|
const matchingItemIndex = findMatchingItemIndex(menu, item);
|
||||||
if (matchingItemIndex === -1) {
|
if (matchingItemIndex === -1) {
|
||||||
return;
|
return;
|
||||||
@ -74,11 +75,12 @@ function normalizeLabel(label) {
|
|||||||
return process.platform === 'darwin' ? label : label.replace(/&/g, '');
|
return process.platform === 'darwin' ? label : label.replace(/&/g, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
function cloneMenuItem(item) {
|
function cloneAndLocaliseMenuItem(item, t) {
|
||||||
item = _.pick(
|
item = _.pick(
|
||||||
item,
|
item,
|
||||||
'type',
|
'type',
|
||||||
'label',
|
'label',
|
||||||
|
'localisedLabel',
|
||||||
'id',
|
'id',
|
||||||
'enabled',
|
'enabled',
|
||||||
'visible',
|
'visible',
|
||||||
@ -92,11 +94,18 @@ function cloneMenuItem(item) {
|
|||||||
'beforeGroupContaining',
|
'beforeGroupContaining',
|
||||||
'afterGroupContaining'
|
'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) {
|
if (item.id === null || item.id === undefined) {
|
||||||
item.id = normalizeLabel(item.label);
|
item.id = normalizeLabel(item.label);
|
||||||
}
|
}
|
||||||
if (item.submenu != null) {
|
if (item.submenu != null) {
|
||||||
item.submenu = item.submenu.map(submenuItem => cloneMenuItem(submenuItem));
|
item.submenu = item.submenu.map(submenuItem => cloneAndLocaliseMenuItem(submenuItem, t));
|
||||||
}
|
}
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
@ -133,6 +142,6 @@ module.exports = {
|
|||||||
merge,
|
merge,
|
||||||
unmerge,
|
unmerge,
|
||||||
normalizeLabel,
|
normalizeLabel,
|
||||||
cloneMenuItem,
|
cloneAndLocaliseMenuItem,
|
||||||
acceleratorForKeystroke
|
acceleratorForKeystroke
|
||||||
};
|
};
|
||||||
|
@ -60,8 +60,8 @@ if (buildMetadata) {
|
|||||||
//
|
//
|
||||||
// See {::add} for more info about adding menu's directly.
|
// See {::add} for more info about adding menu's directly.
|
||||||
module.exports = MenuManager = class MenuManager {
|
module.exports = MenuManager = class MenuManager {
|
||||||
constructor({resourcePath, keymapManager, packageManager}) {
|
constructor({ i18n, keymapManager, packageManager }) {
|
||||||
this.resourcePath = resourcePath;
|
this.i18n = i18n;
|
||||||
this.keymapManager = keymapManager;
|
this.keymapManager = keymapManager;
|
||||||
this.packageManager = packageManager;
|
this.packageManager = packageManager;
|
||||||
this.initialized = false;
|
this.initialized = false;
|
||||||
@ -101,9 +101,8 @@ module.exports = MenuManager = class MenuManager {
|
|||||||
// added menu items.
|
// added menu items.
|
||||||
add(items) {
|
add(items) {
|
||||||
items = _.deepClone(items);
|
items = _.deepClone(items);
|
||||||
for (let i = 0; i < items.length; i++) {
|
for (const item of items) {
|
||||||
const item = items[i];
|
if (item.label == null && item.localisedLabel == null) {
|
||||||
if (item.label == null) {
|
|
||||||
continue; // TODO: Should we emit a warning here?
|
continue; // TODO: Should we emit a warning here?
|
||||||
}
|
}
|
||||||
this.merge(this.template, item);
|
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
|
// Merges an item in a submenu aware way such that new items are always
|
||||||
// appended to the bottom of existing menus where possible.
|
// appended to the bottom of existing menus where possible.
|
||||||
merge(menu, item) {
|
merge(menu, item) {
|
||||||
MenuHelpers.merge(menu, item);
|
MenuHelpers.merge(menu, item, this.i18n.t);
|
||||||
}
|
}
|
||||||
|
|
||||||
unmerge(menu, item) {
|
unmerge(menu, item) {
|
||||||
MenuHelpers.unmerge(menu, item);
|
MenuHelpers.unmerge(menu, item, this.i18n.t);
|
||||||
}
|
}
|
||||||
|
|
||||||
sendToBrowserProcess(template, keystrokesByCommand) {
|
sendToBrowserProcess(template, keystrokesByCommand) {
|
||||||
@ -242,7 +241,8 @@ module.exports = MenuManager = class MenuManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sortPackagesMenu() {
|
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)) {
|
if (!(packagesMenu && packagesMenu.submenu != null)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -255,5 +255,4 @@ module.exports = MenuManager = class MenuManager {
|
|||||||
});
|
});
|
||||||
return this.update();
|
return this.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@ -38,7 +38,8 @@ module.exports = class PackageManager {
|
|||||||
grammarRegistry: this.grammarRegistry,
|
grammarRegistry: this.grammarRegistry,
|
||||||
deserializerManager: this.deserializerManager,
|
deserializerManager: this.deserializerManager,
|
||||||
viewRegistry: this.viewRegistry,
|
viewRegistry: this.viewRegistry,
|
||||||
uriHandlerRegistry: this.uriHandlerRegistry
|
uriHandlerRegistry: this.uriHandlerRegistry,
|
||||||
|
i18n: this.i18n
|
||||||
} = params);
|
} = params);
|
||||||
|
|
||||||
this.emitter = new Emitter();
|
this.emitter = new Emitter();
|
||||||
@ -587,7 +588,8 @@ module.exports = class PackageManager {
|
|||||||
menuManager: this.menuManager,
|
menuManager: this.menuManager,
|
||||||
contextMenuManager: this.contextMenuManager,
|
contextMenuManager: this.contextMenuManager,
|
||||||
deserializerManager: this.deserializerManager,
|
deserializerManager: this.deserializerManager,
|
||||||
viewRegistry: this.viewRegistry
|
viewRegistry: this.viewRegistry,
|
||||||
|
i18n: this.i18n
|
||||||
};
|
};
|
||||||
|
|
||||||
pack = metadata.theme ? new ThemePackage(options) : new Package(options);
|
pack = metadata.theme ? new ThemePackage(options) : new Package(options);
|
||||||
@ -692,7 +694,8 @@ module.exports = class PackageManager {
|
|||||||
menuManager: this.menuManager,
|
menuManager: this.menuManager,
|
||||||
contextMenuManager: this.contextMenuManager,
|
contextMenuManager: this.contextMenuManager,
|
||||||
deserializerManager: this.deserializerManager,
|
deserializerManager: this.deserializerManager,
|
||||||
viewRegistry: this.viewRegistry
|
viewRegistry: this.viewRegistry,
|
||||||
|
i18n: this.i18n
|
||||||
};
|
};
|
||||||
|
|
||||||
const pack = metadata.theme
|
const pack = metadata.theme
|
||||||
|
@ -30,6 +30,7 @@ module.exports = class Package {
|
|||||||
this.contextMenuManager = params.contextMenuManager;
|
this.contextMenuManager = params.contextMenuManager;
|
||||||
this.deserializerManager = params.deserializerManager;
|
this.deserializerManager = params.deserializerManager;
|
||||||
this.viewRegistry = params.viewRegistry;
|
this.viewRegistry = params.viewRegistry;
|
||||||
|
this.i18n = params.i18n;
|
||||||
this.emitter = new Emitter();
|
this.emitter = new Emitter();
|
||||||
|
|
||||||
this.mainModule = null;
|
this.mainModule = null;
|
||||||
@ -240,7 +241,10 @@ module.exports = class Package {
|
|||||||
}
|
}
|
||||||
if (typeof this.mainModule.activate === 'function') {
|
if (typeof this.mainModule.activate === 'function') {
|
||||||
this.mainModule.activate(
|
this.mainModule.activate(
|
||||||
this.packageManager.getPackageState(this.name) || {}
|
this.packageManager.getPackageState(this.name) || {},
|
||||||
|
{
|
||||||
|
t: this.i18n.getT(this.name)
|
||||||
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
this.mainActivated = true;
|
this.mainActivated = true;
|
||||||
|
@ -13,7 +13,7 @@ const NullGrammar = require('./null-grammar');
|
|||||||
const TextMateLanguageMode = require('./text-mate-language-mode');
|
const TextMateLanguageMode = require('./text-mate-language-mode');
|
||||||
const ScopeDescriptor = require('./scope-descriptor');
|
const ScopeDescriptor = require('./scope-descriptor');
|
||||||
|
|
||||||
const TextMateScopeSelector = require('first-mate').ScopeSelector;
|
const TextMateScopeSelector = require('second-mate').ScopeSelector;
|
||||||
const GutterContainer = require('./gutter-container');
|
const GutterContainer = require('./gutter-container');
|
||||||
let TextEditorComponent = null;
|
let TextEditorComponent = null;
|
||||||
let TextEditorElement = null;
|
let TextEditorElement = null;
|
||||||
|
@ -5,7 +5,7 @@ const TokenizedLine = require('./tokenized-line');
|
|||||||
const TokenIterator = require('./token-iterator');
|
const TokenIterator = require('./token-iterator');
|
||||||
const ScopeDescriptor = require('./scope-descriptor');
|
const ScopeDescriptor = require('./scope-descriptor');
|
||||||
const NullGrammar = require('./null-grammar');
|
const NullGrammar = require('./null-grammar');
|
||||||
const { OnigRegExp } = require('oniguruma');
|
const { OnigScanner } = require('second-mate');
|
||||||
const {
|
const {
|
||||||
toFirstMateScopeId,
|
toFirstMateScopeId,
|
||||||
fromFirstMateScopeId
|
fromFirstMateScopeId
|
||||||
@ -144,7 +144,7 @@ class TextMateLanguageMode {
|
|||||||
);
|
);
|
||||||
if (!decreaseIndentRegex) return;
|
if (!decreaseIndentRegex) return;
|
||||||
|
|
||||||
if (!decreaseIndentRegex.testSync(line)) return;
|
if (!decreaseIndentRegex.findNextMatchSync(line)) return;
|
||||||
|
|
||||||
const precedingRow = this.buffer.previousNonBlankRow(bufferRow);
|
const precedingRow = this.buffer.previousNonBlankRow(bufferRow);
|
||||||
if (precedingRow == null) return;
|
if (precedingRow == null) return;
|
||||||
@ -156,14 +156,14 @@ class TextMateLanguageMode {
|
|||||||
scopeDescriptor
|
scopeDescriptor
|
||||||
);
|
);
|
||||||
if (increaseIndentRegex) {
|
if (increaseIndentRegex) {
|
||||||
if (!increaseIndentRegex.testSync(precedingLine)) desiredIndentLevel -= 1;
|
if (!increaseIndentRegex.findNextMatchSync(precedingLine)) desiredIndentLevel -= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const decreaseNextIndentRegex = this.decreaseNextIndentRegexForScopeDescriptor(
|
const decreaseNextIndentRegex = this.decreaseNextIndentRegexForScopeDescriptor(
|
||||||
scopeDescriptor
|
scopeDescriptor
|
||||||
);
|
);
|
||||||
if (decreaseNextIndentRegex) {
|
if (decreaseNextIndentRegex) {
|
||||||
if (decreaseNextIndentRegex.testSync(precedingLine))
|
if (decreaseNextIndentRegex.findNextMatchSync(precedingLine))
|
||||||
desiredIndentLevel -= 1;
|
desiredIndentLevel -= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -203,17 +203,17 @@ class TextMateLanguageMode {
|
|||||||
if (!increaseIndentRegex) return desiredIndentLevel;
|
if (!increaseIndentRegex) return desiredIndentLevel;
|
||||||
|
|
||||||
if (!this.isRowCommented(precedingRow)) {
|
if (!this.isRowCommented(precedingRow)) {
|
||||||
if (increaseIndentRegex && increaseIndentRegex.testSync(precedingLine))
|
if (increaseIndentRegex && increaseIndentRegex.findNextMatchSync(precedingLine))
|
||||||
desiredIndentLevel += 1;
|
desiredIndentLevel += 1;
|
||||||
if (
|
if (
|
||||||
decreaseNextIndentRegex &&
|
decreaseNextIndentRegex &&
|
||||||
decreaseNextIndentRegex.testSync(precedingLine)
|
decreaseNextIndentRegex.findNextMatchSync(precedingLine)
|
||||||
)
|
)
|
||||||
desiredIndentLevel -= 1;
|
desiredIndentLevel -= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.buffer.isRowBlank(precedingRow)) {
|
if (!this.buffer.isRowBlank(precedingRow)) {
|
||||||
if (decreaseIndentRegex && decreaseIndentRegex.testSync(line))
|
if (decreaseIndentRegex && decreaseIndentRegex.findNextMatchSync(line))
|
||||||
desiredIndentLevel -= 1;
|
desiredIndentLevel -= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -812,7 +812,7 @@ class TextMateLanguageMode {
|
|||||||
if (indentation < startIndentLevel) {
|
if (indentation < startIndentLevel) {
|
||||||
break;
|
break;
|
||||||
} else if (indentation === startIndentLevel) {
|
} else if (indentation === startIndentLevel) {
|
||||||
if (foldEndRegex && foldEndRegex.searchSync(line)) foldEndRow = nextRow;
|
if (foldEndRegex && foldEndRegex.findNextMatchSync(line)) foldEndRow = nextRow;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
foldEndRow = nextRow;
|
foldEndRow = nextRow;
|
||||||
@ -848,7 +848,7 @@ class TextMateLanguageMode {
|
|||||||
regexForPattern(pattern) {
|
regexForPattern(pattern) {
|
||||||
if (pattern) {
|
if (pattern) {
|
||||||
if (!this.regexesByPattern[pattern]) {
|
if (!this.regexesByPattern[pattern]) {
|
||||||
this.regexesByPattern[pattern] = new OnigRegExp(pattern);
|
this.regexesByPattern[pattern] = new OnigScanner([pattern]);
|
||||||
}
|
}
|
||||||
return this.regexesByPattern[pattern];
|
return this.regexesByPattern[pattern];
|
||||||
}
|
}
|
||||||
|
@ -19,10 +19,11 @@
|
|||||||
}
|
}
|
||||||
StartupTime.addMarker('window:start', startWindowTime);
|
StartupTime.addMarker('window:start', startWindowTime);
|
||||||
|
|
||||||
window.onload = function() {
|
window.onload = async function() {
|
||||||
try {
|
try {
|
||||||
StartupTime.addMarker('window:onload:start');
|
StartupTime.addMarker('window:onload:start');
|
||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
|
await require('second-mate').ready
|
||||||
|
|
||||||
process.on('unhandledRejection', function(error, promise) {
|
process.on('unhandledRejection', function(error, promise) {
|
||||||
console.error(
|
console.error(
|
||||||
|
106
yarn.lock
106
yarn.lock
@ -1438,6 +1438,45 @@
|
|||||||
minimatch "^3.1.2"
|
minimatch "^3.1.2"
|
||||||
strip-json-comments "^3.1.1"
|
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":
|
"@gar/promisify@^1.0.1":
|
||||||
version "1.1.3"
|
version "1.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6"
|
resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6"
|
||||||
@ -2659,11 +2698,9 @@ braces@~3.0.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
fill-range "^7.0.1"
|
fill-range "^7.0.1"
|
||||||
|
|
||||||
"bracket-matcher@https://github.com/pulsar-edit/bracket-matcher.git#c877977":
|
"bracket-matcher@file:packages/bracket-matcher":
|
||||||
version "0.92.0"
|
version "0.92.0"
|
||||||
resolved "https://github.com/pulsar-edit/bracket-matcher.git#c877977ac7e9b7fe43c2100a1880c7ffc119280b"
|
|
||||||
dependencies:
|
dependencies:
|
||||||
first-mate "^7.4.1"
|
|
||||||
underscore-plus "1.x"
|
underscore-plus "1.x"
|
||||||
|
|
||||||
browser-stdout@1.3.1:
|
browser-stdout@1.3.1:
|
||||||
@ -3789,10 +3826,9 @@ doctrine@^3.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
esutils "^2.0.2"
|
esutils "^2.0.2"
|
||||||
|
|
||||||
document-register-element@^1.14.10:
|
"document-register-element@https://github.com/pulsar-edit/document-register-element.git#1f5868f":
|
||||||
version "1.14.10"
|
version "1.14.10"
|
||||||
resolved "https://registry.yarnpkg.com/document-register-element/-/document-register-element-1.14.10.tgz#a7bd025e6b73bd827fec2d8e90aba755e99387f5"
|
resolved "https://github.com/pulsar-edit/document-register-element.git#1f5868fba7b6c3a337c777a0396c43a080e1fad3"
|
||||||
integrity sha512-w5UA37hEIrs+9pruo2yR5UD13c4UHDlkqqjt4qurnp7QsBI9b1IOi8WXUim+aCqKBsENX3Z/cso7XMOuwJH1Yw==
|
|
||||||
|
|
||||||
dom-serializer@^2.0.0:
|
dom-serializer@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
@ -3991,7 +4027,7 @@ electron@12.2.3:
|
|||||||
"@types/node" "^14.6.2"
|
"@types/node" "^14.6.2"
|
||||||
extract-zip "^1.0.3"
|
extract-zip "^1.0.3"
|
||||||
|
|
||||||
emissary@^1, emissary@^1.0.0, emissary@^1.1.0, emissary@^1.2.0, emissary@^1.3.2:
|
emissary@^1.0.0, emissary@^1.1.0, emissary@^1.2.0, emissary@^1.3.2, emissary@^1.3.3:
|
||||||
version "1.3.3"
|
version "1.3.3"
|
||||||
resolved "https://registry.yarnpkg.com/emissary/-/emissary-1.3.3.tgz#a618d92d682b232d31111dc3625a5df661799606"
|
resolved "https://registry.yarnpkg.com/emissary/-/emissary-1.3.3.tgz#a618d92d682b232d31111dc3625a5df661799606"
|
||||||
integrity sha512-pD6FWNBSlEOzSJDCTcSGVLgNnGw5fnCvvGMdQ/TN43efeXZ/QTq8+hZoK3OOEXPRNjMmSJmeOnEJh+bWT5O8rQ==
|
integrity sha512-pD6FWNBSlEOzSJDCTcSGVLgNnGw5fnCvvGMdQ/TN43efeXZ/QTq8+hZoK3OOEXPRNjMmSJmeOnEJh+bWT5O8rQ==
|
||||||
@ -4390,7 +4426,7 @@ etch@^0.12.2, etch@^0.12.6:
|
|||||||
resolved "https://registry.yarnpkg.com/etch/-/etch-0.12.8.tgz#c24bc9bd3a6148f62204ce8643d2e899b9ecb9de"
|
resolved "https://registry.yarnpkg.com/etch/-/etch-0.12.8.tgz#c24bc9bd3a6148f62204ce8643d2e899b9ecb9de"
|
||||||
integrity sha512-dFLRe4wLroVtwzyy1vGlE3BSDZHiL0kZME5XgNGzZIULcYTvVno8vbiIleAesoKJmwWaxDTzG+4eppg2zk14JQ==
|
integrity sha512-dFLRe4wLroVtwzyy1vGlE3BSDZHiL0kZME5XgNGzZIULcYTvVno8vbiIleAesoKJmwWaxDTzG+4eppg2zk14JQ==
|
||||||
|
|
||||||
event-kit@2.5.3, event-kit@^2.0.0, event-kit@^2.1.0, event-kit@^2.2.0, event-kit@^2.4.0, event-kit@^2.5.3:
|
event-kit@2.5.3, event-kit@^2.0.0, event-kit@^2.1.0, event-kit@^2.4.0, event-kit@^2.5.3:
|
||||||
version "2.5.3"
|
version "2.5.3"
|
||||||
resolved "https://registry.yarnpkg.com/event-kit/-/event-kit-2.5.3.tgz#d47e4bc116ec0aacd00263791fa1a55eb5e79ba1"
|
resolved "https://registry.yarnpkg.com/event-kit/-/event-kit-2.5.3.tgz#d47e4bc116ec0aacd00263791fa1a55eb5e79ba1"
|
||||||
integrity sha512-b7Qi1JNzY4BfAYfnIRanLk0DOD1gdkWHT4GISIn8Q2tAf3LpU8SP2CMwWaq40imYoKWbtN4ZhbSRxvsnikooZQ==
|
integrity sha512-b7Qi1JNzY4BfAYfnIRanLk0DOD1gdkWHT4GISIn8Q2tAf3LpU8SP2CMwWaq40imYoKWbtN4ZhbSRxvsnikooZQ==
|
||||||
@ -4605,19 +4641,6 @@ find-up@^5.0.0:
|
|||||||
locate-path "^6.0.0"
|
locate-path "^6.0.0"
|
||||||
path-exists "^4.0.0"
|
path-exists "^4.0.0"
|
||||||
|
|
||||||
first-mate@7.4.3, first-mate@^7.4.1:
|
|
||||||
version "7.4.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/first-mate/-/first-mate-7.4.3.tgz#058b9b6d2f43e38a5f0952669338cff2c46ae2dd"
|
|
||||||
integrity sha512-PtZUpaPmcV5KV4Rw5TfwczEnExN+X1o3Q/G82E4iRJ0tW91fm3Yi7pa5t4cBH8r3D6EyoBKvfpG2jKE+TZ0/nw==
|
|
||||||
dependencies:
|
|
||||||
emissary "^1"
|
|
||||||
event-kit "^2.2.0"
|
|
||||||
fs-plus "^3.0.0"
|
|
||||||
grim "^2.0.1"
|
|
||||||
oniguruma "^7.2.3"
|
|
||||||
season "^6.0.2"
|
|
||||||
underscore-plus "^1"
|
|
||||||
|
|
||||||
flat-cache@^3.0.4:
|
flat-cache@^3.0.4:
|
||||||
version "3.0.4"
|
version "3.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11"
|
resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11"
|
||||||
@ -5194,7 +5217,7 @@ graphql@14.5.8:
|
|||||||
dependencies:
|
dependencies:
|
||||||
iterall "^1.2.2"
|
iterall "^1.2.2"
|
||||||
|
|
||||||
grim@2.0.3, grim@^2.0.1, grim@^2.0.2:
|
grim@2.0.3, grim@^2.0.1, grim@^2.0.2, grim@^2.0.3:
|
||||||
version "2.0.3"
|
version "2.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/grim/-/grim-2.0.3.tgz#66e575efc4577981d959da0764926b4aaded4b0d"
|
resolved "https://registry.yarnpkg.com/grim/-/grim-2.0.3.tgz#66e575efc4577981d959da0764926b4aaded4b0d"
|
||||||
integrity sha512-FM20Ump11qYLK9k9DbL8yzVpy+YBieya1JG15OeH8s+KbHq8kL4SdwRtURwIUHniSxb24EoBUpwKfFjGNVi4/Q==
|
integrity sha512-FM20Ump11qYLK9k9DbL8yzVpy+YBieya1JG15OeH8s+KbHq8kL4SdwRtURwIUHniSxb24EoBUpwKfFjGNVi4/Q==
|
||||||
@ -5534,6 +5557,16 @@ internal-slot@^1.0.3:
|
|||||||
has "^1.0.3"
|
has "^1.0.3"
|
||||||
side-channel "^1.0.4"
|
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:
|
invert-kv@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6"
|
resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6"
|
||||||
@ -7422,13 +7455,6 @@ onetime@^5.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
mimic-fn "^2.1.0"
|
mimic-fn "^2.1.0"
|
||||||
|
|
||||||
oniguruma@^7.2.3:
|
|
||||||
version "7.2.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/oniguruma/-/oniguruma-7.2.3.tgz#e0b0b415302de8cdd6564e57a1a822ac0ab57012"
|
|
||||||
integrity sha512-PZZcE0yfg8Q1IvaJImh21RUTHl8ep0zwwyoE912KqlWVrsGByjjj29sdACcD1BFyX2bLkfuOJeP+POzAGVWtbA==
|
|
||||||
dependencies:
|
|
||||||
nan "^2.14.0"
|
|
||||||
|
|
||||||
"open-on-github@file:packages/open-on-github":
|
"open-on-github@file:packages/open-on-github":
|
||||||
version "1.3.2"
|
version "1.3.2"
|
||||||
|
|
||||||
@ -8503,6 +8529,18 @@ season@^6.0.2:
|
|||||||
fs-plus "^3.0.0"
|
fs-plus "^3.0.0"
|
||||||
yargs "^3.23.0"
|
yargs "^3.23.0"
|
||||||
|
|
||||||
|
"second-mate@https://github.com/pulsar-edit/second-mate.git#14aa7bd":
|
||||||
|
version "8.0.0"
|
||||||
|
resolved "https://github.com/pulsar-edit/second-mate.git#14aa7bd94b90c47aa99f000394301b9573b8898b"
|
||||||
|
dependencies:
|
||||||
|
emissary "^1.3.3"
|
||||||
|
event-kit "^2.5.3"
|
||||||
|
fs-plus "^3.0.0"
|
||||||
|
grim "^2.0.3"
|
||||||
|
season "^6.0.2"
|
||||||
|
underscore-plus "^1"
|
||||||
|
vscode-oniguruma "^1.7.0"
|
||||||
|
|
||||||
selector-kit@^0.1:
|
selector-kit@^0.1:
|
||||||
version "0.1.0"
|
version "0.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/selector-kit/-/selector-kit-0.1.0.tgz#304338fceccea35ec28ffaddb792ab7715633e6f"
|
resolved "https://registry.yarnpkg.com/selector-kit/-/selector-kit-0.1.0.tgz#304338fceccea35ec28ffaddb792ab7715633e6f"
|
||||||
@ -9517,6 +9555,11 @@ truncate-utf8-bytes@^1.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
utf8-byte-length "^1.0.1"
|
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:
|
tunnel-agent@^0.6.0:
|
||||||
version "0.6.0"
|
version "0.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd"
|
resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd"
|
||||||
@ -9810,6 +9853,11 @@ verror@^1.10.0:
|
|||||||
core-util-is "1.0.2"
|
core-util-is "1.0.2"
|
||||||
extsprintf "^1.2.0"
|
extsprintf "^1.2.0"
|
||||||
|
|
||||||
|
vscode-oniguruma@^1.7.0:
|
||||||
|
version "1.7.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz#439bfad8fe71abd7798338d1cd3dc53a8beea94b"
|
||||||
|
integrity sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==
|
||||||
|
|
||||||
vscode-ripgrep@1.9.0:
|
vscode-ripgrep@1.9.0:
|
||||||
version "1.9.0"
|
version "1.9.0"
|
||||||
resolved "https://registry.yarnpkg.com/vscode-ripgrep/-/vscode-ripgrep-1.9.0.tgz#d6cdea4d290f3c2919472cdcfe2440d5fb1f99db"
|
resolved "https://registry.yarnpkg.com/vscode-ripgrep/-/vscode-ripgrep-1.9.0.tgz#d6cdea4d290f3c2919472cdcfe2440d5fb1f99db"
|
||||||
|
Loading…
Reference in New Issue
Block a user