Skip to content

Commit 2d75888

Browse files
committed
fix: make snippets configurable
1 parent 101da6b commit 2d75888

35 files changed

+287
-115
lines changed

.vscodeignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
*
22
*/**
33
**/.DS_Store
4-
!out/*.json
4+
!out/*.js
5+
out/test
56
!node_modules/vscode-nls/**/*
67
!node_modules/vscode-debugprotocol/**/*
78
!package.json

README.md

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,6 @@ snippets for Material-UI
1111
# Snippets
1212

1313
<!-- snippets -->
14-
15-
## javascriptreact
16-
1714
### `mui-button-group-vertical`: Material-UI vertical &lt;ButtonGroup&gt;
1815

1916
```
@@ -292,5 +289,4 @@ snippets for Material-UI
292289
$0
293290
/>
294291
```
295-
296292
<!-- snippetsend -->

package.json

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,33 @@
77
"engines": {
88
"vscode": "^1.40.0"
99
},
10+
"main": "./out/extension.js",
11+
"activationEvents": [
12+
"onLanguage:javascriptreact",
13+
"onLanguage:typescriptreact"
14+
],
15+
"contributes": {
16+
"configuration": [
17+
{
18+
"title": "Material-UI Snippets",
19+
"properties": {
20+
"material-ui-snippets.formControlMode": {
21+
"type": "string",
22+
"default": "controlled",
23+
"markdownDescription": "Whether to use controlled or uncontrolled inputs and other form controls in snippets",
24+
"enum": [
25+
"controlled",
26+
"uncontrolled"
27+
],
28+
"enumDescriptions": [
29+
"Includes value and onChange properties in form controls",
30+
"Includes defaultValue property in form controls"
31+
]
32+
}
33+
}
34+
}
35+
]
36+
},
1037
"categories": [
1138
"Snippets"
1239
],
@@ -18,7 +45,8 @@
1845
"prettier:check": "prettier --list-different .babelrc.js *.json *.md *.ts 'src/**/*.{js,ts}'",
1946
"tsc": "tsc --noEmit",
2047
"clean": "rimraf out",
21-
"build": "babel-node --extensions \".js,.ts\" src/buildSnippets.ts && BABEL_ENV=coverage babel src --out-dir out --extensions \".js,.ts\"",
48+
"build": "babel-node --extensions \".js,.ts\" scripts/buildReadme.ts && babel src --out-dir out --extensions \".js,.ts\"",
49+
"build:watch": "nodemon -e js,ts,json --watch src --watch .babelrc.js --exec \"(babel-node --extensions '.js,.ts' scripts/buildReadme.ts && babel src --out-dir out --extensions '.js,.ts'; exit 0)\"",
2250
"pretest": "npm run build",
2351
"test": "rimraf .nyc_output; node ./out/test/runTest.js; nyc report; nyc report --reporter=lcov",
2452
"codecov": "nyc report --reporter=text-lcov > coverage.lcov; codecov",
@@ -160,13 +188,5 @@
160188
"major": {
161189
"automerge": false
162190
}
163-
},
164-
"contributes": {
165-
"snippets": [
166-
{
167-
"language": "javascriptreact",
168-
"path": "./out/javascriptreact.json"
169-
}
170-
]
171191
}
172192
}

scripts/buildReadme.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/* eslint-disable @typescript-eslint/no-explicit-any */
2+
3+
import * as fs from 'fs-extra'
4+
import * as path from 'path'
5+
import markdownEscape from 'markdown-escape'
6+
import snippets from '../src/snippets'
7+
8+
const root = path.resolve(__dirname, '..')
9+
const out = path.resolve(root, 'out')
10+
fs.mkdirsSync(out)
11+
12+
const markdown: Array<string> = []
13+
14+
for (const snippet of Object.values(snippets)) {
15+
const { prefix, description } = snippet
16+
const body =
17+
typeof snippet.body === 'function'
18+
? snippet.body({
19+
language: 'typescriptreact',
20+
formControlMode: 'controlled',
21+
})
22+
: snippet.body
23+
markdown.push(`### \`${prefix}\`: ${markdownEscape(description)}`)
24+
markdown.push('```\n' + body.replace(/^\n|\n$/gm, '') + '\n```')
25+
}
26+
27+
const oldReadme = fs.readFileSync(path.join(root, 'README.md'), 'utf8')
28+
const startComment = /<!--\s*snippets\s*-->/.exec(oldReadme)
29+
const endComment = /<!--\s*snippetsend\s*-->/.exec(oldReadme)
30+
if (startComment && endComment && endComment.index > startComment.index) {
31+
const newReadme = `${oldReadme.substring(
32+
0,
33+
startComment.index + startComment[0].length
34+
)}
35+
${markdown.join('\n\n')}
36+
${oldReadme.substring(endComment.index)}`
37+
if (newReadme !== oldReadme) {
38+
fs.writeFileSync(path.join(root, 'README.md'), newReadme, 'utf8')
39+
}
40+
console.log('README.md') // eslint-disable-line no-console
41+
}

