Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 00941c0

Browse files
Merge pull request #45 from chrismwendt/explainshell
Add explainshell integration
2 parents 4dee1c7 + f2ae015 commit 00941c0

File tree

11 files changed

+726
-13
lines changed

11 files changed

+726
-13
lines changed

‎README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Bash Language Server
22

33
Bash language server implementation based on [Tree Sitter][tree-sitter] and its
4-
[grammar for Bash][tree-sitter-bash].
4+
[grammar for Bash][tree-sitter-bash] with [explainshell][explainshell] integration.
55

66
## Features
77

@@ -11,6 +11,7 @@ Bash language server implementation based on [Tree Sitter][tree-sitter] and its
1111
- [x] Highlight occurrences
1212
- [x] Code completion
1313
- [x] Simple diagnostics reporting
14+
- [x] Documentation for flags on hover
1415
- [ ] Rename symbol
1516

1617
## Installation
@@ -44,3 +45,4 @@ Please see [docs/development-guide][dev-guide] for more information.
4445
[vscode-marketplace]: https://marketplace.visualstudio.com/items?itemName=mads-hartmann.bash-ide-vscode
4546
[dev-guide]: https://github.com/mads-hartmann/bash-language-server/blob/master/docs/development-guide.md
4647
[ide-bash]: https://atom.io/packages/ide-bash
48+
[explainshell]: https://explainshell.com/

‎package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@
2424
"tslint-config-prettier": "^1.10.0",
2525
"tslint-plugin-prettier": "^1.3.0",
2626
"tslint": "^5.9.1",
27-
"typescript": "^2.8.1"
27+
"typescript": "^2.8.1",
28+
"vscode-languageserver": "^4.1.1"
2829
},
2930
"jest": {
3031
"testEnvironment": "node",

‎server/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,11 @@
1818
},
1919
"dependencies": {
2020
"glob": "^7.1.2",
21+
"request-promise-native": "^1.0.5",
2122
"tree-sitter": "^0.11.0",
2223
"tree-sitter-bash": "^0.11.0",
24+
"turndown": "^4.0.2",
25+
"urijs": "^1.19.1",
2326
"vscode-languageserver": "^4.1.1"
2427
},
2528
"scripts": {

‎server/src/analyser.ts

Lines changed: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ import * as fs from 'fs'
33
import * as glob from 'glob'
44
import * as Path from 'path'
55

6+
import * as request from 'request-promise-native'
67
import { Document } from 'tree-sitter'
78
import * as bash from 'tree-sitter-bash'
9+
import * as URI from 'urijs'
810
import * as LSP from 'vscode-languageserver'
911

1012
import { uniqueBasedOnHash } from './util/array'
@@ -51,14 +53,19 @@ export default class Analyzer {
5153
const absolute = Path.join(rootPath, p)
5254
const uri = 'file://' + absolute
5355
connection.console.log('Analyzing ' + uri)
54-
analyzer.analyze(uri, fs.readFileSync(absolute, 'utf8'))
56+
analyzer.analyze(
57+
uri,
58+
LSP.TextDocument.create(uri, 'shell', 1, fs.readFileSync(absolute, 'utf8')),
59+
)
5560
})
5661
resolve(analyzer)
5762
}
5863
})
5964
})
6065
}
6166

67+
private uriToTextDocument: { [uri: string]: LSP.TextDocument } = {}
68+
6269
private uriToTreeSitterDocument: Documents = {}
6370

6471
// We need this to find the word at a given point etc.
@@ -84,6 +91,69 @@ export default class Analyzer {
8491
return symbols.map(s => s.location)
8592
}
8693

