Skip to content

Commit 499dddc

Browse files
committed
Move snippets to server
1 parent 387d96f commit 499dddc

File tree

7 files changed

+202
-251
lines changed

7 files changed

+202
-251
lines changed

Diff for: server/src/__tests__/snippets.test.ts

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { MarkupContent } from 'vscode-languageserver'
2+
3+
import { SNIPPETS } from '../snippets'
4+
5+
describe('snippets', () => {
6+
it('should have unique labels', () => {
7+
const labels = SNIPPETS.map((snippet) => snippet.label)
8+
const uniqueLabels = new Set(labels)
9+
expect(labels.length).toBe(uniqueLabels.size)
10+
})
11+
12+
SNIPPETS.forEach(({ label, documentation }) => {
13+
it(`contains the label in the documentation for "${label}"`, () => {
14+
const stringDocumentation = (documentation as MarkupContent)?.value
15+
expect(stringDocumentation).toBeDefined()
16+
if (stringDocumentation) {
17+
expect(stringDocumentation).toContain(label)
18+
const secondLine = stringDocumentation.split('\n')[1]
19+
try {
20+
expect(
21+
secondLine.startsWith(label) || secondLine.startsWith(`[${label}]`),
22+
).toBe(true)
23+
} catch (error) {
24+
// eslint-disable-next-line no-console
25+
console.error(`Did not start with label: ${label}`, secondLine)
26+
throw error
27+
}
28+
}
29+
})
30+
})
31+
})

Diff for: server/src/server.ts

+2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import Executables from './executables'
1414
import { initializeParser } from './parser'
1515
import * as ReservedWords from './reserved-words'
1616
import { Linter } from './shellcheck'
17+
import { SNIPPETS } from './snippets'
1718
import { BashCompletionItem, CompletionItemDataType } from './types'
1819
import { uniqueBasedOnHash } from './util/array'
1920
import { logger, setLogConnection, setLogLevel } from './util/logger'
@@ -541,6 +542,7 @@ export default class BashServer {
541542
...programCompletions,
542543
...builtinsCompletions,
543544
...optionsCompletions,
545+
...SNIPPETS,
544546
]
545547