src/buildSnippets.ts

Lines changed: 0 additions & 76 deletions
This file was deleted.

src/extension.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import * as vscode from 'vscode'
2+
import snippets from './snippets'
3+
4+
export function activate(context: vscode.ExtensionContext): void {
5+
for (const language of ['javascriptreact', 'typescriptreact']) {
6+
context.subscriptions.push(
7+
vscode.languages.registerCompletionItemProvider(language, {
8+
provideCompletionItems(
9+
/* eslint-disable @typescript-eslint/no-unused-vars */
10+
document: vscode.TextDocument,
11+
position: vscode.Position,
12+
token: vscode.CancellationToken,
13+
context: vscode.CompletionContext
14+
/* eslint-enable @typescript-eslint/no-unused-vars */
15+
): vscode.ProviderResult<
16+
vscode.CompletionItem[] | vscode.CompletionList
17+
> {
18+
const result = []
19+
for (const snippet of Object.values(snippets)) {
20+
const { prefix, description } = snippet
21+
const body = (typeof snippet.body === 'function'
22+
? snippet.body({
23+
language: 'typescriptreact',
24+
formControlMode:
25+
vscode.workspace
26+
.getConfiguration('material-ui-snippets')
27+
.get('formControlMode') || 'controlled',
28+
})
29+
: snippet.body
30+
).replace(/^\n|\n$/gm, '')
31+
const snippetCompletion = new vscode.CompletionItem(prefix)
32+
snippetCompletion.insertText = new vscode.SnippetString(body)
33+
snippetCompletion.documentation = new vscode.MarkdownString(
34+
description
35+
)
36+
result.push(snippetCompletion)
37+
}
38+
return result
39+
},
40+
})
41+
)
42+
}
43+
}

src/snippets/index.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/* eslint-disable @typescript-eslint/no-explicit-any */
2+
3+
import requireGlob from 'require-glob'
4+
import path from 'path'
5+
6+
export type SnippetOptions = {
7+
language: 'javascriptreact' | 'typescriptreact'
8+
formControlMode: 'controlled' | 'uncontrolled'
9+
}
10+
11+
export type SnippetBodyFunction = (options: SnippetOptions) => string
12+
13+
export type SnippetBody = string | SnippetBodyFunction
14+
15+
export type Snippet = {
16+
prefix: string
17+
description: string
18+
body: SnippetBody
19+
}
20+
21+
const snippets: Record<string, Snippet> = requireGlob.sync('./*.{js,ts}', {
22+
reducer: (
23+
options: Record<string, any>,
24+
result: Record<string, any>,
25+
file: { path: string; exports: any }
26+
) => {
27+
if (file.path === __filename) return result
28+
const filename = path.basename(file.path)
29+
const filenameNoExt = filename.replace(/\.[^.]+$/, '')
30+
const { prefix = filenameNoExt, description, body } = file.exports
31+
if (!prefix || typeof prefix !== 'string') {
32+
throw new Error(
33+
`src/snippets/${filename}: prefix must be a string if exported`
34+
)
35+
}
36+
if (!description || typeof description !== 'string') {
37+
throw new Error(
38+
`src/snippets/${filename}: must export a string description`
39+
)
40+
}
41+
if (!body || (typeof body !== 'string' && typeof body !== 'function')) {
42+
throw new Error(
43+
`src/snippets/${filename}: must export a function or string body`
44+
)
45+
}
46+
result[filenameNoExt] = { prefix, description, body }
47+
return result
48+
},
49+
})
50+
51+
export default snippets

src/snippets/javascriptreact/mui-switch.ts

Lines changed: 0 additions & 11 deletions
This file was deleted.
File renamed without changes.
File renamed without changes.

0 commit comments

Comments
 (0)