├── .gitattributes
├── .gitignore
├── .npmignore
├── spec
├── .eslintrc
├── snippet-solver-spec.js
└── selector-solver-spec.js
├── test.html
├── keymaps
└── selector-to-tag.cson
├── lib
├── utils.js
├── main.js
└── selector-solver.js
├── .eslintrc
├── CHANGELOG.md
├── package.json
├── .github
└── workflows
│ └── tests.yml
├── LICENSE.md
└── README.md
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text eol=lf
2 | *.gif binary
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /*.log
2 | node_modules
3 | /.Trash-1000/
4 | .DS_Store
5 | Thumbs.db
6 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | /node_modules/
2 | /.Trash-1000/
3 | .DS_Store
4 | Thumbs.db
5 | *.log
6 |
--------------------------------------------------------------------------------
/spec/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "../.eslintrc"
4 | ],
5 |
6 | "env": {
7 | "jasmine": true
8 | },
9 |
10 | "globals": {
11 | "waitsForPromise": false
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/keymaps/selector-to-tag.cson:
--------------------------------------------------------------------------------
1 | # Keybindings require three things to be fully defined: A selector that is
2 | # matched against the focused element, the keystroke and the command to
3 | # execute.
4 | #
5 | # Below is a basic keybinding which registers on all platforms by applying to
6 | # the root workspace element.
7 |
8 | # For more detailed documentation see
9 | # https://atom.io/docs/latest/advanced/keymaps
10 | 'atom-workspace atom-text-editor:not([mini])':
11 | 'tab': 'selector-to-tag:solve-selector'
12 |
--------------------------------------------------------------------------------
/lib/utils.js:
--------------------------------------------------------------------------------
1 | // String extensions
2 |
3 | if (!String.prototype.insertAt) {
4 | String.prototype.insertAt = function (index, string) {
5 | return this.substr(0, index) + string + this.substr(index);
6 | };
7 | }
8 |
9 | // RegExp extensions
10 |
11 | if (!RegExp.prototype.getAllMatches) {
12 | RegExp.prototype.getAllMatches = function (string) {
13 | var result = [];
14 | var match;
15 | do {
16 | match = this.exec(string);
17 | if (match) {
18 | result.push(match[0]);
19 | }
20 | } while (match);
21 |
22 | return result;
23 | };
24 | }
25 |
26 | module.exports = {};
27 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "eslint:recommended",
4 | "plugin:prettier/recommended"
5 | ],
6 |
7 | "parserOptions": {
8 | "ecmaVersion": 6,
9 | "sourceType": "module",
10 | "ecmaFeatures": {
11 | "impliedStrict": true
12 | }
13 | },
14 |
15 | "env": {
16 | "node": true,
17 | "browser": true,
18 | "es6": true
19 | },
20 |
21 | "globals": {
22 | "atom": false,
23 | "com": false
24 | },
25 |
26 | "rules": {
27 | "eqeqeq": "error",
28 | "block-scoped-var": "error",
29 | "no-mixed-spaces-and-tabs": ["error", "smart-tabs"],
30 | "semi": "error",
31 | "no-console": ["error", {"allow": ["error", "info"]}],
32 | "prettier/prettier": ["error", {
33 | "useTabs": true
34 | }]
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## 0.4.1
2 |
3 | - Fixed: prevent expending to div from nothing
4 |
5 | ## 0.4.0
6 |
7 | - Added: now you can use only the id or class to expand to a div with that class and/or id
8 | - Fixed: not expanding if tag ends in dot (for example "div.")
9 |
10 | ## 0.3.0
11 |
12 | - Added option to expand block tags to multiple lines. (thanks @dsandstrom)
13 |
14 | ## 0.2.1
15 |
16 | - Fixed bug where starting a tag with dash (`-`) will crash the package
17 | - Removed menu entry from Packages
18 |
19 | ## 0.2.0
20 |
21 | - Added option to close self-closing tags
22 |
23 | ## 0.1.2
24 |
25 | - Added support for dashes inside selectors
26 | - Fixed deprecated code
27 |
28 | ## 0.1.1
29 |
30 | - Small cosmetic to the docs
31 |
32 | ## 0.1.0
33 |
34 | - Initial release
35 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "selector-to-tag",
3 | "main": "./lib/main",
4 | "version": "0.6.0",
5 | "description": "Create HTML tag elements using CSS selectors in HTML files",
6 | "repository": "https://github.com/surdu/selector-to-tag",
7 | "license": "MIT",
8 | "engines": {
9 | "atom": ">0.50.0"
10 | },
11 | "scripts": {
12 | "test": "npx eslint **/*.js"
13 | },
14 | "keywords": [
15 | "html",
16 | "css",
17 | "web development",
18 | "solve tags"
19 | ],
20 | "devDependencies": {
21 | "eslint": "^5.0.1",
22 | "eslint-config-prettier": "^6.12.0",
23 | "eslint-plugin-prettier": "^3.1.4",
24 | "prettier": "^2.1.2"
25 | },
26 | "consumedServices": {
27 | "snippets": {
28 | "versions": {
29 | "0.1.0": "consumeSnippets"
30 | }
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/.github/workflows/tests.yml:
--------------------------------------------------------------------------------
1 | name: Tests
2 |
3 | on:
4 | push:
5 | branches: [ master ]
6 | pull_request:
7 | branches: [ master ]
8 |
9 | env:
10 | CI: true
11 |
12 | jobs:
13 | Test:
14 | strategy:
15 | matrix:
16 | os: [ubuntu-latest, macos-latest, windows-latest]
17 | channel: [stable, beta]
18 | runs-on: ${{ matrix.os }}
19 | steps:
20 | - uses: actions/checkout@v1
21 |
22 | - uses: UziTech/action-setup-atom@v1
23 | with:
24 | channel: ${{ matrix.channel }}
25 |
26 | - name: Atom version
27 | run: atom -v
28 |
29 | - name: APM version
30 | run: apm -v
31 |
32 | - name: Install dependencies
33 | run: apm install
34 |
35 | - name: Run linter & spellcheck
36 | run: npm test
37 |
38 | - name: Run tests
39 | run: atom --test spec
40 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright (C) 2015 Nicolae Surdu
2 | All rights reserved.
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining
5 | a copy of this software and associated documentation files (the
6 | "Software"), to deal in the Software without restriction, including
7 | without limitation the rights to use, copy, modify, merge, publish,
8 | distribute, sublicense, and/or sell copies of the Software, and to
9 | permit persons to whom the Software is furnished to do so, subject to
10 | the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be
13 | included in all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/lib/main.js:
--------------------------------------------------------------------------------
1 | const SelectorSolver = require("./selector-solver");
2 |
3 | module.exports = {
4 | config: {
5 | solveTagsOnly: {
6 | type: "boolean",
7 | default: true,
8 | title: "Solve Plain Tags",
9 | description:
10 | "Solve to tag even if the selector is only the tag with no id and/or class.",
11 | },
12 |
13 | activeForFileTypes: {
14 | type: "array",
15 | default: ["htm", "html", "kit", "shtml", "tmpl", "tpl", "xhtml"],
16 | title: "File Extensions",
17 | description: "Active for these file extensions",
18 | items: {
19 | type: "string",
20 | },
21 | },
22 |
23 | activeForGrammar: {
24 | type: "boolean",
25 | default: false,
26 | title: "Only On HTML Grammar",
27 | description:
28 | "Active only if HTML grammar is selected. The `File Extensions` option will be ignored if this is enabled.",
29 | },
30 |
31 | closeSelfclosingTags: {
32 | type: "boolean",
33 | default: false,
34 | title: "Close Self-Closing Tags",
35 | description: "Add a backslash before the end of self-closing tags",
36 | },
37 |
38 | expandBlockTags: {
39 | type: "boolean",
40 | default: false,
41 | title: "Expand Block Tags To Multiple Lines",
42 | description: "Put end tag on a new line",
43 | },
44 |
45 | blockTags: {
46 | type: "array",
47 | default: [
48 | "address",
49 | "article",
50 | "aside",
51 | "audio",
52 | "blockquote",
53 | "canvas",
54 | "dd",
55 | "div",
56 | "dl",
57 | "fieldset",
58 | "figcaption",
59 | "figure",
60 | "footer",
61 | "form",
62 | "header",
63 | "hgroup",
64 | "hr",
65 | "main",
66 | "nav",
67 | "noscript",
68 | "ol",
69 | "output",
70 | "p",
71 | "pre",
72 | "section",
73 | "table",
74 | "tfoot",
75 | "ul",
76 | "video",
77 | ],
78 | title: "Block-Level Elements",
79 | description: "HTML tags that will be expanded to multiple lines.",
80 | },
81 | },
82 |
83 | availableAPI: {},
84 |
85 | activate() {
86 | new SelectorSolver(this.availableAPI);
87 | },
88 |
89 | consumeSnippets(snippetsAPI) {
90 | this.availableAPI.snippetsAPI = snippetsAPI;
91 | },
92 | };
93 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Selector to Tag
2 |
3 | [](https://github.com/surdu/selector-to-tag/actions)
4 | [](https://ko-fi.com/surdu)
5 |
6 | Selector to Tag is an [Atom editor](https://atom.io/) package that allows you to create HTML tag elements using CSS selectors in HTML files.
7 |
8 | Just type a CSS selector and press TAB :
9 |
10 | 
11 |
12 | ## Supported selectors
13 |
14 | For now the following selectors are possible:
15 |
16 | Selector | Output
17 | ------------------|------
18 | `foo` | `` (when `Solve Plain Tags` option is enabled (default `true`))
19 | `foo#bar` | ``
20 | `#foo` | ``
21 | `.foo` | ``
22 | `foo.bar` | ``
23 | `foo.bar.baz` | ``
24 | `foo#bar.baz` | ``
25 | `foo#bar.baz.qux` | ``
26 |
27 | ## Options
28 |
29 | - **File Extensions** - comma separated file extensions in which this package should be active *(Default: "htm, html, kit, shtml, tmpl, tpl, xhtml")*
30 |
31 | - **Only On HTML Grammar** - extension solves tags in any file that have the HTML grammar active. `File extensions` option will be ignored if this is enabled *(Default: false)*
32 |
33 | - **Solve Plain Tags** - this will indicate if this package should also solve to tags when there is no id or class specified in the selector. *(Default: true)*
34 |
35 | - **Close Self-Closing Tags** - Add a backslash before the end of self-closing tags. For example `` will be solved to `` *(Default: false)*
36 |
37 | - **Expand Block Tags To Multiple Lines** - Puts the cursor and end tag on new lines. *(Default: false)*
38 |
39 | - **Block-Level Elements** - If "Expand block tags to multiple lines" is checked, these tags will count as block tags. *(Default: address, article, aside, audio, blockquote, canvas, dd, div, dl, fieldset, figcaption, figure, footer, form, header, hgroup, hr, main, nav, noscript, ol, output, p, pre, section, table, tfoot, ul, video)*
40 |
41 | ## Support
42 |
43 | If you have any sugestions for other selectors or sugestions in general, please submit an issue on GitHub.
44 |
--------------------------------------------------------------------------------
/spec/snippet-solver-spec.js:
--------------------------------------------------------------------------------
1 | describe("Snippet solver", function () {
2 | var editor;
3 | var editorView;
4 |
5 | beforeEach(function () {
6 | waitsForPromise(function () {
7 | return atom.workspace.open("test.html");
8 | });
9 |
10 | waitsForPromise(function () {
11 | const selectorToTag = atom.packages.activatePackage("selector-to-tag");
12 | const snippets = atom.packages.activatePackage("snippets");
13 | return Promise.all([selectorToTag, snippets]);
14 | });
15 |
16 | atom.config.unset("selector-to-tag.closeSelfclosingTags");
17 | atom.config.unset("selector-to-tag.expandBlockTags");
18 |
19 | runs(function () {
20 | editor = atom.workspace.getActiveTextEditor();
21 | editor.setText("");
22 | editorView = atom.views.getView(editor);
23 | });
24 | });
25 |
26 | it("should solve inline tags", function () {
27 | editor.setText("link");
28 | editor.moveToEndOfLine();
29 |
30 | atom.commands.dispatch(editorView, "selector-to-tag:solve-selector");
31 |
32 | expect(editor.getText()).toBe("");
33 |
34 | expect(editor.getCursorScreenPosition()).toEqual([0, 5]);
35 | });
36 |
37 | it("should solve block tags", function () {
38 | editor.setText("div");
39 | editor.moveToEndOfLine();
40 |
41 | atom.commands.dispatch(editorView, "selector-to-tag:solve-selector");
42 |
43 | expect(editor.getText()).toBe("");
44 | expect(editor.getCursorScreenPosition()).toEqual([0, 4]);
45 |
46 | atom.commands.dispatch(editorView, "snippets:next-tab-stop");
47 |
48 | expect(editor.getCursorScreenPosition()).toEqual([0, 5]);
49 | });
50 |
51 | it("should solve expanded block tags", function () {
52 | atom.config.set("selector-to-tag.expandBlockTags", true);
53 |
54 | editor.setText("div");
55 | editor.moveToEndOfLine();
56 |
57 | atom.commands.dispatch(editorView, "selector-to-tag:solve-selector");
58 |
59 | expect(editor.getText()).toBe("\n \n
");
60 | expect(editor.getCursorScreenPosition()).toEqual([0, 4]);
61 |
62 | atom.commands.dispatch(editorView, "snippets:next-tab-stop");
63 |
64 | expect(editor.getCursorScreenPosition()).toEqual([1, 2]);
65 | });
66 |
67 | it("should respect indent style", function () {
68 | atom.config.set("selector-to-tag.expandBlockTags", true);
69 | atom.config.set("editor.tabType", "hard");
70 | atom.config.set("editor.tabLength", 4);
71 |
72 | editor.setText("div");
73 | editor.moveToEndOfLine();
74 |
75 | atom.commands.dispatch(editorView, "selector-to-tag:solve-selector");
76 |
77 | expect(editor.getText()).toBe("\n\t\n
");
78 | expect(editor.getCursorScreenPosition()).toEqual([0, 4]);
79 |
80 | atom.commands.dispatch(editorView, "snippets:next-tab-stop");
81 |
82 | expect(editor.getCursorScreenPosition()).toEqual([1, 4]);
83 | });
84 | });
85 |
--------------------------------------------------------------------------------
/spec/selector-solver-spec.js:
--------------------------------------------------------------------------------
1 | const SelectorSolver = require("../lib/selector-solver");
2 |
3 | describe("selector-to-tab", function () {
4 | var editor;
5 | var editorView;
6 |
7 | function testSelector(selector, expected, expectedCurPos) {
8 | editor.setText(selector);
9 | editor.moveToEndOfLine();
10 |
11 | atom.commands.dispatch(editorView, "selector-to-tag:solve-selector");
12 |
13 | expect(editor.getText()).toBe(expected);
14 |
15 | if (expectedCurPos) {
16 | expect(editor.getCursorScreenPosition()).toEqual(expectedCurPos);
17 | }
18 | }
19 |
20 | beforeEach(function () {
21 | waitsForPromise(function () {
22 | return atom.workspace.open("test.html");
23 | });
24 |
25 | waitsForPromise(function () {
26 | const selectorToTag = atom.packages.activatePackage("selector-to-tag");
27 | const snippets = atom.packages.deactivatePackage("snippets");
28 | return Promise.all([selectorToTag, snippets]);
29 | });
30 |
31 | runs(function () {
32 | editor = atom.workspace.getActiveTextEditor();
33 | editor.setText("");
34 | editorView = atom.views.getView(editor);
35 | });
36 | });
37 |
38 | describe("HTML detector", function () {
39 | var solver;
40 |
41 | beforeEach(function () {
42 | solver = new SelectorSolver();
43 | });
44 |
45 | it("should handle files with no extension", function () {
46 | expect(solver.isHTML("untitled")).toBe(false);
47 | });
48 |
49 | it("should detect HTML files", function () {
50 | expect(solver.isHTML("test.HTML")).toBe(true);
51 | });
52 | });
53 |
54 | describe("Tag solver", function () {
55 | beforeEach(function () {
56 | atom.config.unset("selector-to-tag.closeSelfclosingTags");
57 | atom.config.unset("selector-to-tag.expandBlockTags");
58 | });
59 |
60 | it("should solve block tags", function () {
61 | testSelector("div", "", [0, 5]);
62 | });
63 |
64 | it("should solve inline tags", function () {
65 | testSelector("link", "", [0, 5]);
66 | });
67 |
68 | it("should solve unknown tags", function () {
69 | testSelector("ceva", "", [0, 6]);
70 | });
71 |
72 | it("should solve tag with id", function () {
73 | testSelector("div#mama", '', [0, 15]);
74 | });
75 |
76 | it("should solve tag with id and class", function () {
77 | testSelector("div#mama.tata", '', [
78 | 0,
79 | 28,
80 | ]);
81 | });
82 |
83 | it("should solve tag with id and multiple classes", function () {
84 | testSelector(
85 | "div#mama.tata.sora",
86 | '',
87 | [0, 33]
88 | );
89 | });
90 |
91 | it("should solve selectors containing - and _", function () {
92 | testSelector(
93 | "some-tag_1#id-1_2.class_1.class-2",
94 | '',
95 | [0, 48]
96 | );
97 | });
98 |
99 | it("should solve self-closing tags", function () {
100 | atom.config.set("selector-to-tag.closeSelfclosingTags", true);
101 | testSelector("link", "", [0, 5]);
102 | });
103 |
104 | it("should expand block tags to multiple lines", function () {
105 | atom.config.set("selector-to-tag.expandBlockTags", true);
106 | testSelector("div#mama", '\n\n
', [1, 0]);
107 | });
108 |
109 | it("shouldn't expand tag if class or id is not specified", function () {
110 | testSelector("div#", "div#");
111 | testSelector("div.", "div.");
112 | });
113 |
114 | it("should solve to div if only class or id specified", function () {
115 | testSelector("#mama", '', [0, 15]);
116 | testSelector(".tata", '', [0, 18]);
117 | });
118 |
119 | it("should not solve to div if nothing is specified", function () {
120 | testSelector("", "");
121 | });
122 |
123 | it("should not create tags from invalid strings", function () {
124 | testSelector("42", "42");
125 | });
126 |
127 | it("should remember tag's case", function () {
128 | testSelector("View", "");
129 | });
130 |
131 | it("should not solve tags if inside another tag", function () {
132 | editor.setText('');
133 | editor.setCursorScreenPosition([0, 19]);
134 | atom.commands.dispatch(editorView, "selector-to-tag:solve-selector");
135 |
136 | expect(editor.getText()).toBe(
137 | ''
138 | );
139 | });
140 |
141 | it("should solve tags with a namespace prefix", function () {
142 | testSelector("ns:View", "");
143 | });
144 | });
145 | });
146 |
--------------------------------------------------------------------------------
/lib/selector-solver.js:
--------------------------------------------------------------------------------
1 | require("./utils");
2 |
3 | function removeFirstChar(string) {
4 | return string.substring(1);
5 | }
6 |
7 | class SelectorSolver {
8 | constructor(availableAPI) {
9 | this.defaultFileTypes = [
10 | "htm",
11 | "html",
12 | "kit",
13 | "shtml",
14 | "tmpl",
15 | "tpl",
16 | "xhtml",
17 | ];
18 | this.availableAPI = availableAPI;
19 |
20 | this.selfClosingTags = [
21 | "area",
22 | "br",
23 | "col",
24 | "embed",
25 | "hr",
26 | "img",
27 | "input",
28 | "link",
29 | "meta",
30 | "param",
31 | ];
32 |
33 | atom.commands.add("atom-text-editor", {
34 | "selector-to-tag:solve-selector": this.handleCommand.bind(this),
35 | });
36 | }
37 |
38 | isHTML(filename, grammar) {
39 | if (atom.config.get("selector-to-tag.activeForGrammar")) {
40 | // check for grammar
41 | return grammar === ".text.html.basic";
42 | } else {
43 | var extension = filename.split(".");
44 | if (extension.length > 1) {
45 | extension = extension[extension.length - 1].toLowerCase();
46 | return (
47 | (
48 | atom.config.get("selector-to-tag.activeForFileTypes") ||
49 | this.defaultFileTypes
50 | ).indexOf(extension) !== -1
51 | );
52 | }
53 | }
54 | return false;
55 | }
56 |
57 | solveSelector(
58 | selector,
59 | solveTagsOnly,
60 | closeSelfclosingTags,
61 | expandBlockTags,
62 | blockTags,
63 | editor
64 | ) {
65 | if (typeof blockTags === "undefined") {
66 | blockTags = [];
67 | }
68 |
69 | // it shouldn't expand tag if class or id is not specified
70 | if (selector.substr(-1) === "#" || selector.substr(-1) === ".") {
71 | return null;
72 | }
73 |
74 | var tag = (/^[a-zA-Z][\w-:]*/.exec(selector) || [])[0];
75 | const id = (/#[\w-]+/.exec(selector) || []).map(removeFirstChar)[0];
76 | const classes = /(\.[\w-]+)/g.getAllMatches(selector).map(removeFirstChar);
77 |
78 | if ((solveTagsOnly && tag) || classes.length > 0 || id) {
79 | let string, snippet, isSelfClosing, isBlock;
80 |
81 | if (!tag) {
82 | tag = "div";
83 | }
84 |
85 | isSelfClosing = this.selfClosingTags.indexOf(tag) !== -1;
86 | isBlock = blockTags.indexOf(tag) !== -1;
87 |
88 | var element = document.createElementNS(
89 | "http://www.w3.org/1999/xhtml",
90 | tag
91 | );
92 |
93 | if (id) {
94 | element.id = id;
95 | }
96 |
97 | if (classes.length > 0) {
98 | element.className = classes.join(" ");
99 | }
100 |
101 | string = element.outerHTML;
102 | snippet = string;
103 |
104 | if (isSelfClosing) {
105 | if (closeSelfclosingTags) {
106 | string = string.insertAt(string.length - 1, "/");
107 | }
108 | snippet = snippet.insertAt(snippet.length - 1, "$1");
109 | }
110 |
111 | if (isBlock) {
112 | if (expandBlockTags) {
113 | string = string.insertAt(string.indexOf(""), "\n\n");
114 | snippet = snippet.insertAt(
115 | snippet.indexOf(""),
116 | `\n${editor.buildIndentString(1)}$2\n`
117 | );
118 | } else {
119 | snippet = snippet.insertAt(snippet.indexOf(""), "$2");
120 | }
121 |
122 | snippet = snippet.insertAt(snippet.indexOf(">"), "$1");
123 | }
124 |
125 | return { element, string, isSelfClosing, isBlock, snippet };
126 | }
127 |
128 | return null;
129 | }
130 |
131 | insertSolvedText(solvedElement, selectorRange, editor) {
132 | if (this.availableAPI.snippetsAPI) {
133 | const snippetsAPI = this.availableAPI.snippetsAPI;
134 | editor.setTextInBufferRange(selectorRange, "");
135 | snippetsAPI.insertSnippet(solvedElement.snippet, editor);
136 | } else {
137 | editor.setTextInBufferRange(selectorRange, solvedElement.string);
138 | }
139 | }
140 |
141 | handleCommand(event) {
142 | const editor = event.currentTarget.getModel();
143 |
144 | if (!this.isHTML(editor.getTitle(), editor.getRootScopeDescriptor())) {
145 | event.abortKeyBinding();
146 | return;
147 | }
148 |
149 | const cursorPosition = editor.getCursorBufferPosition();
150 | const textBeforeCursor = editor.getTextInBufferRange([
151 | [0, 0],
152 | cursorPosition,
153 | ]);
154 |
155 | const lastClosingBracketIndex = textBeforeCursor.lastIndexOf(">");
156 | const lastOpeningBracketIndex = textBeforeCursor.lastIndexOf("<");
157 | const isInsideTag = lastOpeningBracketIndex > lastClosingBracketIndex;
158 |
159 | if (isInsideTag) {
160 | event.abortKeyBinding();
161 | return;
162 | }
163 |
164 | const match = /\s*([\w.#-_:]*)$/.exec(textBeforeCursor);
165 |
166 | if (!match || match.length < 1) {
167 | event.abortKeyBinding();
168 | return;
169 | }
170 |
171 | const expandBlockTags = atom.config.get("selector-to-tag.expandBlockTags");
172 | const solveTagsOnly = atom.config.get("selector-to-tag.solveTagsOnly");
173 | const closeSelfclosingTags = atom.config.get(
174 | "selector-to-tag.closeSelfclosingTags"
175 | );
176 | const blockTags = atom.config.get("selector-to-tag.blockTags");
177 |
178 | const selector = match[1];
179 | const solvedElement = this.solveSelector(
180 | selector,
181 | solveTagsOnly,
182 | closeSelfclosingTags,
183 | expandBlockTags,
184 | blockTags,
185 | editor
186 | );
187 |
188 | if (!solvedElement) {
189 | event.abortKeyBinding();
190 | return;
191 | }
192 |
193 | const selectorRange = [
194 | [cursorPosition.row, cursorPosition.column - selector.length],
195 | cursorPosition,
196 | ];
197 |
198 | if (expandBlockTags && solvedElement.isBlock) {
199 | editor.transact(() => {
200 | this.insertSolvedText(solvedElement, selectorRange, editor);
201 |
202 | if (!this.availableAPI.snippetsAPI) {
203 | editor.selectUp();
204 | editor.getLastSelection().autoIndentSelectedRows();
205 | editor.clearSelections();
206 | editor.moveToEndOfLine();
207 | }
208 | });
209 | } else {
210 | this.insertSolvedText(solvedElement, selectorRange, editor);
211 |
212 | if (!this.availableAPI.snippetsAPI) {
213 | const newCursorPosition = editor.getCursorScreenPosition();
214 |
215 | if (solvedElement.isSelfClosing) {
216 | if (atom.config.get("selector-to-tag.closeSelfclosingTags")) {
217 | newCursorPosition.column -= 2;
218 | } else {
219 | newCursorPosition.column -= 1;
220 | }
221 |
222 | editor.setCursorScreenPosition(newCursorPosition);
223 | } else {
224 | newCursorPosition.column -= solvedElement.element.tagName.length + 3;
225 | editor.setCursorScreenPosition(newCursorPosition);
226 | }
227 | }
228 | }
229 | }
230 | }
231 |
232 | module.exports = SelectorSolver;
233 |
--------------------------------------------------------------------------------