=
246 | dirname => () => {
247 | const mocha = new Mocha({
248 | ui: 'tdd',
249 | timeout: 1000000,
250 | color: true,
251 | bail: true
252 | });
253 |
254 | return new Promise((resolve, reject) => {
255 | const fileNames = fs
256 | .readdirSync(dirname)
257 | .filter(fileName => fileName.endsWith('.test.js'));
258 | for (const fileName of fileNames) {
259 | mocha.addFile(path.join(dirname, fileName));
260 | }
261 | try {
262 | mocha.run(failures => {
263 | if (failures > 0) {
264 | reject(new Error(`${failures} tests failed.`));
265 | } else {
266 | resolve();
267 | }
268 | });
269 | } catch (err) {
270 | reject(err);
271 | }
272 | });
273 | };
274 |
--------------------------------------------------------------------------------
/packages/extension-test/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig-noncomposite-base",
3 | "compilerOptions": {
4 | "outDir": "dist",
5 | "rootDir": "src",
6 | "types": ["node"],
7 | "noUnusedLocals": false,
8 | "noUnusedParameters": false,
9 | "allowUnreachableCode": true,
10 | "noImplicitAny": false,
11 | "strict": false,
12 | "strictNullChecks": false,
13 | "strictPropertyInitialization": false
14 | },
15 | "include": ["src"],
16 | "references": []
17 | }
18 |
--------------------------------------------------------------------------------
/packages/extension/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "auto-rename-tag",
3 | "version": "0.1.10",
4 | "lockfileVersion": 2,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "auto-rename-tag",
9 | "version": "0.1.10",
10 | "license": "MIT",
11 | "dependencies": {
12 | "source-map-support": "^0.5.21",
13 | "vscode-languageclient": "^7.0.0"
14 | },
15 | "devDependencies": {
16 | "@types/vscode": "^1.65.0",
17 | "typescript": "^4.6.3"
18 | },
19 | "engines": {
20 | "vscode": "^1.41.1"
21 | }
22 | },
23 | "node_modules/@types/vscode": {
24 | "version": "1.65.0",
25 | "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.65.0.tgz",
26 | "integrity": "sha512-wQhExnh2nEzpjDMSKhUvnNmz3ucpd3E+R7wJkOhBNK3No6fG3VUdmVmMOKD0A8NDZDDDiQcLNxe3oGmX5SjJ5w==",
27 | "dev": true
28 | },
29 | "node_modules/balanced-match": {
30 | "version": "1.0.2",
31 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
32 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
33 | },
34 | "node_modules/brace-expansion": {
35 | "version": "1.1.11",
36 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
37 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
38 | "dependencies": {
39 | "balanced-match": "^1.0.0",
40 | "concat-map": "0.0.1"
41 | }
42 | },
43 | "node_modules/buffer-from": {
44 | "version": "1.1.1",
45 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
46 | "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A=="
47 | },
48 | "node_modules/concat-map": {
49 | "version": "0.0.1",
50 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
51 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
52 | },
53 | "node_modules/lru-cache": {
54 | "version": "6.0.0",
55 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
56 | "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
57 | "dependencies": {
58 | "yallist": "^4.0.0"
59 | },
60 | "engines": {
61 | "node": ">=10"
62 | }
63 | },
64 | "node_modules/minimatch": {
65 | "version": "3.0.5",
66 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.5.tgz",
67 | "integrity": "sha512-tUpxzX0VAzJHjLu0xUfFv1gwVp9ba3IOuRAVH2EGuRW8a5emA2FlACLqiT/lDVtS1W+TGNwqz3sWaNyLgDJWuw==",
68 | "dependencies": {
69 | "brace-expansion": "^1.1.7"
70 | },
71 | "engines": {
72 | "node": "*"
73 | }
74 | },
75 | "node_modules/semver": {
76 | "version": "7.3.5",
77 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
78 | "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
79 | "dependencies": {
80 | "lru-cache": "^6.0.0"
81 | },
82 | "bin": {
83 | "semver": "bin/semver.js"
84 | },
85 | "engines": {
86 | "node": ">=10"
87 | }
88 | },
89 | "node_modules/source-map": {
90 | "version": "0.6.1",
91 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
92 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
93 | "engines": {
94 | "node": ">=0.10.0"
95 | }
96 | },
97 | "node_modules/source-map-support": {
98 | "version": "0.5.21",
99 | "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
100 | "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
101 | "dependencies": {
102 | "buffer-from": "^1.0.0",
103 | "source-map": "^0.6.0"
104 | }
105 | },
106 | "node_modules/typescript": {
107 | "version": "4.6.3",
108 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.3.tgz",
109 | "integrity": "sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==",
110 | "dev": true,
111 | "bin": {
112 | "tsc": "bin/tsc",
113 | "tsserver": "bin/tsserver"
114 | },
115 | "engines": {
116 | "node": ">=4.2.0"
117 | }
118 | },
119 | "node_modules/vscode-jsonrpc": {
120 | "version": "6.0.0",
121 | "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-6.0.0.tgz",
122 | "integrity": "sha512-wnJA4BnEjOSyFMvjZdpiOwhSq9uDoK8e/kpRJDTaMYzwlkrhG1fwDIZI94CLsLzlCK5cIbMMtFlJlfR57Lavmg==",
123 | "engines": {
124 | "node": ">=8.0.0 || >=10.0.0"
125 | }
126 | },
127 | "node_modules/vscode-languageclient": {
128 | "version": "7.0.0",
129 | "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-7.0.0.tgz",
130 | "integrity": "sha512-P9AXdAPlsCgslpP9pRxYPqkNYV7Xq8300/aZDpO35j1fJm/ncize8iGswzYlcvFw5DQUx4eVk+KvfXdL0rehNg==",
131 | "dependencies": {
132 | "minimatch": "^3.0.4",
133 | "semver": "^7.3.4",
134 | "vscode-languageserver-protocol": "3.16.0"
135 | },
136 | "engines": {
137 | "vscode": "^1.52.0"
138 | }
139 | },
140 | "node_modules/vscode-languageserver-protocol": {
141 | "version": "3.16.0",
142 | "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.16.0.tgz",
143 | "integrity": "sha512-sdeUoAawceQdgIfTI+sdcwkiK2KU+2cbEYA0agzM2uqaUy2UpnnGHtWTHVEtS0ES4zHU0eMFRGN+oQgDxlD66A==",
144 | "dependencies": {
145 | "vscode-jsonrpc": "6.0.0",
146 | "vscode-languageserver-types": "3.16.0"
147 | }
148 | },
149 | "node_modules/vscode-languageserver-types": {
150 | "version": "3.16.0",
151 | "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0.tgz",
152 | "integrity": "sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA=="
153 | },
154 | "node_modules/yallist": {
155 | "version": "4.0.0",
156 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
157 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
158 | }
159 | },
160 | "dependencies": {
161 | "@types/vscode": {
162 | "version": "1.65.0",
163 | "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.65.0.tgz",
164 | "integrity": "sha512-wQhExnh2nEzpjDMSKhUvnNmz3ucpd3E+R7wJkOhBNK3No6fG3VUdmVmMOKD0A8NDZDDDiQcLNxe3oGmX5SjJ5w==",
165 | "dev": true
166 | },
167 | "balanced-match": {
168 | "version": "1.0.2",
169 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
170 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
171 | },
172 | "brace-expansion": {
173 | "version": "1.1.11",
174 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
175 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
176 | "requires": {
177 | "balanced-match": "^1.0.0",
178 | "concat-map": "0.0.1"
179 | }
180 | },
181 | "buffer-from": {
182 | "version": "1.1.1",
183 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
184 | "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A=="
185 | },
186 | "concat-map": {
187 | "version": "0.0.1",
188 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
189 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
190 | },
191 | "lru-cache": {
192 | "version": "6.0.0",
193 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
194 | "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
195 | "requires": {
196 | "yallist": "^4.0.0"
197 | }
198 | },
199 | "minimatch": {
200 | "version": "3.0.5",
201 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.5.tgz",
202 | "integrity": "sha512-tUpxzX0VAzJHjLu0xUfFv1gwVp9ba3IOuRAVH2EGuRW8a5emA2FlACLqiT/lDVtS1W+TGNwqz3sWaNyLgDJWuw==",
203 | "requires": {
204 | "brace-expansion": "^1.1.7"
205 | }
206 | },
207 | "semver": {
208 | "version": "7.3.5",
209 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
210 | "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
211 | "requires": {
212 | "lru-cache": "^6.0.0"
213 | }
214 | },
215 | "source-map": {
216 | "version": "0.6.1",
217 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
218 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
219 | },
220 | "source-map-support": {
221 | "version": "0.5.21",
222 | "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
223 | "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
224 | "requires": {
225 | "buffer-from": "^1.0.0",
226 | "source-map": "^0.6.0"
227 | }
228 | },
229 | "typescript": {
230 | "version": "4.6.3",
231 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.3.tgz",
232 | "integrity": "sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==",
233 | "dev": true
234 | },
235 | "vscode-jsonrpc": {
236 | "version": "6.0.0",
237 | "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-6.0.0.tgz",
238 | "integrity": "sha512-wnJA4BnEjOSyFMvjZdpiOwhSq9uDoK8e/kpRJDTaMYzwlkrhG1fwDIZI94CLsLzlCK5cIbMMtFlJlfR57Lavmg=="
239 | },
240 | "vscode-languageclient": {
241 | "version": "7.0.0",
242 | "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-7.0.0.tgz",
243 | "integrity": "sha512-P9AXdAPlsCgslpP9pRxYPqkNYV7Xq8300/aZDpO35j1fJm/ncize8iGswzYlcvFw5DQUx4eVk+KvfXdL0rehNg==",
244 | "requires": {
245 | "minimatch": "^3.0.4",
246 | "semver": "^7.3.4",
247 | "vscode-languageserver-protocol": "3.16.0"
248 | }
249 | },
250 | "vscode-languageserver-protocol": {
251 | "version": "3.16.0",
252 | "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.16.0.tgz",
253 | "integrity": "sha512-sdeUoAawceQdgIfTI+sdcwkiK2KU+2cbEYA0agzM2uqaUy2UpnnGHtWTHVEtS0ES4zHU0eMFRGN+oQgDxlD66A==",
254 | "requires": {
255 | "vscode-jsonrpc": "6.0.0",
256 | "vscode-languageserver-types": "3.16.0"
257 | }
258 | },
259 | "vscode-languageserver-types": {
260 | "version": "3.16.0",
261 | "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0.tgz",
262 | "integrity": "sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA=="
263 | },
264 | "yallist": {
265 | "version": "4.0.0",
266 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
267 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
268 | }
269 | }
270 | }
271 |
--------------------------------------------------------------------------------
/packages/extension/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "auto-rename-tag",
3 | "displayName": "Auto Rename Tag",
4 | "description": "Auto rename paired HTML/XML tag",
5 | "version": "0.1.10",
6 | "publisher": "formulahendry",
7 | "license": "MIT",
8 | "icon": "images/logo.png",
9 | "engines": {
10 | "vscode": "^1.41.1"
11 | },
12 | "categories": [
13 | "Other",
14 | "Programming Languages"
15 | ],
16 | "keywords": [
17 | "AutoComplete",
18 | "rename",
19 | "tag",
20 | "html",
21 | "xml",
22 | "multi-root ready"
23 | ],
24 | "bugs": {
25 | "url": "https://github.com/formulahendry/vscode-auto-rename-tag/issues",
26 | "email": "formulahendry@gmail.com"
27 | },
28 | "homepage": "https://github.com/formulahendry/vscode-auto-rename-tag/blob/master/README.md",
29 | "repository": {
30 | "type": "git",
31 | "url": "https://github.com/formulahendry/vscode-auto-rename-tag.git"
32 | },
33 | "activationEvents": [
34 | "*"
35 | ],
36 | "main": "dist/extensionMain.js",
37 | "capabilities": {
38 | "untrustedWorkspaces": {
39 | "supported": true
40 | },
41 | "virtualWorkspaces": true
42 | },
43 | "contributes": {
44 | "configuration": {
45 | "type": "object",
46 | "title": "Auto Rename Tag configuration",
47 | "properties": {
48 | "auto-rename-tag.activationOnLanguage": {
49 | "type": "array",
50 | "default": [
51 | "*"
52 | ],
53 | "description": "Set the languages that the extension will be activated. e.g. [\"html\",\"xml\",\"php\"] By default, it is [\"*\"] and will be activated for all languages.",
54 | "scope": "resource"
55 | }
56 | }
57 | }
58 | },
59 | "extensionKind": [
60 | "ui",
61 | "workspace"
62 | ],
63 | "scripts": {
64 | "build": "tsc -b"
65 | },
66 | "devDependencies": {
67 | "@types/vscode": "^1.65.0",
68 | "typescript": "^4.6.3"
69 | },
70 | "dependencies": {
71 | "source-map-support": "^0.5.21",
72 | "vscode-languageclient": "^7.0.0"
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/packages/extension/playground/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "html.mirrorCursorOnMatchingTag": false,
3 | "editor.linkedEditing": false,
4 | "editor.formatOnSave": false
5 | }
6 |
--------------------------------------------------------------------------------
/packages/extension/src/createLanguageClientProxy.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode';
2 | import {
3 | Code2ProtocolConverter,
4 | LanguageClient,
5 | LanguageClientOptions,
6 | RequestType,
7 | ServerOptions,
8 | TransportKind
9 | } from 'vscode-languageclient/node';
10 |
11 | type VslSendRequest = (
12 | type: RequestType
,
13 | params: P
14 | ) => Thenable;
15 |
16 | export interface LanguageClientProxy {
17 | readonly code2ProtocolConverter: Code2ProtocolConverter;
18 | readonly sendRequest: VslSendRequest;
19 | }
20 |
21 | export const createLanguageClientProxy: (
22 | context: vscode.ExtensionContext,
23 | id: string,
24 | name: string,
25 | clientOptions: LanguageClientOptions
26 | ) => Promise = async (
27 | context,
28 | id,
29 | name,
30 | clientOptions
31 | ) => {
32 | const serverModule = context.asAbsolutePath('../server/dist/serverMain.js');
33 | const serverOptions: ServerOptions = {
34 | run: { module: serverModule, transport: TransportKind.ipc },
35 | debug: {
36 | module: serverModule,
37 | transport: TransportKind.ipc,
38 | options: { execArgv: ['--nolazy', '--inspect=6009'] }
39 | }
40 | };
41 | const outputChannel = vscode.window.createOutputChannel(name);
42 | clientOptions.outputChannel = {
43 | name: outputChannel.name,
44 | append() {},
45 | appendLine(value: string) {
46 | try {
47 | const message = JSON.parse(value);
48 | if (!message.isLSPMessage) {
49 | outputChannel.appendLine(value);
50 | }
51 | } catch (error) {
52 | if (typeof value !== 'object') {
53 | outputChannel.appendLine(value);
54 | }
55 | }
56 | },
57 | replace(value) {
58 | outputChannel.replace(value);
59 | },
60 | clear() {
61 | outputChannel.clear();
62 | },
63 | show() {
64 | outputChannel.show();
65 | },
66 | hide() {
67 | outputChannel.hide();
68 | },
69 | dispose() {
70 | outputChannel.dispose();
71 | }
72 | };
73 |
74 | const languageClient = new LanguageClient(
75 | id,
76 | name,
77 | serverOptions,
78 | clientOptions
79 | );
80 |
81 | languageClient.registerProposedFeatures();
82 | context.subscriptions.push(languageClient.start());
83 | await languageClient.onReady();
84 | const languageClientProxy: LanguageClientProxy = {
85 | code2ProtocolConverter: languageClient.code2ProtocolConverter,
86 | sendRequest: (type, params) => languageClient.sendRequest(type, params)
87 | };
88 | return languageClientProxy;
89 | };
90 |
--------------------------------------------------------------------------------
/packages/extension/src/extensionMain.ts:
--------------------------------------------------------------------------------
1 | import { AssertionError } from 'assert';
2 | import 'source-map-support/register';
3 | import * as vscode from 'vscode';
4 | import {
5 | Disposable,
6 | LanguageClientOptions,
7 | RequestType,
8 | VersionedTextDocumentIdentifier
9 | } from 'vscode-languageclient';
10 | import {
11 | createLanguageClientProxy,
12 | LanguageClientProxy
13 | } from './createLanguageClientProxy';
14 |
15 | interface Tag {
16 | word: string;
17 | offset: number;
18 | oldWord: string;
19 | previousOffset: number;
20 | }
21 |
22 | interface Params {
23 | readonly textDocument: VersionedTextDocumentIdentifier;
24 | readonly tags: Tag[];
25 | }
26 |
27 | interface Result {
28 | readonly originalOffset: number;
29 | readonly originalWord: string;
30 | readonly startOffset: number;
31 | readonly endOffset: number;
32 | readonly tagName: string;
33 | }
34 |
35 | const assertDefined: (value: T) => asserts value is NonNullable = val => {
36 | if (val === undefined || val === null) {
37 | throw new AssertionError({
38 | message: `Expected 'value' to be defined, but received ${val}`
39 | });
40 | }
41 | };
42 |
43 | const autoRenameTagRequestType = new RequestType(
44 | '$/auto-rename-tag'
45 | );
46 |
47 | // TODO implement max concurrent requests
48 |
49 | const askServerForAutoCompletionsElementRenameTag: (
50 | languageClientProxy: LanguageClientProxy,
51 | document: vscode.TextDocument,
52 | tags: Tag[]
53 | ) => Promise = async (languageClientProxy, document, tags) => {
54 | const params: Params = {
55 | textDocument:
56 | languageClientProxy.code2ProtocolConverter.asVersionedTextDocumentIdentifier(
57 | document
58 | ),
59 | tags
60 | };
61 | return languageClientProxy.sendRequest(autoRenameTagRequestType, params);
62 | };
63 |
64 | /**
65 | * Utility variable that stores the last changed version (document.uri.fsPath and document.version)
66 | * When a change was caused by auto-rename-tag, we can ignore that change, which is a simple performance improvement. One thing to take care of is undo, but that works now (and there are test cases).
67 | */
68 | let lastChangeByAutoRenameTag: { fsPath: string; version: number } = {
69 | fsPath: '',
70 | version: -1
71 | };
72 |
73 | const applyResults: (results: Result[]) => Promise = async results => {
74 | assertDefined(vscode.window.activeTextEditor);
75 | const prev = vscode.window.activeTextEditor.document.version;
76 | const applied = await vscode.window.activeTextEditor.edit(
77 | editBuilder => {
78 | assertDefined(vscode.window.activeTextEditor);
79 | for (const result of results) {
80 | const startPosition =
81 | vscode.window.activeTextEditor.document.positionAt(
82 | result.startOffset
83 | );
84 | const endPosition = vscode.window.activeTextEditor.document.positionAt(
85 | result.endOffset
86 | );
87 | const range = new vscode.Range(startPosition, endPosition);
88 | editBuilder.replace(range, result.tagName);
89 | }
90 | },
91 | {
92 | undoStopBefore: false,
93 | undoStopAfter: false
94 | }
95 | );
96 |
97 | const next = vscode.window.activeTextEditor.document.version;
98 | if (!applied) {
99 | return;
100 | }
101 | lastChangeByAutoRenameTag = {
102 | fsPath: vscode.window.activeTextEditor.document.uri.fsPath,
103 | version: vscode.window.activeTextEditor.document.version
104 | };
105 | if (prev + 1 !== next) {
106 | return;
107 | }
108 | for (const result of results) {
109 | const oldWordAtOffset = wordsAtOffsets[result.originalOffset];
110 | delete wordsAtOffsets[result.originalOffset];
111 |
112 | let moved = 0;
113 | if (result.originalWord.startsWith('')) {
114 | moved = result.endOffset - result.startOffset + 2;
115 | }
116 | wordsAtOffsets[result.originalOffset + moved] = {
117 | newWord: oldWordAtOffset && oldWordAtOffset.newWord,
118 | oldWord: result.originalWord
119 | };
120 | }
121 | };
122 |
123 | let latestCancelTokenSource: vscode.CancellationTokenSource | undefined;
124 | let previousText: string | undefined;
125 | const tagNameReLeft = /<\/?[^<>\s\\\/\'\"\(\)\`\{\}\[\]]*$/;
126 | const tagNameRERight = /^[^<>\s\\\/\'\"\(\)\`\{\}\[\]]*/;
127 |
128 | let wordsAtOffsets: {
129 | [offset: string]: {
130 | oldWord: string;
131 | newWord: string;
132 | };
133 | } = {};
134 |
135 | const updateWordsAtOffset: (tags: Tag[]) => void = tags => {
136 | const keys = Object.keys(wordsAtOffsets);
137 | if (keys.length > 0) {
138 | if (keys.length !== tags.length) {
139 | wordsAtOffsets = {};
140 | }
141 | for (const tag of tags) {
142 | if (!wordsAtOffsets.hasOwnProperty(tag.previousOffset)) {
143 | wordsAtOffsets = {};
144 | break;
145 | }
146 | }
147 | }
148 | for (const tag of tags) {
149 | wordsAtOffsets[tag.offset] = {
150 | oldWord:
151 | (wordsAtOffsets[tag.previousOffset] &&
152 | wordsAtOffsets[tag.previousOffset].oldWord) ||
153 | tag.oldWord,
154 | newWord: tag.word
155 | };
156 | if (tag.previousOffset !== tag.offset) {
157 | delete wordsAtOffsets[tag.previousOffset];
158 | }
159 | tag.oldWord = wordsAtOffsets[tag.offset].oldWord;
160 | }
161 | };
162 | const doAutoCompletionElementRenameTag: (
163 | languageClientProxy: LanguageClientProxy,
164 | tags: Tag[]
165 | ) => Promise = async (languageClientProxy, tags) => {
166 | if (latestCancelTokenSource) {
167 | latestCancelTokenSource.cancel();
168 | }
169 | const cancelTokenSource = new vscode.CancellationTokenSource();
170 | latestCancelTokenSource = cancelTokenSource;
171 | if (!vscode.window.activeTextEditor) {
172 | return;
173 | }
174 | const beforeVersion = vscode.window.activeTextEditor.document.version;
175 | // the change event is fired before we can update the version of the last change by auto rename tag, therefore we wait for that
176 | await new Promise(resolve => setTimeout(resolve, 0));
177 | if (!vscode.window.activeTextEditor) {
178 | return;
179 | }
180 | if (
181 | lastChangeByAutoRenameTag.fsPath ===
182 | vscode.window.activeTextEditor.document.uri.fsPath &&
183 | lastChangeByAutoRenameTag.version ===
184 | vscode.window.activeTextEditor.document.version
185 | ) {
186 | return;
187 | }
188 |
189 | if (cancelTokenSource.token.isCancellationRequested) {
190 | return;
191 | }
192 |
193 | const results = await askServerForAutoCompletionsElementRenameTag(
194 | languageClientProxy,
195 | vscode.window.activeTextEditor.document,
196 | tags
197 | );
198 | if (cancelTokenSource.token.isCancellationRequested) {
199 | return;
200 | }
201 | if (latestCancelTokenSource === cancelTokenSource) {
202 | latestCancelTokenSource = undefined;
203 | cancelTokenSource.dispose();
204 | }
205 | if (results.length === 0) {
206 | wordsAtOffsets = {};
207 | return;
208 | }
209 | if (!vscode.window.activeTextEditor) {
210 | return;
211 | }
212 | const afterVersion = vscode.window.activeTextEditor.document.version;
213 | if (beforeVersion !== afterVersion) {
214 | return;
215 | }
216 | await applyResults(results);
217 | };
218 |
219 | const setPreviousText: (
220 | textEditor: vscode.TextEditor | undefined
221 | ) => void = textEditor => {
222 | if (textEditor) {
223 | previousText = textEditor.document.getText();
224 | } else {
225 | previousText = undefined;
226 | }
227 | };
228 |
229 | export const activate: (
230 | context: vscode.ExtensionContext
231 | ) => Promise = async context => {
232 | vscode.workspace
233 | .getConfiguration('auto-rename-tag')
234 | .get('activationOnLanguage');
235 | const isEnabled = (document: vscode.TextDocument | undefined) => {
236 | if (!document) {
237 | return false;
238 | }
239 |
240 | const languageId = document.languageId;
241 |
242 | if (languageId === 'html' || languageId === 'handlebars') {
243 | const editorSettings = vscode.workspace.getConfiguration(
244 | 'editor',
245 | document
246 | );
247 | if (
248 | editorSettings.get('renameOnType') ||
249 | editorSettings.get('linkedEditing')
250 | ) {
251 | return false;
252 | }
253 | }
254 |
255 | const config = vscode.workspace.getConfiguration(
256 | 'auto-rename-tag',
257 | document.uri
258 | );
259 |
260 | const languages = config.get('activationOnLanguage', ['*']);
261 | return languages.includes('*') || languages.includes(languageId);
262 | };
263 | context.subscriptions.push(
264 | vscode.workspace.onDidChangeConfiguration(event => {
265 | // purges cache for `vscode.workspace.getConfiguration`
266 | if (!event.affectsConfiguration('auto-rename-tag')) {
267 | return;
268 | }
269 | })
270 | );
271 | const clientOptions: LanguageClientOptions = {
272 | documentSelector: [
273 | {
274 | scheme: '*'
275 | }
276 | ]
277 | };
278 | const languageClientProxy = await createLanguageClientProxy(
279 | context,
280 | 'auto-rename-tag',
281 | 'Auto Rename Tag',
282 | clientOptions
283 | );
284 | let activeTextEditor: vscode.TextEditor | undefined =
285 | vscode.window.activeTextEditor;
286 | let changeListener: Disposable | undefined;
287 | context.subscriptions.push({
288 | dispose() {
289 | if (changeListener) {
290 | changeListener.dispose();
291 | changeListener = undefined;
292 | }
293 | }
294 | });
295 | const setupChangeListener = () => {
296 | if (changeListener) {
297 | return;
298 | }
299 | changeListener = vscode.workspace.onDidChangeTextDocument(async event => {
300 | if (event.document !== activeTextEditor?.document) {
301 | return;
302 | }
303 |
304 | if (!isEnabled(event.document)) {
305 | changeListener?.dispose();
306 | changeListener = undefined;
307 | return;
308 | }
309 |
310 | if (event.contentChanges.length === 0) {
311 | return;
312 | }
313 |
314 | const currentText = event.document.getText();
315 | const tags: Tag[] = [];
316 | let totalInserted = 0;
317 | const sortedChanges = event.contentChanges
318 | .slice()
319 | .sort((a, b) => a.rangeOffset - b.rangeOffset);
320 | const keys = Object.keys(wordsAtOffsets);
321 | for (const change of sortedChanges) {
322 | for (const key of keys) {
323 | const parsedKey = parseInt(key, 10);
324 | if (
325 | change.rangeOffset <= parsedKey &&
326 | parsedKey <= change.rangeOffset + change.rangeLength
327 | ) {
328 | delete wordsAtOffsets[key];
329 | }
330 | }
331 | assertDefined(previousText);
332 | const line = event.document.lineAt(change.range.start.line);
333 | const lineStart = event.document.offsetAt(line.range.start);
334 | const lineChangeOffset = change.rangeOffset - lineStart;
335 | const lineLeft = line.text.slice(0, lineChangeOffset + totalInserted);
336 | const lineRight = line.text.slice(lineChangeOffset + totalInserted);
337 | const lineTagNameLeft = lineLeft.match(tagNameReLeft);
338 | const lineTagNameRight = lineRight.match(tagNameRERight);
339 | const previousTextRight = previousText.slice(change.rangeOffset);
340 | const previousTagNameRight = previousTextRight.match(tagNameRERight);
341 | let newWord: string;
342 | let oldWord: string;
343 | if (!lineTagNameLeft) {
344 | totalInserted += change.text.length - change.rangeLength;
345 | continue;
346 | }
347 | newWord = lineTagNameLeft[0];
348 | oldWord = lineTagNameLeft[0];
349 | if (lineTagNameRight) {
350 | newWord += lineTagNameRight[0];
351 | }
352 | if (previousTagNameRight) {
353 | oldWord += previousTagNameRight[0];
354 | }
355 | const offset =
356 | change.rangeOffset - lineTagNameLeft[0].length + totalInserted;
357 | tags.push({
358 | oldWord,
359 | word: newWord,
360 | offset,
361 | previousOffset: offset - totalInserted
362 | });
363 | totalInserted += change.text.length - change.rangeLength;
364 | }
365 | updateWordsAtOffset(tags);
366 | if (tags.length === 0) {
367 | previousText = currentText;
368 | return;
369 | }
370 | assertDefined(vscode.window.activeTextEditor);
371 | previousText = currentText;
372 | doAutoCompletionElementRenameTag(languageClientProxy, tags);
373 | });
374 | };
375 | setPreviousText(vscode.window.activeTextEditor);
376 | setupChangeListener();
377 | context.subscriptions.push(
378 | vscode.window.onDidChangeActiveTextEditor(textEditor => {
379 | activeTextEditor = textEditor;
380 | const doument = activeTextEditor?.document;
381 | if (!isEnabled(doument)) {
382 | if (changeListener) {
383 | changeListener.dispose();
384 | changeListener = undefined;
385 | }
386 | return;
387 | }
388 | setPreviousText(textEditor);
389 | setupChangeListener();
390 | })
391 | );
392 | };
393 |
--------------------------------------------------------------------------------
/packages/extension/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig-noncomposite-base",
3 | "compilerOptions": {
4 | "outDir": "dist",
5 | "rootDir": "src",
6 | "types": ["node"],
7 | "noUnusedLocals": false,
8 | "noUnusedParameters": false
9 | },
10 | "include": ["src"],
11 | "references": []
12 | }
13 |
--------------------------------------------------------------------------------
/packages/server/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "server",
3 | "version": "1.0.0-dev",
4 | "lockfileVersion": 2,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "server",
9 | "version": "1.0.0-dev",
10 | "dependencies": {
11 | "@babel/code-frame": "^7.16.7",
12 | "source-map-support": "^0.5.21",
13 | "typescript": "^4.6.3",
14 | "vscode-languageserver": "^7.0.0",
15 | "vscode-languageserver-textdocument": "^1.0.4"
16 | },
17 | "devDependencies": {
18 | "@types/babel__code-frame": "^7.0.3"
19 | }
20 | },
21 | "../service": {
22 | "version": "1.0.0-dev",
23 | "extraneous": true,
24 | "devDependencies": {
25 | "typescript": "^4.5.5"
26 | }
27 | },
28 | "node_modules/@babel/code-frame": {
29 | "version": "7.16.7",
30 | "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz",
31 | "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==",
32 | "dependencies": {
33 | "@babel/highlight": "^7.16.7"
34 | },
35 | "engines": {
36 | "node": ">=6.9.0"
37 | }
38 | },
39 | "node_modules/@babel/helper-validator-identifier": {
40 | "version": "7.16.7",
41 | "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz",
42 | "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==",
43 | "engines": {
44 | "node": ">=6.9.0"
45 | }
46 | },
47 | "node_modules/@babel/highlight": {
48 | "version": "7.16.10",
49 | "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.10.tgz",
50 | "integrity": "sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==",
51 | "dependencies": {
52 | "@babel/helper-validator-identifier": "^7.16.7",
53 | "chalk": "^2.0.0",
54 | "js-tokens": "^4.0.0"
55 | },
56 | "engines": {
57 | "node": ">=6.9.0"
58 | }
59 | },
60 | "node_modules/@types/babel__code-frame": {
61 | "version": "7.0.3",
62 | "resolved": "https://registry.npmjs.org/@types/babel__code-frame/-/babel__code-frame-7.0.3.tgz",
63 | "integrity": "sha512-2TN6oiwtNjOezilFVl77zwdNPwQWaDBBCCWWxyo1ctiO3vAtd7H/aB/CBJdw9+kqq3+latD0SXoedIuHySSZWw==",
64 | "dev": true
65 | },
66 | "node_modules/ansi-styles": {
67 | "version": "3.2.1",
68 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
69 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
70 | "dependencies": {
71 | "color-convert": "^1.9.0"
72 | },
73 | "engines": {
74 | "node": ">=4"
75 | }
76 | },
77 | "node_modules/buffer-from": {
78 | "version": "1.1.1",
79 | "license": "MIT"
80 | },
81 | "node_modules/chalk": {
82 | "version": "2.4.2",
83 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
84 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
85 | "dependencies": {
86 | "ansi-styles": "^3.2.1",
87 | "escape-string-regexp": "^1.0.5",
88 | "supports-color": "^5.3.0"
89 | },
90 | "engines": {
91 | "node": ">=4"
92 | }
93 | },
94 | "node_modules/color-convert": {
95 | "version": "1.9.3",
96 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
97 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
98 | "dependencies": {
99 | "color-name": "1.1.3"
100 | }
101 | },
102 | "node_modules/color-name": {
103 | "version": "1.1.3",
104 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
105 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
106 | },
107 | "node_modules/escape-string-regexp": {
108 | "version": "1.0.5",
109 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
110 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
111 | "engines": {
112 | "node": ">=0.8.0"
113 | }
114 | },
115 | "node_modules/has-flag": {
116 | "version": "3.0.0",
117 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
118 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
119 | "engines": {
120 | "node": ">=4"
121 | }
122 | },
123 | "node_modules/js-tokens": {
124 | "version": "4.0.0",
125 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
126 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
127 | },
128 | "node_modules/source-map": {
129 | "version": "0.6.1",
130 | "license": "BSD-3-Clause",
131 | "engines": {
132 | "node": ">=0.10.0"
133 | }
134 | },
135 | "node_modules/source-map-support": {
136 | "version": "0.5.21",
137 | "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
138 | "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
139 | "dependencies": {
140 | "buffer-from": "^1.0.0",
141 | "source-map": "^0.6.0"
142 | }
143 | },
144 | "node_modules/supports-color": {
145 | "version": "5.5.0",
146 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
147 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
148 | "dependencies": {
149 | "has-flag": "^3.0.0"
150 | },
151 | "engines": {
152 | "node": ">=4"
153 | }
154 | },
155 | "node_modules/typescript": {
156 | "version": "4.6.3",
157 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.3.tgz",
158 | "integrity": "sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==",
159 | "bin": {
160 | "tsc": "bin/tsc",
161 | "tsserver": "bin/tsserver"
162 | },
163 | "engines": {
164 | "node": ">=4.2.0"
165 | }
166 | },
167 | "node_modules/vscode-jsonrpc": {
168 | "version": "6.0.0",
169 | "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-6.0.0.tgz",
170 | "integrity": "sha512-wnJA4BnEjOSyFMvjZdpiOwhSq9uDoK8e/kpRJDTaMYzwlkrhG1fwDIZI94CLsLzlCK5cIbMMtFlJlfR57Lavmg==",
171 | "engines": {
172 | "node": ">=8.0.0 || >=10.0.0"
173 | }
174 | },
175 | "node_modules/vscode-languageserver": {
176 | "version": "7.0.0",
177 | "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-7.0.0.tgz",
178 | "integrity": "sha512-60HTx5ID+fLRcgdHfmz0LDZAXYEV68fzwG0JWwEPBode9NuMYTIxuYXPg4ngO8i8+Ou0lM7y6GzaYWbiDL0drw==",
179 | "dependencies": {
180 | "vscode-languageserver-protocol": "3.16.0"
181 | },
182 | "bin": {
183 | "installServerIntoExtension": "bin/installServerIntoExtension"
184 | }
185 | },
186 | "node_modules/vscode-languageserver-protocol": {
187 | "version": "3.16.0",
188 | "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.16.0.tgz",
189 | "integrity": "sha512-sdeUoAawceQdgIfTI+sdcwkiK2KU+2cbEYA0agzM2uqaUy2UpnnGHtWTHVEtS0ES4zHU0eMFRGN+oQgDxlD66A==",
190 | "dependencies": {
191 | "vscode-jsonrpc": "6.0.0",
192 | "vscode-languageserver-types": "3.16.0"
193 | }
194 | },
195 | "node_modules/vscode-languageserver-textdocument": {
196 | "version": "1.0.4",
197 | "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.4.tgz",
198 | "integrity": "sha512-/xhqXP/2A2RSs+J8JNXpiiNVvvNM0oTosNVmQnunlKvq9o4mupHOBAnnzH0lwIPKazXKvAKsVp1kr+H/K4lgoQ=="
199 | },
200 | "node_modules/vscode-languageserver-types": {
201 | "version": "3.16.0",
202 | "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0.tgz",
203 | "integrity": "sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA=="
204 | }
205 | },
206 | "dependencies": {
207 | "@babel/code-frame": {
208 | "version": "7.16.7",
209 | "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz",
210 | "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==",
211 | "requires": {
212 | "@babel/highlight": "^7.16.7"
213 | }
214 | },
215 | "@babel/helper-validator-identifier": {
216 | "version": "7.16.7",
217 | "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz",
218 | "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw=="
219 | },
220 | "@babel/highlight": {
221 | "version": "7.16.10",
222 | "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.10.tgz",
223 | "integrity": "sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==",
224 | "requires": {
225 | "@babel/helper-validator-identifier": "^7.16.7",
226 | "chalk": "^2.0.0",
227 | "js-tokens": "^4.0.0"
228 | }
229 | },
230 | "@types/babel__code-frame": {
231 | "version": "7.0.3",
232 | "resolved": "https://registry.npmjs.org/@types/babel__code-frame/-/babel__code-frame-7.0.3.tgz",
233 | "integrity": "sha512-2TN6oiwtNjOezilFVl77zwdNPwQWaDBBCCWWxyo1ctiO3vAtd7H/aB/CBJdw9+kqq3+latD0SXoedIuHySSZWw==",
234 | "dev": true
235 | },
236 | "ansi-styles": {
237 | "version": "3.2.1",
238 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
239 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
240 | "requires": {
241 | "color-convert": "^1.9.0"
242 | }
243 | },
244 | "buffer-from": {
245 | "version": "1.1.1"
246 | },
247 | "chalk": {
248 | "version": "2.4.2",
249 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
250 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
251 | "requires": {
252 | "ansi-styles": "^3.2.1",
253 | "escape-string-regexp": "^1.0.5",
254 | "supports-color": "^5.3.0"
255 | }
256 | },
257 | "color-convert": {
258 | "version": "1.9.3",
259 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
260 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
261 | "requires": {
262 | "color-name": "1.1.3"
263 | }
264 | },
265 | "color-name": {
266 | "version": "1.1.3",
267 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
268 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
269 | },
270 | "escape-string-regexp": {
271 | "version": "1.0.5",
272 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
273 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
274 | },
275 | "has-flag": {
276 | "version": "3.0.0",
277 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
278 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
279 | },
280 | "js-tokens": {
281 | "version": "4.0.0",
282 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
283 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
284 | },
285 | "source-map": {
286 | "version": "0.6.1"
287 | },
288 | "source-map-support": {
289 | "version": "0.5.21",
290 | "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
291 | "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
292 | "requires": {
293 | "buffer-from": "^1.0.0",
294 | "source-map": "^0.6.0"
295 | }
296 | },
297 | "supports-color": {
298 | "version": "5.5.0",
299 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
300 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
301 | "requires": {
302 | "has-flag": "^3.0.0"
303 | }
304 | },
305 | "typescript": {
306 | "version": "4.6.3",
307 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.3.tgz",
308 | "integrity": "sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw=="
309 | },
310 | "vscode-jsonrpc": {
311 | "version": "6.0.0",
312 | "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-6.0.0.tgz",
313 | "integrity": "sha512-wnJA4BnEjOSyFMvjZdpiOwhSq9uDoK8e/kpRJDTaMYzwlkrhG1fwDIZI94CLsLzlCK5cIbMMtFlJlfR57Lavmg=="
314 | },
315 | "vscode-languageserver": {
316 | "version": "7.0.0",
317 | "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-7.0.0.tgz",
318 | "integrity": "sha512-60HTx5ID+fLRcgdHfmz0LDZAXYEV68fzwG0JWwEPBode9NuMYTIxuYXPg4ngO8i8+Ou0lM7y6GzaYWbiDL0drw==",
319 | "requires": {
320 | "vscode-languageserver-protocol": "3.16.0"
321 | }
322 | },
323 | "vscode-languageserver-protocol": {
324 | "version": "3.16.0",
325 | "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.16.0.tgz",
326 | "integrity": "sha512-sdeUoAawceQdgIfTI+sdcwkiK2KU+2cbEYA0agzM2uqaUy2UpnnGHtWTHVEtS0ES4zHU0eMFRGN+oQgDxlD66A==",
327 | "requires": {
328 | "vscode-jsonrpc": "6.0.0",
329 | "vscode-languageserver-types": "3.16.0"
330 | }
331 | },
332 | "vscode-languageserver-textdocument": {
333 | "version": "1.0.4",
334 | "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.4.tgz",
335 | "integrity": "sha512-/xhqXP/2A2RSs+J8JNXpiiNVvvNM0oTosNVmQnunlKvq9o4mupHOBAnnzH0lwIPKazXKvAKsVp1kr+H/K4lgoQ=="
336 | },
337 | "vscode-languageserver-types": {
338 | "version": "3.16.0",
339 | "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0.tgz",
340 | "integrity": "sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA=="
341 | }
342 | }
343 | }
344 |
--------------------------------------------------------------------------------
/packages/server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "server",
3 | "version": "1.0.0-dev",
4 | "main": "dist/serverMain.js",
5 | "scripts": {
6 | "dev": "tsc -b -w"
7 | },
8 | "dependencies": {
9 | "@babel/code-frame": "^7.16.7",
10 | "service": "^1.0.0-dev",
11 | "source-map-support": "^0.5.21",
12 | "typescript": "^4.6.3",
13 | "vscode-languageserver": "^7.0.0",
14 | "vscode-languageserver-textdocument": "^1.0.4"
15 | },
16 | "devDependencies": {
17 | "@types/babel__code-frame": "^7.0.3"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/packages/server/src/autoRenameTag.ts:
--------------------------------------------------------------------------------
1 | import { doAutoRenameTag } from 'service';
2 | import {
3 | RequestType,
4 | // TODO
5 | TextDocuments,
6 | VersionedTextDocumentIdentifier
7 | } from 'vscode-languageserver';
8 | import { TextDocument } from 'vscode-languageserver-textdocument';
9 |
10 | interface Tag {
11 | readonly word: string;
12 | readonly oldWord: string;
13 | readonly offset: number;
14 | }
15 | interface Params {
16 | readonly textDocument: VersionedTextDocumentIdentifier;
17 | readonly tags: Tag[];
18 | }
19 |
20 | interface Result {
21 | readonly startOffset: number;
22 | readonly endOffset: number;
23 | readonly tagName: string;
24 | readonly originalWord: string;
25 | readonly originalOffset: number;
26 | }
27 |
28 | export const autoRenameTagRequestType = new RequestType(
29 | '$/auto-rename-tag'
30 | );
31 |
32 | const NULL_AUTO_RENAME_TAG_RESULT: Result[] = [];
33 |
34 | export const autoRenameTag: (
35 | documents: TextDocuments
36 | ) => (params: Params) => Promise =
37 | documents =>
38 | async ({ textDocument, tags }) => {
39 | await new Promise(r => setTimeout(r, 20));
40 | const document = documents.get(textDocument.uri);
41 | if (!document) {
42 | return NULL_AUTO_RENAME_TAG_RESULT;
43 | }
44 | if (textDocument.version !== document.version) {
45 | return NULL_AUTO_RENAME_TAG_RESULT;
46 | }
47 | const text = document.getText();
48 | const results: Result[] = tags
49 | .map(tag => {
50 | const result = doAutoRenameTag(
51 | text,
52 | tag.offset,
53 | tag.word,
54 | tag.oldWord,
55 | document.languageId
56 | );
57 | if (!result) {
58 | return result;
59 | }
60 | (result as any).originalOffset = tag.offset;
61 | (result as any).originalWord = tag.word;
62 | return result as Result;
63 | })
64 | .filter(Boolean) as Result[];
65 | return results;
66 | };
67 |
--------------------------------------------------------------------------------
/packages/server/src/errorHandlingAndLogging.ts:
--------------------------------------------------------------------------------
1 | import { codeFrameColumns } from '@babel/code-frame';
2 | import * as fs from 'fs';
3 | import 'source-map-support/register';
4 | import { Connection } from 'vscode-languageserver';
5 |
6 | export const handleError: (error: Error) => void = error => {
7 | console.error(error.stack);
8 | const lines = error.stack?.split('\n') || [];
9 | let file = lines[1];
10 | if (file) {
11 | let match = file.match(/\((.*):(\d+):(\d+)\)$/);
12 | if (!match) {
13 | match = file.match(/at (.*):(\d+):(\d+)$/);
14 | }
15 | if (match) {
16 | const [_, path, line, column] = match;
17 | const rawLines = fs.readFileSync(path, 'utf-8');
18 | const location = {
19 | start: {
20 | line: parseInt(line),
21 | column: parseInt(column)
22 | }
23 | };
24 |
25 | const result = codeFrameColumns(rawLines, location);
26 | console.log('\n' + result + '\n');
27 | }
28 | }
29 | let relevantStack = (error as Error).stack?.split('\n').slice(1).join('\n');
30 | if (relevantStack?.includes('at CallbackList.invoke')) {
31 | relevantStack = relevantStack.slice(
32 | 0,
33 | relevantStack.indexOf('at CallbackList.invoke')
34 | );
35 | }
36 | console.log(relevantStack);
37 | };
38 |
39 | const useConnectionConsole: (
40 | connection: Connection,
41 | { trace }: { trace?: boolean }
42 | ) => (method: 'log' | 'info' | 'error') => (...args: any[]) => void =
43 | (connection, { trace = false } = {}) =>
44 | method =>
45 | (...args) => {
46 | if (trace) {
47 | const stack = new Error().stack || '';
48 | let file = stack.split('\n')[2];
49 | file = file.slice(file.indexOf('at') + 'at'.length, -1);
50 | const match = file.match(/(.*):(\d+):(\d+)$/);
51 | if (match) {
52 | const [_, path, line, column] = match;
53 | connection.console[method]('at ' + path + ':' + line);
54 | }
55 | }
56 | const stringify: (arg: any) => string = arg => {
57 | if (arg && arg.toString) {
58 | if (arg.toString() === '[object Promise]') {
59 | return JSON.stringify(arg);
60 | }
61 | if (arg.toString() === '[object Object]') {
62 | return JSON.stringify(arg);
63 | }
64 | return arg;
65 | }
66 | return JSON.stringify(arg);
67 | };
68 | connection.console[method](args.map(stringify).join(''));
69 | };
70 |
71 | /**
72 | * Enables better stack traces for errors and logging.
73 | */
74 | export const enableBetterErrorHandlingAndLogging: (
75 | connection: Connection
76 | ) => void = connection => {
77 | const connectionConsole = useConnectionConsole(connection, { trace: false });
78 | console.log = connectionConsole('log');
79 | console.info = connectionConsole('info');
80 | console.error = connectionConsole('error');
81 | process.on('uncaughtException', handleError);
82 | process.on('unhandledRejection', handleError);
83 | };
84 |
--------------------------------------------------------------------------------
/packages/server/src/serverMain.ts:
--------------------------------------------------------------------------------
1 | import { TextDocument } from 'vscode-languageserver-textdocument';
2 | import {
3 | createConnection,
4 | TextDocuments,
5 | TextDocumentSyncKind
6 | } from 'vscode-languageserver/node';
7 | import { autoRenameTag, autoRenameTagRequestType } from './autoRenameTag';
8 | import {
9 | enableBetterErrorHandlingAndLogging,
10 | handleError
11 | } from './errorHandlingAndLogging';
12 |
13 | const connection = createConnection();
14 | const documents = new TextDocuments(TextDocument);
15 |
16 | enableBetterErrorHandlingAndLogging(connection);
17 |
18 | connection.onInitialize(() => ({
19 | capabilities: {
20 | textDocumentSync: TextDocumentSyncKind.Incremental
21 | }
22 | }));
23 |
24 | connection.onInitialized(() => {
25 | console.log('Auto Rename Tag has been initialized.');
26 | });
27 |
28 | const handleRequest: (
29 | fn: (params: Params) => Result
30 | ) => (params: Params) => Result = fn => params => {
31 | try {
32 | return fn(params);
33 | } catch (error) {
34 | handleError(error);
35 | throw error;
36 | }
37 | };
38 |
39 | connection.onRequest(
40 | autoRenameTagRequestType,
41 | handleRequest(autoRenameTag(documents))
42 | );
43 |
44 | documents.listen(connection);
45 | connection.listen();
46 |
--------------------------------------------------------------------------------
/packages/server/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig-noncomposite-base",
3 | "compilerOptions": {
4 | "outDir": "dist",
5 | "rootDir": "src",
6 | "types": ["node"],
7 | "allowUnusedLabels": true,
8 | "allowUnreachableCode": true,
9 | "noUnusedParameters": false,
10 | "noUnusedLocals": false
11 | },
12 | "include": ["src"],
13 | "references": [
14 | {
15 | "path": "../service/tsconfig.json"
16 | }
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/packages/service/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "service",
3 | "version": "1.0.0-dev",
4 | "lockfileVersion": 2,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "service",
9 | "version": "1.0.0-dev",
10 | "devDependencies": {
11 | "typescript": "^4.6.3"
12 | }
13 | },
14 | "node_modules/typescript": {
15 | "version": "4.6.3",
16 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.3.tgz",
17 | "integrity": "sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==",
18 | "dev": true,
19 | "bin": {
20 | "tsc": "bin/tsc",
21 | "tsserver": "bin/tsserver"
22 | },
23 | "engines": {
24 | "node": ">=4.2.0"
25 | }
26 | }
27 | },
28 | "dependencies": {
29 | "typescript": {
30 | "version": "4.6.3",
31 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.3.tgz",
32 | "integrity": "sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==",
33 | "dev": true
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/packages/service/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "service",
3 | "version": "1.0.0-dev",
4 | "main": "dist/serviceMain.js",
5 | "description": "",
6 | "sideEffects": false,
7 | "scripts": {
8 | "dev": "tsc -b -w",
9 | "test": "jest"
10 | },
11 | "devDependencies": {
12 | "typescript": "^4.6.3"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/packages/service/src/benchmark/doAutoRenameTagBenchmark.ts:
--------------------------------------------------------------------------------
1 | import * as fs from 'fs';
2 | import * as path from 'path';
3 | import { doAutoRenameTag } from '../doAutoRenameTag';
4 | import { isSelfClosingTagInLanguage } from '../isSelfClosingTag';
5 | import { getMatchingTagPairs } from '../getMatchingTagPairs';
6 |
7 | const file = fs
8 | .readFileSync(path.join(__dirname, '../../src/benchmark/file.txt'))
9 | .toString();
10 |
11 | const measure: any = (name: string, fn: any, runs: number) => {
12 | const NS_PER_MS = 1e6;
13 | const NS_PER_SEC = 1e9;
14 | const start = process.hrtime();
15 | for (let i = 0; i < runs; i++) {
16 | fn();
17 | }
18 | console.log(runs + ' runs');
19 | const elapsedTime = process.hrtime(start);
20 | const elapsedTimeMs =
21 | (elapsedTime[0] * NS_PER_SEC + elapsedTime[1]) / NS_PER_MS / runs;
22 | console.log(name + ' took ' + elapsedTimeMs + 'ms');
23 | };
24 |
25 | measure(
26 | 'rename',
27 | () => {
28 | doAutoRenameTag(file, 0, ' {
36 | const whitespaceSet = new Set([' ', '\n', '\t', '\r', '\f']);
37 | let whitespaceCount = 0;
38 | for (let i = 0; i < file.length; i++) {
39 | const j = file[i];
40 | if (whitespaceSet.has(j)) {
41 | whitespaceCount++;
42 | }
43 | }
44 | },
45 | 10
46 | ); //?
47 |
--------------------------------------------------------------------------------
/packages/service/src/doAutoRenameTag.ts:
--------------------------------------------------------------------------------
1 | import { getMatchingTagPairs } from './getMatchingTagPairs';
2 | import {
3 | createScannerFast,
4 | ScannerStateFast
5 | } from './htmlScanner/htmlScannerFast';
6 | import { isSelfClosingTagInLanguage } from './isSelfClosingTag';
7 | import { getNextClosingTagName } from './util/getNextClosingTagName';
8 | import { getPreviousOpeningTagName } from './util/getPreviousOpenTagName';
9 |
10 | export const doAutoRenameTag: (
11 | text: string,
12 | offset: number,
13 | newWord: string,
14 | oldWord: string,
15 | languageId: string
16 | ) =>
17 | | {
18 | startOffset: number;
19 | endOffset: number;
20 | tagName: string;
21 | }
22 | | undefined = (text, offset, newWord, oldWord, languageId) => {
23 | const matchingTagPairs = getMatchingTagPairs(languageId);
24 | const isSelfClosingTag = isSelfClosingTagInLanguage(languageId);
25 | const isReact =
26 | languageId === 'javascript' ||
27 | languageId === 'typescript' ||
28 | languageId === 'javascriptreact' ||
29 | languageId === 'typescriptreact';
30 | const scanner = createScannerFast({
31 | input: text,
32 | initialOffset: 0,
33 | initialState: ScannerStateFast.WithinContent,
34 | matchingTagPairs
35 | });
36 | if (newWord.startsWith('')) {
37 | scanner.stream.goTo(offset);
38 | const tagName = newWord.slice(2);
39 | const oldTagName = oldWord.slice(2);
40 | if (oldTagName.startsWith('script') || oldTagName.startsWith('style')) {
41 | const tag = `<${oldTagName}`;
42 | let i = scanner.stream.position;
43 | let found = false;
44 | while (i--) {
45 | if (text.slice(i).startsWith(tag)) {
46 | found = true;
47 | break;
48 | }
49 | }
50 | if (!found) {
51 | return undefined;
52 | }
53 | return {
54 | startOffset: i + 1,
55 | endOffset: i + 1 + oldTagName.length,
56 | tagName
57 | };
58 | }
59 | const parent = getPreviousOpeningTagName(
60 | scanner,
61 | scanner.stream.position,
62 | isSelfClosingTag,
63 | isReact
64 | );
65 | if (!parent) {
66 | return undefined;
67 | }
68 | if (parent.tagName === tagName) {
69 | return undefined;
70 | }
71 | if (parent.tagName !== oldTagName) {
72 | return undefined;
73 | }
74 | if (!parent.seenRightAngleBracket) {
75 | return undefined;
76 | }
77 | const startOffset = parent.offset;
78 | const endOffset = parent.offset + parent.tagName.length;
79 | return {
80 | startOffset,
81 | endOffset,
82 | tagName
83 | };
84 | } else {
85 | scanner.stream.goTo(offset + 1);
86 | const tagName = newWord.slice(1);
87 | const oldTagName = oldWord.slice(1);
88 | if (oldTagName.startsWith('script') || oldTagName.startsWith('style')) {
89 | const hasAdvanced = scanner.stream.advanceUntilEitherChar(
90 | ['>'],
91 | true,
92 | isReact
93 | );
94 | if (!hasAdvanced) {
95 | return undefined;
96 | }
97 | const match = text
98 | .slice(scanner.stream.position)
99 | .match(new RegExp(`${oldTagName}`));
100 | if (!match) {
101 | return undefined;
102 | }
103 | const index = match.index as number;
104 | return {
105 | startOffset: scanner.stream.position + index + 2,
106 | endOffset: scanner.stream.position + index + 2 + oldTagName.length,
107 | tagName
108 | };
109 | }
110 | const hasAdvanced = scanner.stream.advanceUntilEitherChar(
111 | ['<', '>'],
112 | true,
113 | isReact
114 | );
115 | // if start tag is not closed, return undefined
116 | if (scanner.stream.peekRight(0) === '<') {
117 | return undefined;
118 | }
119 | if (!hasAdvanced) {
120 | return undefined;
121 | }
122 | if (scanner.stream.peekLeft(1) === '/') {
123 | return undefined;
124 | }
125 | const possibleEndOfStartTag = scanner.stream.position;
126 | // check if we might be at an end tag
127 | while (scanner.stream.peekLeft(1).match(/[a-zA-Z\-\:]/)) {
128 | scanner.stream.goBack(1);
129 | if (scanner.stream.peekLeft(1) === '/') {
130 | return undefined;
131 | }
132 | }
133 | scanner.stream.goTo(possibleEndOfStartTag);
134 | scanner.stream.advance(1);
135 | const nextClosingTag = getNextClosingTagName(
136 | scanner,
137 | scanner.stream.position,
138 | isSelfClosingTag,
139 | isReact
140 | );
141 | if (!nextClosingTag) {
142 | return undefined;
143 | }
144 | if (nextClosingTag.tagName === tagName) {
145 | return undefined;
146 | }
147 | if (nextClosingTag.tagName !== oldTagName) {
148 | return undefined;
149 | }
150 | const previousOpenTag = getPreviousOpeningTagName(
151 | scanner,
152 | offset,
153 | isSelfClosingTag,
154 | isReact
155 | );
156 |
157 | if (
158 | previousOpenTag &&
159 | previousOpenTag.tagName === oldTagName &&
160 | previousOpenTag.indent === nextClosingTag.indent
161 | ) {
162 | return undefined;
163 | }
164 |
165 | const startOffset = nextClosingTag.offset;
166 | const endOffset = nextClosingTag.offset + nextClosingTag.tagName.length;
167 |
168 | return {
169 | startOffset,
170 | endOffset,
171 | tagName
172 | };
173 | }
174 | };
175 |
176 | // const testCase = {
177 | // text: '',
178 | // offset: 8,
179 | // newWord: '
192 | // `,
195 | // 9,
196 | // '']],
8 | ruby: [
9 | ['<%=', '%>'],
10 | ['"', '"'],
11 | ["'", "'"]
12 | ],
13 | html: [
14 | [''],
15 | ['"', '"'],
16 | ["'", "'"],
17 | ['