546548
if (word) {

Diff for: server/src/snippets.ts

+168
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
/**
2+
* Naming convention for `label`:
3+
* - is always a language keyword, builtin name or expansion symbol like `:-`.
4+
* - If a snippet is for a builtin then builtin name is used.
5+
* - If a snippet is for expansion then expansion symbol is used.
6+
* - If a snippet is for a specific external program like **awk** then program name must be added to `prefix` like this:
7+
* `awk:{{snippet-prefix}}`.
8+
*/
9+
import { CompletionItemKind, InsertTextFormat, MarkupKind } from 'vscode-languageserver'
10+
11+
import { BashCompletionItem } from './types'
12+
13+
export const SNIPPETS: BashCompletionItem[] = [
14+
{
15+
label: 'shebang',
16+
insertText: '#!/usr/bin/env ${1|bash,sh|}',
17+
},
18+
{
19+
label: 'if',
20+
insertText: ['if ${1:command}; then', '\t$0', 'fi'].join('\n'),
21+
},
22+
{
23+
label: 'if-else',
24+
insertText: ['if ${1:command}; then', '\t${2:echo}', 'else', '\t$0', 'fi'].join('\n'),
25+
},
26+
{
27+
label: 'while',
28+
insertText: ['while ${1:command}; do', '\t$0', 'done'].join('\n'),
29+
},
30+
{
31+
label: 'until',
32+
insertText: ['until ${1:command}; do', '\t$0', 'done'].join('\n'),
33+
},
34+
{
35+
label: 'for',
36+
insertText: ['for ${1:variable} in ${2:list}; do', '\t$0', 'done'].join('\n'),
37+
},
38+
{
39+
label: 'function',
40+
insertText: ['${1:function_name}() {', '\t$0', '}'].join('\n'),
41+
},
42+
{
43+
label: 'main',
44+
insertText: ['main() {', '\t$0', '}'].join('\n'),
45+
},
46+
{
47+
documentation: '[:-] expansion',
48+
label: ':-',
49+
insertText: '"\\${${1:variable}:-${2:default}}"',
50+
},
51+
{
52+
documentation: '[:=] expansion',
53+
label: ':=',
54+
insertText: '"\\${${1:variable}:=${2:default}}"',
55+
},
56+
{
57+
documentation: '[:?] expansion',
58+
label: ':?',
59+
insertText: '"\\${${1:variable}:?${2:error_message}}"',
60+
},
61+
{
62+
documentation: '[:+] expansion',
63+
label: ':+',
64+
insertText: '"\\${${1:variable}:+${2:alternative}}"',
65+
},
66+
{
67+
documentation: '[#] expansion',
68+
label: '#',
69+
insertText: '"\\${${1:variable}#${2:pattern}}"',
70+
},
71+
{
72+
documentation: '[##] expansion',
73+
label: '##',
74+
insertText: '"\\${${1:variable}##${2:pattern}}"',
75+
},
76+
{
77+
documentation: '[%] expansion',
78+
label: '%',
79+
insertText: '"\\${${1:variable}%${2:pattern}}"',
80+
},
81+
{
82+
documentation: '[%%] expansion',
83+
label: '%%',
84+
insertText: '"\\${${1:variable}%%${2:pattern}}"',
85+
},
86+
{
87+
documentation: '[..] brace expansion',
88+
label: '..',
89+
insertText: '{${1:from}..${2:to}}',
90+
},
91+
{
92+
label: 'echo',
93+
insertText: 'echo "${1:message}"',
94+
},
95+
{
96+
label: 'printf',
97+
insertText: 'printf \'%s\' "${1:message}"',
98+
},
99+
{
100+
label: 'source',
101+
insertText: 'source "${1:path/to/file}"',
102+
},
103+
{
104+
label: 'alias',
105+
insertText: 'alias ${1:name}=${2:value}',
106+
},
107+
{
108+
label: 'cd',
109+
insertText: 'cd "${1:path/to/directory}"',
110+
},
111+
{
112+
label: 'getopts',
113+
insertText: 'getopts ${1:optstring} ${2:name}',
114+
},
115+
{
116+
label: 'jobs',
117+
insertText: 'jobs -x ${1:command}',
118+
},
119+
{
120+
label: 'kill',
121+
insertText: 'kill ${1|-l,-L|}',
122+
},
123+
{
124+
label: 'let',
125+
insertText: 'let ${1:argument}',
126+
},
127+
{
128+
label: 'test',
129+
insertText:
130+
'[[ ${1:argument1} ${2|-ef,-nt,-ot,==,=,!=,=~,<,>,-eq,-ne,-lt,-le,-gt,-ge|} ${3:argument2} ]]',
131+
},
132+
{
133+
documentation: '[dev]ice name',
134+
label: 'dev',
135+
insertText: '/dev/${1|null,stdin,stdout,stderr|}',
136+
},
137+
{
138+
label: 'sed:filter-lines',
139+
insertText:
140+
"sed ${1|--regexp-extended,-E|} ${2|--quiet,-n|} '/${3:pattern}/' ${4:path/to/file}",
141+
},
142+
{
143+
label: 'awk:filter-lines',
144+
insertText: "awk '/${1:pattern}/' ${2:path/to/file}",
145+
},
146+
].map((item) => ({
147+
...item,
148+
documentation: {
149+
value: [
150+
markdownBlock(
151+
`${item.documentation || item.label} (bash-language-server)\n\n`,
152+
'man',
153+
),
154+
markdownBlock(item.insertText, 'bash'),
155+
].join('\n'),
156+
kind: MarkupKind.Markdown,
157+
},
158+
159+
insertTextFormat: InsertTextFormat.Snippet,
160+
data: {
161+
type: CompletionItemKind.Snippet,
162+
},
163+
}))
164+
165+
function markdownBlock(text: string, language: string): string {
166+
const tripleQoute = '```'
167+
return [tripleQoute + language, text, tripleQoute].join('\n')
168+
}

Diff for: server/src/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export enum CompletionItemDataType {
55
Executable,
66
ReservedWord,
77
Symbol,
8+
Snippet,
89
}
910

1011
export interface BashCompletionItem extends LSP.CompletionItem {

Diff for: vscode-client/package.json

-6
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,6 @@
2727
],
2828
"main": "./out/extension",
2929
"contributes": {
30-
"snippets": [
31-
{
32-
"language": "shellscript",
33-
"path": "./snippets/snippets.json"
34-
}
35-
],
3630
"configuration": {
3731
"type": "object",
3832
"title": "Bash IDE configuration",

Diff for: vscode-client/snippets/convention.md

-17
This file was deleted.

0 commit comments

Comments
 (0)