Skip to content

Commit d244878

Browse files
authoredMar 4, 2020
Merge pull request #192 from mads-hartmann/word-at-point
Improve completion handler and wordAtPoint
2 parents 6afc5df + 71338ad commit d244878

12 files changed

+294
-60
lines changed
 

‎.eslintignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
**/out
22
**/node_modules
33
!.eslintrc.js
4+
coverage

‎server/CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Bash Language Server
22

3+
## 1.10.0
4+
5+
* Improved completion handler and support auto-completion and documentation for [bash reserved words](https://www.gnu.org/software/bash/manual/html_node/Reserved-Word-Index.html) (https://github.com/mads-hartmann/bash-language-server/pull/192)
6+
37
## 1.9.0
48

59
* Skip analyzing files with a non-bash shebang

‎server/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"description": "A language server for Bash",
44
"author": "Mads Hartmann",
55
"license": "MIT",
6-
"version": "1.9.0",
6+
"version": "1.10.0",
77
"publisher": "mads-hartmann",
88
"main": "./out/server.js",
99
"typings": "./out/server.d.ts",

‎server/src/__tests__/__snapshots__/analyzer.test.ts.snap

+16-16
Original file line numberDiff line numberDiff line change
@@ -131,127 +131,127 @@ Array [
131131
Object {
132132
"data": Object {
133133
"name": "ret",
134-
"type": "function",
134+
"type": 3,
135135
},
136136
"kind": 6,
137137
"label": "ret",
138138
},
139139
Object {
140140
"data": Object {
141141
"name": "configures",
142-
"type": "function",
142+
"type": 3,
143143
},
144144
"kind": 6,
145145
"label": "configures",
146146
},
147147
Object {
148148
"data": Object {
149149
"name": "npm_config_loglevel",
150-
"type": "function",
150+
"type": 3,
151151
},
152152
"kind": 6,
153153
"label": "npm_config_loglevel",
154154
},
155155
Object {
156156
"data": Object {
157157
"name": "node",
158-
"type": "function",
158+
"type": 3,
159159
},
160160
"kind": 6,
161161
"label": "node",
162162
},
163163
Object {
164164
"data": Object {
165165
"name": "TMP",
166-
"type": "function",
166+
"type": 3,
167167
},
168168
"kind": 6,
169169
"label": "TMP",
170170
},
171171
Object {
172172
"data": Object {
173173
"name": "BACK",
174-
"type": "function",
174+
"type": 3,
175175
},
176176
"kind": 6,
177177
"label": "BACK",
178178
},
179179
Object {
180180
"data": Object {
181181
"name": "tar",
182-
"type": "function",
182+
"type": 3,
183183
},
184184
"kind": 6,
185185
"label": "tar",
186186
},
187187
Object {
188188
"data": Object {
189189
"name": "MAKE",
190-
"type": "function",
190+
"type": 3,
191191
},
192192
"kind": 6,
193193
"label": "MAKE",
194194
},
195195
Object {
196196
"data": Object {
197197
"name": "make",
198-
"type": "function",
198+
"type": 3,
199199
},
200200
"kind": 6,
201201
"label": "make",
202202
},
203203
Object {
204204
"data": Object {
205205
"name": "clean",
206-
"type": "function",
206+
"type": 3,
207207
},
208208
"kind": 6,
209209
"label": "clean",
210210
},
211211
Object {
212212
"data": Object {
213213
"name": "node_version",
214-
"type": "function",
214+
"type": 3,
215215
},
216216
"kind": 6,
217217
"label": "node_version",
218218
},
219219
Object {
220220
"data": Object {
221221
"name": "t",
222-
"type": "function",
222+
"type": 3,
223223
},
224224
"kind": 6,
225225
"label": "t",
226226
},
227227
Object {
228228
"data": Object {
229229
"name": "url",
230-
"type": "function",
230+
"type": 3,
231231
},
232232
"kind": 6,
233233
"label": "url",
234234
},
235235
Object {
236236
"data": Object {
237237
"name": "ver",
238-
"type": "function",
238+
"type": 3,
239239
},
240240
"kind": 6,
241241
"label": "ver",
242242
},
243243
Object {
244244
"data": Object {
245245
"name": "isnpm10",
246-
"type": "function",
246+
"type": 3,
247247
},
248248
"kind": 6,
249249
"label": "isnpm10",
250250
},
251251
Object {
252252
"data": Object {
253253
"name": "NODE",
254-
"type": "function",
254+
"type": 3,
255255
},
256256
"kind": 6,
257257
"label": "NODE",

‎server/src/__tests__/analyzer.test.ts

+11-2
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,17 @@ describe('wordAtPoint', () => {
8686
it('returns current word at a given point', () => {
8787
analyzer.analyze(CURRENT_URI, FIXTURES.INSTALL)
8888
expect(analyzer.wordAtPoint(CURRENT_URI, 25, 5)).toEqual('rm')
89-
// FIXME: seems like there is an issue here:
90-
// expect(analyzer.wordAtPoint(CURRENT_URI, 24, 4)).toEqual('else')
89+
90+
// FIXME: grammar issue: else is not found
91+
// expect(analyzer.wordAtPoint(CURRENT_URI, 24, 5)).toEqual('else')
92+
93+
expect(analyzer.wordAtPoint(CURRENT_URI, 30, 1)).toEqual(null)
94+
95+
expect(analyzer.wordAtPoint(CURRENT_URI, 30, 3)).toEqual('ret')
96+
expect(analyzer.wordAtPoint(CURRENT_URI, 30, 4)).toEqual('ret')
97+
expect(analyzer.wordAtPoint(CURRENT_URI, 30, 5)).toEqual('ret')
98+
99+
expect(analyzer.wordAtPoint(CURRENT_URI, 38, 5)).toEqual('configures')
91100
})
92101
})
93102

‎server/src/__tests__/server.test.ts

+43-5
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import * as lsp from 'vscode-languageserver'
22

33
import { FIXTURE_FOLDER, FIXTURE_URI } from '../../../testing/fixtures'
44
import LspServer from '../server'
5+
import { CompletionItemDataType } from '../types'
56

67
async function initializeServer() {
78
const diagnostics: Array<lsp.PublishDiagnosticsParams | undefined> = undefined
@@ -130,7 +131,7 @@ describe('server', () => {
130131
})
131132
})
132133

133-
it('responds to onCompletion when word is found', async () => {
134+
it('responds to onCompletion with filtered list when word is found', async () => {
134135
const { connection, server } = await initializeServer()
135136
server.register(connection)
136137

@@ -142,18 +143,55 @@ describe('server', () => {
142143
uri: FIXTURE_URI.INSTALL,
143144
},
144145
position: {
146+
// rm
145147
line: 25,
146148
character: 5,
147149
},
148150
},
149151
{} as any,
150152
)
151153

154+
// Limited set
155+
expect('length' in result && result.length < 5).toBe(true)
156+
expect(result).toEqual(
157+
expect.arrayContaining([
158+
{
159+
data: {
160+
name: 'rm',
161+
type: CompletionItemDataType.Executable,
162+
},
163+
kind: expect.any(Number),
164+
label: 'rm',
165+
},
166+
]),
167+
)
168+
})
169+
170+
it('responds to onCompletion with entire list when no word is found', async () => {
171+
const { connection, server } = await initializeServer()
172+
server.register(connection)
173+
174+
const onCompletion = connection.onCompletion.mock.calls[0][0]
175+
176+
const result = await onCompletion(
177+
{
178+
textDocument: {
179+
uri: FIXTURE_URI.INSTALL,
180+
},
181+
position: {
182+
// else
183+
line: 24,
184+
character: 5,
185+
},
186+
},
187+
{} as any,
188+
)
189+
152190
// Entire list
153-
expect('length' in result && result.length > 50)
191+
expect('length' in result && result.length > 50).toBe(true)
154192
})
155193

156-
it('responds to onCompletion when no word is found', async () => {
194+
it('responds to onCompletion with empty list when word is a comment', async () => {
157195
const { connection, server } = await initializeServer()
158196
server.register(connection)
159197

@@ -165,14 +203,14 @@ describe('server', () => {
165203
uri: FIXTURE_URI.INSTALL,
166204
},
167205
position: {
206+
// inside comment
168207
line: 2,
169208
character: 1,
170209
},
171210
},
172211
{} as any,
173212
)
174213

175-
// Entire list
176-
expect('length' in result && result.length > 50)
214+
expect(result).toEqual([])
177215
})
178216
})

‎server/src/analyser.ts

+23-10
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import * as LSP from 'vscode-languageserver'
55
import * as Parser from 'web-tree-sitter'
66

77
import { getGlobPattern } from './config'
8+
import { BashCompletionItem, CompletionItemDataType } from './types'
89
import { uniqueBasedOnHash } from './util/array'
910
import { flattenArray, flattenObjectValues } from './util/flatten'
1011
import { getFilePaths } from './util/fs'
@@ -118,17 +119,17 @@ export default class Analyzer {
118119
}
119120

120121
public async getExplainshellDocumentation({
121-
pos,
122+
params,
122123
endpoint,
123124
}: {
124-
pos: LSP.TextDocumentPositionParams
125+
params: LSP.TextDocumentPositionParams
125126
endpoint: string
126127
}): Promise<any> {
127128
const leafNode = this.uriToTreeSitterTrees[
128-
pos.textDocument.uri
129+
params.textDocument.uri
129130
].rootNode.descendantForPosition({
130-
row: pos.position.line,
131-
column: pos.position.character,
131+
row: params.position.line,
132+
column: params.position.character,
132133
})
133134

134135
// explainshell needs the whole command, not just the "word" (tree-sitter
@@ -138,7 +139,7 @@ export default class Analyzer {
138139
// encounters newlines.
139140
const interestingNode = leafNode.type === 'word' ? leafNode.parent : leafNode
140141

141-
const cmd = this.uriToFileContent[pos.textDocument.uri].slice(
142+
const cmd = this.uriToFileContent[params.textDocument.uri].slice(
142143
interestingNode.startIndex,
143144
interestingNode.endIndex,
144145
)
@@ -162,7 +163,7 @@ export default class Analyzer {
162163
return { ...response, status: 'error' }
163164
} else {
164165
const offsetOfMousePointerInCommand =
165-
this.uriToTextDocument[pos.textDocument.uri].offsetAt(pos.position) -
166+
this.uriToTextDocument[params.textDocument.uri].offsetAt(params.position) -
166167
interestingNode.startIndex
167168

168169
const match = explainshellResponse.matches.find(
@@ -232,7 +233,7 @@ export default class Analyzer {
232233
/**
233234
* Find unique symbol completions for the given file.
234235
*/
235-
public findSymbolCompletions(uri: string): LSP.CompletionItem[] {
236+
public findSymbolCompletions(uri: string): BashCompletionItem[] {
236237
const hashFunction = ({ name, kind }: LSP.SymbolInformation) => `${name}${kind}`
237238

238239
return uniqueBasedOnHash(this.findSymbols(uri), hashFunction).map(
@@ -241,7 +242,7 @@ export default class Analyzer {
241242
kind: this.symbolKindToCompletionKind(symbol.kind),
242243
data: {
243244
name: symbol.name,
244-
type: 'function',
245+
type: CompletionItemDataType.Symbol,
245246
},
246247
}),
247248
)
@@ -328,13 +329,25 @@ export default class Analyzer {
328329
const document = this.uriToTreeSitterTrees[uri]
329330
const contents = this.uriToFileContent[uri]
330331

331-
const node = document.rootNode.namedDescendantForPosition({ row: line, column })
332+
if (!document.rootNode) {
333+
// Check for lacking rootNode (due to failed parse?)
334+
return null
335+
}
336+
337+
const point = { row: line, column }
338+
339+
const node = TreeSitterUtil.namedLeafDescendantForPosition(point, document.rootNode)
340+
341+
if (!node) {
342+
return null
343+
}
332344

333345
const start = node.startIndex
334346
const end = node.endIndex
335347
const name = contents.slice(start, end)
336348

337349
// Hack. Might be a problem with the grammar.
350+
// TODO: Document this with a test case
338351
if (name.endsWith('=')) {
339352
return name.slice(0, name.length - 1)
340353
}

‎server/src/builtins.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,10 @@ export const LIST = [
6363
'wait',
6464
]
6565

66+
const SET = new Set(LIST)
67+
6668
export function isBuiltin(word: string): boolean {
67-
return LIST.find(builtin => builtin === word) !== undefined
69+
return SET.has(word)
6870
}
6971

7072
export async function documentation(builtin: string): Promise<string> {

0 commit comments

Comments
 (0)
Please sign in to comment.