Skip to content

Commit

Permalink
Merge pull request #294 from otreblan/master
Browse files Browse the repository at this point in the history
Complete command line arguments
  • Loading branch information
skovhus authored Aug 11, 2021
2 parents a16b288 + bf5e329 commit 2b4d8a6
Show file tree
Hide file tree
Showing 7 changed files with 184 additions and 4 deletions.
20 changes: 19 additions & 1 deletion server/src/__tests__/analyzer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,24 @@ describe('wordAtPoint', () => {
})
})

describe('commandNameAtPoint', () => {
it('returns current command name at a given point', () => {
analyzer.analyze(CURRENT_URI, FIXTURES.INSTALL)
expect(analyzer.commandNameAtPoint(CURRENT_URI, 15, 0)).toEqual(null)

expect(analyzer.commandNameAtPoint(CURRENT_URI, 20, 2)).toEqual('curl')
expect(analyzer.commandNameAtPoint(CURRENT_URI, 20, 15)).toEqual('curl')
expect(analyzer.commandNameAtPoint(CURRENT_URI, 20, 19)).toEqual('curl')

expect(analyzer.commandNameAtPoint(CURRENT_URI, 26, 4)).toEqual('echo')
expect(analyzer.commandNameAtPoint(CURRENT_URI, 26, 9)).toEqual('echo')

expect(analyzer.commandNameAtPoint(CURRENT_URI, 38, 13)).toEqual('env')
expect(analyzer.commandNameAtPoint(CURRENT_URI, 38, 24)).toEqual('grep')
expect(analyzer.commandNameAtPoint(CURRENT_URI, 38, 44)).toEqual('sed')
})
})

describe('findSymbolCompletions', () => {
it('return a list of symbols across the workspace', () => {
analyzer.analyze('install.sh', FIXTURES.INSTALL)
Expand Down Expand Up @@ -250,7 +268,7 @@ describe('fromRoot', () => {
expect(connection.window.showWarningMessage).not.toHaveBeenCalled()

// if you add a .sh file to testing/fixtures, update this value
const FIXTURE_FILES_MATCHING_GLOB = 11
const FIXTURE_FILES_MATCHING_GLOB = 12

// Intro, stats on glob, one file skipped due to shebang, and outro
const LOG_LINES = FIXTURE_FILES_MATCHING_GLOB + 4
Expand Down
35 changes: 35 additions & 0 deletions server/src/__tests__/server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,41 @@ describe('server', () => {
)
})

it('responds to onCompletion with options list when command name is found', async () => {
const { connection, server } = await initializeServer()
server.register(connection)

const onCompletion = connection.onCompletion.mock.calls[0][0]

const result = await onCompletion(
{
textDocument: {
uri: FIXTURE_URI.OPTIONS,
},
position: {
// grep --line-
line: 2,
character: 12,
},
},
{} as any,
{} as any,
)

expect(result).toEqual(
expect.arrayContaining([
{
data: {
name: expect.stringMatching(RegExp('--line-.*')),
type: CompletionItemDataType.Symbol,
},
kind: expect.any(Number),
label: expect.stringMatching(RegExp('--line-.*')),
},
]),
)
})

it('responds to onCompletion with entire list when no word is found', async () => {
const { connection, server } = await initializeServer()
server.register(connection)
Expand Down
40 changes: 37 additions & 3 deletions server/src/analyser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -367,17 +367,28 @@ export default class Analyzer {
}

/**
* Find the full word at the given point.
* Find the node at the given point.
*/
public wordAtPoint(uri: string, line: number, column: number): string | null {
private nodeAtPoint(
uri: string,
line: number,
column: number,
): Parser.SyntaxNode | null {
const document = this.uriToTreeSitterTrees[uri]

if (!document.rootNode) {
// Check for lacking rootNode (due to failed parse?)
return null
}

const node = document.rootNode.descendantForPosition({ row: line, column })
return document.rootNode.descendantForPosition({ row: line, column })
}

/**
* Find the full word at the given point.
*/
public wordAtPoint(uri: string, line: number, column: number): string | null {
const node = this.nodeAtPoint(uri, line, column)

if (!node || node.childCount > 0 || node.text.trim() === '') {
return null
Expand All @@ -386,6 +397,29 @@ export default class Analyzer {
return node.text.trim()
}

/**
* Find the name of the command at the given point.
*/
public commandNameAtPoint(uri: string, line: number, column: number): string | null {
let node = this.nodeAtPoint(uri, line, column)

while (node && node.type !== 'command') {
node = node.parent
}

if (!node) {
return null
}

const firstChild = node.firstNamedChild

if (!firstChild || firstChild.type !== 'command_name') {
return null
}

return firstChild.text.trim()
}

/**
* Find a block of comments above a line position
*/
Expand Down
32 changes: 32 additions & 0 deletions server/src/get-options.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#!/usr/bin/env bash

DATADIR="$(pkg-config --variable=datadir bash-completion)"

# Exit if bash-completion isn't installed.
if (( $? != 0 ))
then
exit 1
fi

source "$DATADIR/bash-completion/bash_completion"

COMP_LINE="$*"
COMP_WORDS=("$@")
COMP_CWORD="${#COMP_WORDS[@]}"
((COMP_CWORD--))
COMP_POINT="${#COMP_LINE}"
COMP_WORDBREAKS='"'"'><=;|&(:"

_command_offset 0 2> /dev/null

if (( ${#COMPREPLY[@]} == 0 ))
then
# Disabled by default because _longopt executes the program
# to get its options.
if (( ${BASH_LSP_COMPLETE_LONGOPTS} == 1 ))
then
_longopt "${COMP_WORDS[0]}"
fi
fi

printf "%s\t" "${COMPREPLY[@]}"
56 changes: 56 additions & 0 deletions server/src/server.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import * as Process from 'child_process'
import * as path from 'path'
import * as Path from 'path'
import * as TurndownService from 'turndown'
import * as LSP from 'vscode-languageserver'
import { TextDocument } from 'vscode-languageserver-textdocument'
Expand Down Expand Up @@ -123,6 +125,16 @@ export default class BashServer {
)
}

private getCommandNameAtPoint(
params: LSP.ReferenceParams | LSP.TextDocumentPositionParams,
): string | null {
return this.analyzer.commandNameAtPoint(
params.textDocument.uri,
params.position.line,
params.position.character,
)
}

private logRequest({
request,
params,
Expand Down Expand Up @@ -323,6 +335,22 @@ export default class BashServer {
return []
}

let options: string[] = []
if (word && word.startsWith('-')) {
const commandName = this.getCommandNameAtPoint({
...params,
position: {
line: params.position.line,
// Go one character back to get completion on the current word
character: Math.max(params.position.character - 1, 0),
},
})

if (commandName) {
options = getCommandOptions(commandName, word)
}
}

const currentUri = params.textDocument.uri

// TODO: an improvement here would be to detect if the current word is
Expand Down Expand Up @@ -385,11 +413,21 @@ export default class BashServer {
},
}))

const optionsCompletions = options.map(option => ({
label: option,
kind: LSP.SymbolKind.Interface,
data: {
name: option,
type: CompletionItemDataType.Symbol,
},
}))

const allCompletions = [
...reservedWordsCompletions,
...symbolCompletions,
...programCompletions,
...builtinsCompletions,
...optionsCompletions,
]

if (word) {
Expand Down Expand Up @@ -531,3 +569,21 @@ const getMarkdownContent = (documentation: string): LSP.MarkupContent => ({
// Passed as markdown for syntax highlighting
kind: 'markdown' as const,
})

function getCommandOptions(name: string, word: string): string[] {
// TODO: The options could be cached.
const options = Process.spawnSync(Path.join(__dirname, '../src/get-options.sh'), [
name,
word,
])

if (options.status !== 0) {
return []
}

return options.stdout
.toString()
.split('\t')
.map(l => l.trim())
.filter(l => l.length > 0)
}
2 changes: 2 additions & 0 deletions testing/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export const FIXTURE_URI = {
PARSE_PROBLEMS: `file://${path.join(FIXTURE_FOLDER, 'parse-problems.sh')}`,
SOURCING: `file://${path.join(FIXTURE_FOLDER, 'sourcing.sh')}`,
COMMENT_DOC: `file://${path.join(FIXTURE_FOLDER, 'comment-doc-on-hover.sh')}`,
OPTIONS: `file://${path.join(FIXTURE_FOLDER, 'options.sh')}`,
}

export const FIXTURE_DOCUMENT = {
Expand All @@ -30,6 +31,7 @@ export const FIXTURE_DOCUMENT = {
PARSE_PROBLEMS: getDocument(FIXTURE_URI.PARSE_PROBLEMS),
SOURCING: getDocument(FIXTURE_URI.SOURCING),
COMMENT_DOC: getDocument(FIXTURE_URI.COMMENT_DOC),
OPTIONS: getDocument(FIXTURE_URI.OPTIONS),
}

export default FIXTURE_DOCUMENT
3 changes: 3 additions & 0 deletions testing/fixtures/options.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
grep -
grep --
grep --line-

0 comments on commit 2b4d8a6

Please # to comment.