94+
public async getExplainshellDocumentation({
95+
pos,
96+
endpoint,
97+
}: {
98+
pos: LSP.TextDocumentPositionParams
99+
endpoint: string
100+
}): Promise<any> {
101+
const leafNode = this.uriToTreeSitterDocument[
102+
pos.textDocument.uri
103+
].rootNode.descendantForPosition({
104+
row: pos.position.line,
105+
column: pos.position.character,
106+
})
107+
108+
// explainshell needs the whole command, not just the "word" (tree-sitter
109+
// parlance) that the user hovered over. A relatively successful heuristic
110+
// is to simply go up one level in the AST. If you go up too far, you'll
111+
// start to include newlines, and explainshell completely balks when it
112+
// encounters newlines.
113+
const interestingNode = leafNode.type === 'word' ? leafNode.parent : leafNode
114+
115+
const cmd = this.uriToFileContent[pos.textDocument.uri].slice(
116+
interestingNode.startIndex,
117+
interestingNode.endIndex,
118+
)
119+
120+
const explainshellResponse = await request({
121+
uri: URI(endpoint)
122+
.path('/api/explain')
123+
.addQuery('cmd', cmd)
124+
.toString(),
125+
json: true,
126+
})
127+
128+
// Attaches debugging information to the return value (useful for logging to
129+
// VS Code output).
130+
const response = { ...explainshellResponse, cmd, cmdType: interestingNode.type }
131+
132+
if (explainshellResponse.status === 'error') {
133+
return response
134+
} else if (!explainshellResponse.matches) {
135+
return { ...response, status: 'error' }
136+
} else {
137+
const offsetOfMousePointerInCommand =
138+
this.uriToTextDocument[pos.textDocument.uri].offsetAt(pos.position) -
139+
interestingNode.startIndex
140+
141+
const match = explainshellResponse.matches.find(
142+
helpItem =>
143+
helpItem.start <= offsetOfMousePointerInCommand &&
144+
offsetOfMousePointerInCommand < helpItem.end,
145+
)
146+
147+
const helpHTML = match && match.helpHTML
148+
149+
if (!helpHTML) {
150+
return { ...response, status: 'error' }
151+
}
152+
153+
return { ...response, helpHTML }
154+
}
155+
}
156+
87157
/**
88158
* Find all the locations where something named name has been defined.
89159
*/
@@ -157,12 +227,15 @@ export default class Analyzer {
157227
* Returns all, if any, syntax errors that occurred while parsing the file.
158228
*
159229
*/
160-
public analyze(uri: string, contents: string): LSP.Diagnostic[] {
230+
public analyze(uri: string, document: LSP.TextDocument): LSP.Diagnostic[] {
231+
const contents = document.getText()
232+
161233
const d = new Document()
162234
d.setLanguage(bash)
163235
d.setInputString(contents)
164236
d.parse()
165237

238+
this.uriToTextDocument[uri] = document
166239
this.uriToTreeSitterDocument[uri] = d
167240
this.uriToDeclarations[uri] = {}
168241
this.uriToFileContent[uri] = contents

‎server/src/server.ts

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import * as LSP from 'vscode-languageserver'
22

3+
import * as TurndownService from 'turndown'
34
import Analyzer from './analyser'
45
import * as Builtins from './builtins'
56
import Executables from './executables'
@@ -53,8 +54,7 @@ export default class BashServer {
5354
this.documents.listen(this.connection)
5455
this.documents.onDidChangeContent(change => {
5556
const uri = change.document.uri
56-
const contents = change.document.getText()
57-
const diagnostics = this.analyzer.analyze(uri, contents)
57+
const diagnostics = this.analyzer.analyze(uri, change.document)
5858
connection.sendDiagnostics({
5959
uri: change.document.uri,
6060
diagnostics,
@@ -100,7 +100,7 @@ export default class BashServer {
100100
)
101101
}
102102

103-
private onHover(pos: LSP.TextDocumentPositionParams): Promise<LSP.Hover> {
103+
private asynconHover(pos: LSP.TextDocumentPositionParams): Promise<LSP.Hover> {
104104
this.connection.console.log(
105105
`Hovering over ${pos.position.line}:${pos.position.character}`,
106106
)
@@ -121,6 +121,26 @@ export default class BashServer {
121121
value: doc,
122122
},
123123
}))
124+
} else if (process.env.EXPLAINSHELL_ENDPOINT !== '') {
125+
const response = await this.analyzer.getExplainshellDocumentation({
126+
pos,
127+
endpoint: process.env.EXPLAINSHELL_ENDPOINT,
128+
})
129+
130+
if (response.status === 'error') {
131+
this.connection.console.log(
132+
'getExplainshellDocumentation returned: ' + JSON.stringify(response, null, 4),
133+
)
134+
135+
return null
136+
}
137+
138+
return {
139+
contents: {
140+
kind: 'markdown',
141+
value: new TurndownService().turndown(response.helpHTML),
142+
},
143+
}
124144
} else {
125145
return null
126146
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /