From b26b3899fec8222e26088820790920ac5e1c3819 Mon Sep 17 00:00:00 2001 From: skovhus Date: Thu, 16 Apr 2020 14:01:32 +0200 Subject: [PATCH 1/7] Update fixtures --- testing/fixtures.ts | 10 ++++++---- testing/fixtures/sourcing.sh | 4 ++++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/testing/fixtures.ts b/testing/fixtures.ts index 79fc3b645..1fdd35480 100644 --- a/testing/fixtures.ts +++ b/testing/fixtures.ts @@ -14,18 +14,20 @@ function getDocument(uri: string) { } export const FIXTURE_URI = { - MISSING_NODE: `file://${path.join(FIXTURE_FOLDER, 'missing-node.sh')}`, + INSTALL: `file://${path.join(FIXTURE_FOLDER, 'install.sh')}`, ISSUE101: `file://${path.join(FIXTURE_FOLDER, 'issue101.sh')}`, ISSUE206: `file://${path.join(FIXTURE_FOLDER, 'issue206.sh')}`, - INSTALL: `file://${path.join(FIXTURE_FOLDER, 'install.sh')}`, + MISSING_NODE: `file://${path.join(FIXTURE_FOLDER, 'missing-node.sh')}`, PARSE_PROBLEMS: `file://${path.join(FIXTURE_FOLDER, 'parse-problems.sh')}`, + SOURCING: `file://${path.join(FIXTURE_FOLDER, 'sourcing.sh')}`, } export const FIXTURE_DOCUMENT = { - MISSING_NODE: getDocument(FIXTURE_URI.MISSING_NODE), - ISSUE101: getDocument(FIXTURE_URI.ISSUE101), INSTALL: getDocument(FIXTURE_URI.INSTALL), + ISSUE101: getDocument(FIXTURE_URI.ISSUE101), + MISSING_NODE: getDocument(FIXTURE_URI.MISSING_NODE), PARSE_PROBLEMS: getDocument(FIXTURE_URI.PARSE_PROBLEMS), + SOURCING: getDocument(FIXTURE_URI.SOURCING), } export default FIXTURE_DOCUMENT diff --git a/testing/fixtures/sourcing.sh b/testing/fixtures/sourcing.sh index 7ff024422..488563915 100644 --- a/testing/fixtures/sourcing.sh +++ b/testing/fixtures/sourcing.sh @@ -3,3 +3,7 @@ source ./extension.inc echo $RED 'Hello in red!' + +echo $BLU + +add_a_us From 998afc49afd0cb7ad6b45193d6da4026981ff6c2 Mon Sep 17 00:00:00 2001 From: skovhus Date: Thu, 16 Apr 2020 14:02:45 +0200 Subject: [PATCH 2/7] Remove duplicated completions for executables and builtins --- server/src/server.ts | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/server/src/server.ts b/server/src/server.ts index 53a4119fb..1a5f69c84 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -234,16 +234,19 @@ export default class BashServer { }, })) - const programCompletions = this.executables.list().map((s: string) => { - return { - label: s, - kind: LSP.SymbolKind.Function, - data: { - name: s, - type: CompletionItemDataType.Executable, - }, - } - }) + const programCompletions = this.executables + .list() + .filter(executable => !Builtins.isBuiltin(executable)) + .map(executable => { + return { + label: executable, + kind: LSP.SymbolKind.Function, + data: { + name: executable, + type: CompletionItemDataType.Executable, + }, + } + }) const builtinsCompletions = Builtins.LIST.map(builtin => ({ label: builtin, @@ -254,7 +257,6 @@ export default class BashServer { }, })) - // TODO: we have duplicates here (e.g. echo is both a builtin AND have a man page) const allCompletions = [ ...reservedWordsCompletions, ...symbolCompletions, From e89eab30628bfe5cd3d9ac9794e79aac539e54fa Mon Sep 17 00:00:00 2001 From: skovhus Date: Thu, 16 Apr 2020 14:03:33 +0200 Subject: [PATCH 3/7] Add note for onHover use case --- server/src/server.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/src/server.ts b/server/src/server.ts index 1a5f69c84..13ee071d7 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -177,6 +177,8 @@ export default class BashServer { })) } + // FIXME: could also be a symbol + return null } From 36e8f5b4ff1ce5627f70334914311c27346f34b6 Mon Sep 17 00:00:00 2001 From: skovhus Date: Thu, 16 Apr 2020 14:04:07 +0200 Subject: [PATCH 4/7] Use entire workspace for completion symbols --- .../__snapshots__/analyzer.test.ts.snap | 137 +----------------- server/src/__tests__/analyzer.test.ts | 96 +++++++++++- server/src/__tests__/server.test.ts | 100 ++++++++++++- server/src/analyser.ts | 84 ++--------- server/src/server.ts | 121 +++++++++++++++- testing/fixtures/sourcing.sh | 4 + 6 files changed, 327 insertions(+), 215 deletions(-) diff --git a/server/src/__tests__/__snapshots__/analyzer.test.ts.snap b/server/src/__tests__/__snapshots__/analyzer.test.ts.snap index d02687588..5d2e25cb9 100644 --- a/server/src/__tests__/__snapshots__/analyzer.test.ts.snap +++ b/server/src/__tests__/__snapshots__/analyzer.test.ts.snap @@ -126,140 +126,7 @@ Array [ ] `; -exports[`findSymbolCompletions return a list of symbols 1`] = ` -Array [ - Object { - "data": Object { - "name": "ret", - "type": 3, - }, - "kind": 6, - "label": "ret", - }, - Object { - "data": Object { - "name": "configures", - "type": 3, - }, - "kind": 6, - "label": "configures", - }, - Object { - "data": Object { - "name": "npm_config_loglevel", - "type": 3, - }, - "kind": 6, - "label": "npm_config_loglevel", - }, - Object { - "data": Object { - "name": "node", - "type": 3, - }, - "kind": 6, - "label": "node", - }, - Object { - "data": Object { - "name": "TMP", - "type": 3, - }, - "kind": 6, - "label": "TMP", - }, - Object { - "data": Object { - "name": "BACK", - "type": 3, - }, - "kind": 6, - "label": "BACK", - }, - Object { - "data": Object { - "name": "tar", - "type": 3, - }, - "kind": 6, - "label": "tar", - }, - Object { - "data": Object { - "name": "MAKE", - "type": 3, - }, - "kind": 6, - "label": "MAKE", - }, - Object { - "data": Object { - "name": "make", - "type": 3, - }, - "kind": 6, - "label": "make", - }, - Object { - "data": Object { - "name": "clean", - "type": 3, - }, - "kind": 6, - "label": "clean", - }, - Object { - "data": Object { - "name": "node_version", - "type": 3, - }, - "kind": 6, - "label": "node_version", - }, - Object { - "data": Object { - "name": "t", - "type": 3, - }, - "kind": 6, - "label": "t", - }, - Object { - "data": Object { - "name": "url", - "type": 3, - }, - "kind": 6, - "label": "url", - }, - Object { - "data": Object { - "name": "ver", - "type": 3, - }, - "kind": 6, - "label": "ver", - }, - Object { - "data": Object { - "name": "isnpm10", - "type": 3, - }, - "kind": 6, - "label": "isnpm10", - }, - Object { - "data": Object { - "name": "NODE", - "type": 3, - }, - "kind": 6, - "label": "NODE", - }, -] -`; - -exports[`findSymbols issue 101 1`] = ` +exports[`findSymbolsForFile issue 101 1`] = ` Array [ Object { "kind": 12, @@ -335,7 +202,7 @@ Array [ ] `; -exports[`findSymbols returns a list of SymbolInformation if uri is found 1`] = ` +exports[`findSymbolsForFile returns a list of SymbolInformation if uri is found 1`] = ` Array [ Object { "kind": 13, diff --git a/server/src/__tests__/analyzer.test.ts b/server/src/__tests__/analyzer.test.ts index 166c3f40f..4a1166408 100644 --- a/server/src/__tests__/analyzer.test.ts +++ b/server/src/__tests__/analyzer.test.ts @@ -61,23 +61,23 @@ describe('findReferences', () => { }) }) -describe('findSymbols', () => { +describe('findSymbolsForFile', () => { it('returns empty list if uri is not found', () => { analyzer.analyze(CURRENT_URI, FIXTURES.INSTALL) - const result = analyzer.findSymbols('foobar.sh') + const result = analyzer.findSymbolsForFile({ uri: 'foobar.sh' }) expect(result).toEqual([]) }) it('returns a list of SymbolInformation if uri is found', () => { analyzer.analyze(CURRENT_URI, FIXTURES.INSTALL) - const result = analyzer.findSymbols(CURRENT_URI) + const result = analyzer.findSymbolsForFile({ uri: CURRENT_URI }) expect(result).not.toEqual([]) expect(result).toMatchSnapshot() }) it('issue 101', () => { analyzer.analyze(CURRENT_URI, FIXTURES.ISSUE101) - const result = analyzer.findSymbols(CURRENT_URI) + const result = analyzer.findSymbolsForFile({ uri: CURRENT_URI }) expect(result).not.toEqual([]) expect(result).toMatchSnapshot() }) @@ -102,9 +102,91 @@ describe('wordAtPoint', () => { }) describe('findSymbolCompletions', () => { - it('return a list of symbols', () => { - analyzer.analyze(CURRENT_URI, FIXTURES.INSTALL) - expect(analyzer.findSymbolCompletions(CURRENT_URI)).toMatchSnapshot() + it('return a list of symbols across the workspace', () => { + analyzer.analyze('install.sh', FIXTURES.INSTALL) + analyzer.analyze('sourcing-sh', FIXTURES.SOURCING) + + expect(analyzer.findSymbolsMatchingWord({ word: 'npm_config_logl' })) + .toMatchInlineSnapshot(` + Array [ + Object { + "kind": 13, + "location": Object { + "range": Object { + "end": Object { + "character": 27, + "line": 40, + }, + "start": Object { + "character": 0, + "line": 40, + }, + }, + "uri": "dummy-uri.sh", + }, + "name": "npm_config_loglevel", + }, + Object { + "kind": 13, + "location": Object { + "range": Object { + "end": Object { + "character": 31, + "line": 48, + }, + "start": Object { + "character": 2, + "line": 48, + }, + }, + "uri": "dummy-uri.sh", + }, + "name": "npm_config_loglevel", + }, + Object { + "kind": 13, + "location": Object { + "range": Object { + "end": Object { + "character": 27, + "line": 40, + }, + "start": Object { + "character": 0, + "line": 40, + }, + }, + "uri": "install.sh", + }, + "name": "npm_config_loglevel", + }, + Object { + "kind": 13, + "location": Object { + "range": Object { + "end": Object { + "character": 31, + "line": 48, + }, + "start": Object { + "character": 2, + "line": 48, + }, + }, + "uri": "install.sh", + }, + "name": "npm_config_loglevel", + }, + ] + `) + + expect(analyzer.findSymbolsMatchingWord({ word: 'xxxxxxxx' })).toMatchInlineSnapshot( + `Array []`, + ) + + expect(analyzer.findSymbolsMatchingWord({ word: 'BLU' })).toMatchInlineSnapshot( + `Array []`, + ) }) }) diff --git a/server/src/__tests__/server.test.ts b/server/src/__tests__/server.test.ts index 567958c4e..9a77efd83 100644 --- a/server/src/__tests__/server.test.ts +++ b/server/src/__tests__/server.test.ts @@ -219,7 +219,7 @@ describe('server', () => { {} as any, ) - // Limited set + // Limited set (not using snapshot due to different executables on CI and locally) expect('length' in result && result.length < 5).toBe(true) expect(result).toEqual( expect.arrayContaining([ @@ -281,4 +281,102 @@ describe('server', () => { expect(result).toEqual([]) }) + + it('responds to onCompletion when word is found in another file', async () => { + const { connection, server } = await initializeServer() + server.register(connection) + + const onCompletion = connection.onCompletion.mock.calls[0][0] + + const resultVariable = await onCompletion( + { + textDocument: { + uri: FIXTURE_URI.SOURCING, + }, + position: { + // $BLU (variable) + line: 6, + character: 7, + }, + }, + {} as any, + ) + + expect(resultVariable).toMatchInlineSnapshot(` + Array [ + Object { + "data": Object { + "name": "BLUE", + "type": 3, + }, + "documentation": "Variable defined in ../extension.inc", + "kind": 6, + "label": "BLUE", + }, + ] + `) + + const resultFunction = await onCompletion( + { + textDocument: { + uri: FIXTURE_URI.SOURCING, + }, + position: { + // add_a_us (function) + line: 8, + character: 7, + }, + }, + {} as any, + ) + + expect(resultFunction).toMatchInlineSnapshot(` + Array [ + Object { + "data": Object { + "name": "add_a_user", + "type": 3, + }, + "documentation": "Function defined in ../issue101.sh", + "kind": 3, + "label": "add_a_user", + }, + ] + `) + }) + + it('responds to onCompletion with local symbol when word is found in multiple files', 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.SOURCING, + }, + position: { + // BOL (BOLD is defined in multiple places) + line: 12, + character: 7, + }, + }, + {} as any, + ) + + expect(result).toMatchInlineSnapshot(` + Array [ + Object { + "data": Object { + "name": "BOLD", + "type": 3, + }, + "documentation": undefined, + "kind": 6, + "label": "BOLD", + }, + ] + `) + }) }) diff --git a/server/src/analyser.ts b/server/src/analyser.ts index 0bbd2407c..94311cbe8 100644 --- a/server/src/analyser.ts +++ b/server/src/analyser.ts @@ -7,8 +7,6 @@ import * as LSP from 'vscode-languageserver' import * as Parser from 'web-tree-sitter' import { getGlobPattern } from './config' -import { BashCompletionItem, CompletionItemDataType } from './types' -import { uniqueBasedOnHash } from './util/array' import { flattenArray, flattenObjectValues } from './util/flatten' import { getFilePaths } from './util/fs' import { getShebang, isBashShebang } from './util/shebang' @@ -242,27 +240,27 @@ export default class Analyzer { /** * Find all symbol definitions in the given file. */ - public findSymbols(uri: string): LSP.SymbolInformation[] { + public findSymbolsForFile({ uri }: { uri: string }): LSP.SymbolInformation[] { const declarationsInFile = this.uriToDeclarations[uri] || {} return flattenObjectValues(declarationsInFile) } /** - * Find unique symbol completions for the given file. + * Find symbol completions for the given word. */ - public findSymbolCompletions(uri: string): BashCompletionItem[] { - const hashFunction = ({ name, kind }: LSP.SymbolInformation) => `${name}${kind}` - - return uniqueBasedOnHash(this.findSymbols(uri), hashFunction).map( - (symbol: LSP.SymbolInformation) => ({ - label: symbol.name, - kind: this.symbolKindToCompletionKind(symbol.kind), - data: { - name: symbol.name, - type: CompletionItemDataType.Symbol, - }, - }), - ) + public findSymbolsMatchingWord({ word }: { word: string }): LSP.SymbolInformation[] { + const symbols: LSP.SymbolInformation[] = [] + + Object.keys(this.uriToDeclarations).forEach(uri => { + const declarationsInFile = this.uriToDeclarations[uri] || {} + Object.keys(declarationsInFile).map(name => { + if (name.startsWith(word)) { + declarationsInFile[name].forEach(symbol => symbols.push(symbol)) + } + }) + }) + + return symbols } /** @@ -385,56 +383,4 @@ export default class Analyzer { return symbols } - - private symbolKindToCompletionKind(s: LSP.SymbolKind): LSP.CompletionItemKind { - switch (s) { - case LSP.SymbolKind.File: - return LSP.CompletionItemKind.File - case LSP.SymbolKind.Module: - case LSP.SymbolKind.Namespace: - case LSP.SymbolKind.Package: - return LSP.CompletionItemKind.Module - case LSP.SymbolKind.Class: - return LSP.CompletionItemKind.Class - case LSP.SymbolKind.Method: - return LSP.CompletionItemKind.Method - case LSP.SymbolKind.Property: - return LSP.CompletionItemKind.Property - case LSP.SymbolKind.Field: - return LSP.CompletionItemKind.Field - case LSP.SymbolKind.Constructor: - return LSP.CompletionItemKind.Constructor - case LSP.SymbolKind.Enum: - return LSP.CompletionItemKind.Enum - case LSP.SymbolKind.Interface: - return LSP.CompletionItemKind.Interface - case LSP.SymbolKind.Function: - return LSP.CompletionItemKind.Function - case LSP.SymbolKind.Variable: - return LSP.CompletionItemKind.Variable - case LSP.SymbolKind.Constant: - return LSP.CompletionItemKind.Constant - case LSP.SymbolKind.String: - case LSP.SymbolKind.Number: - case LSP.SymbolKind.Boolean: - case LSP.SymbolKind.Array: - case LSP.SymbolKind.Key: - case LSP.SymbolKind.Null: - return LSP.CompletionItemKind.Text - case LSP.SymbolKind.Object: - return LSP.CompletionItemKind.Module - case LSP.SymbolKind.EnumMember: - return LSP.CompletionItemKind.EnumMember - case LSP.SymbolKind.Struct: - return LSP.CompletionItemKind.Struct - case LSP.SymbolKind.Event: - return LSP.CompletionItemKind.Event - case LSP.SymbolKind.Operator: - return LSP.CompletionItemKind.Operator - case LSP.SymbolKind.TypeParameter: - return LSP.CompletionItemKind.TypeParameter - default: - return LSP.CompletionItemKind.Text - } - } } diff --git a/server/src/server.ts b/server/src/server.ts index 13ee071d7..d6aa1a086 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -1,3 +1,4 @@ +import * as path from 'path' import * as TurndownService from 'turndown' import * as LSP from 'vscode-languageserver' @@ -8,6 +9,7 @@ import Executables from './executables' import { initializeParser } from './parser' import * as ReservedWords from './reservedWords' import { BashCompletionItem, CompletionItemDataType } from './types' +import { uniqueBasedOnHash } from './util/array' /** * The BashServer glues together the separate components to implement @@ -190,7 +192,7 @@ export default class BashServer { private onDocumentSymbol(params: LSP.DocumentSymbolParams): LSP.SymbolInformation[] { this.connection.console.log(`onDocumentSymbol`) - return this.analyzer.findSymbols(params.textDocument.uri) + return this.analyzer.findSymbolsForFile({ uri: params.textDocument.uri }) } private onWorkspaceSymbol(params: LSP.WorkspaceSymbolParams): LSP.SymbolInformation[] { @@ -223,9 +225,14 @@ export default class BashServer { const word = this.getWordAtPoint(params) this.logRequest({ request: 'onCompletion', params, word }) - const symbolCompletions = this.analyzer.findSymbolCompletions(params.textDocument.uri) + const currentUri = params.textDocument.uri - // TODO: we could do some caching here... + const symbolCompletions = getCompletionItemsForSymbols({ + symbols: this.analyzer.findSymbolsMatchingWord({ + word, + }), + currentUri, + }) const reservedWordsCompletions = ReservedWords.LIST.map(reservedWord => ({ label: reservedWord, @@ -316,3 +323,111 @@ export default class BashServer { } } } + +function getCompletionItemsForSymbols({ + symbols, + currentUri, +}: { + symbols: LSP.SymbolInformation[] + currentUri: string +}): BashCompletionItem[] { + const isCurrentFile = ({ location: { uri } }: LSP.SymbolInformation) => + uri === currentUri + + const getSymbolId = ({ name, kind }: LSP.SymbolInformation) => `${name}${kind}` + + const symbolsCurrentFile = symbols.filter(s => isCurrentFile(s)) + + const symbolsOtherFiles = symbols + .filter(s => !isCurrentFile(s)) + // Remove identical symbols + .filter( + symbolOtherFiles => + !symbolsCurrentFile.some( + symbolCurrentFile => + getSymbolId(symbolCurrentFile) === getSymbolId(symbolOtherFiles), + ), + ) + + return uniqueBasedOnHash( + [...symbolsCurrentFile, ...symbolsOtherFiles], + getSymbolId, + ).map((symbol: LSP.SymbolInformation) => ({ + label: symbol.name, + kind: symbolKindToCompletionKind(symbol.kind), + data: { + name: symbol.name, + type: CompletionItemDataType.Symbol, + }, + documentation: + symbol.location.uri !== currentUri + ? `${symbolKindToDescription(symbol.kind)} defined in ${path.relative( + currentUri, + symbol.location.uri, + )}` + : undefined, + })) +} + +function symbolKindToCompletionKind(s: LSP.SymbolKind): LSP.CompletionItemKind { + switch (s) { + case LSP.SymbolKind.File: + return LSP.CompletionItemKind.File + case LSP.SymbolKind.Module: + case LSP.SymbolKind.Namespace: + case LSP.SymbolKind.Package: + return LSP.CompletionItemKind.Module + case LSP.SymbolKind.Class: + return LSP.CompletionItemKind.Class + case LSP.SymbolKind.Method: + return LSP.CompletionItemKind.Method + case LSP.SymbolKind.Property: + return LSP.CompletionItemKind.Property + case LSP.SymbolKind.Field: + return LSP.CompletionItemKind.Field + case LSP.SymbolKind.Constructor: + return LSP.CompletionItemKind.Constructor + case LSP.SymbolKind.Enum: + return LSP.CompletionItemKind.Enum + case LSP.SymbolKind.Interface: + return LSP.CompletionItemKind.Interface + case LSP.SymbolKind.Function: + return LSP.CompletionItemKind.Function + case LSP.SymbolKind.Variable: + return LSP.CompletionItemKind.Variable + case LSP.SymbolKind.Constant: + return LSP.CompletionItemKind.Constant + case LSP.SymbolKind.String: + case LSP.SymbolKind.Number: + case LSP.SymbolKind.Boolean: + case LSP.SymbolKind.Array: + case LSP.SymbolKind.Key: + case LSP.SymbolKind.Null: + return LSP.CompletionItemKind.Text + case LSP.SymbolKind.Object: + return LSP.CompletionItemKind.Module + case LSP.SymbolKind.EnumMember: + return LSP.CompletionItemKind.EnumMember + case LSP.SymbolKind.Struct: + return LSP.CompletionItemKind.Struct + case LSP.SymbolKind.Event: + return LSP.CompletionItemKind.Event + case LSP.SymbolKind.Operator: + return LSP.CompletionItemKind.Operator + case LSP.SymbolKind.TypeParameter: + return LSP.CompletionItemKind.TypeParameter + default: + return LSP.CompletionItemKind.Text + } +} + +function symbolKindToDescription(s: LSP.SymbolKind): string { + switch (s) { + case LSP.SymbolKind.Function: + return 'Function' + case LSP.SymbolKind.Variable: + return 'Variable' + default: + return 'Keyword' + } +} diff --git a/testing/fixtures/sourcing.sh b/testing/fixtures/sourcing.sh index 488563915..7e285c4d6 100644 --- a/testing/fixtures/sourcing.sh +++ b/testing/fixtures/sourcing.sh @@ -7,3 +7,7 @@ echo $RED 'Hello in red!' echo $BLU add_a_us + +BOLD=`tput bold` # redefined + +echo $BOL From 61b4b69f0c6a7470a03da1352fa141f459a5e0be Mon Sep 17 00:00:00 2001 From: skovhus Date: Thu, 16 Apr 2020 16:23:17 +0200 Subject: [PATCH 5/7] Check that executable files are executable --- server/src/executables.ts | 68 ++++++++++++++++++++++----------------- server/src/util/fs.ts | 13 -------- 2 files changed, 39 insertions(+), 42 deletions(-) diff --git a/server/src/executables.ts b/server/src/executables.ts index 3e14f0523..6d733d74b 100644 --- a/server/src/executables.ts +++ b/server/src/executables.ts @@ -1,10 +1,14 @@ -import * as Fs from 'fs' -import * as Path from 'path' +import * as fs from 'fs' +import { basename, join } from 'path' +import { promisify } from 'util' import * as ArrayUtil from './util/array' import * as FsUtil from './util/fs' import * as ShUtil from './util/sh' +const lstatAsync = promisify(fs.lstat) +const readdirAsync = promisify(fs.readdir) + /** * Provides information based on the programs on your PATH */ @@ -58,34 +62,40 @@ export default class Executables { /** * Only returns direct children, or the path itself if it's an executable. */ -function findExecutablesInPath(path: string): Promise { +async function findExecutablesInPath(path: string): Promise { path = FsUtil.untildify(path) - return new Promise((resolve, _) => { - Fs.lstat(path, (err, stat) => { - if (err) { - resolve([]) - } else { - if (stat.isDirectory()) { - Fs.readdir(path, (readDirErr, paths) => { - if (readDirErr) { - resolve([]) - } else { - const files = paths.map(p => - FsUtil.getStats(Path.join(path, p)) - .then(s => (s.isFile() ? [Path.basename(p)] : [])) - .catch(() => []), - ) - - resolve(Promise.all(files).then(ArrayUtil.flatten)) - } - }) - } else if (stat.isFile()) { - resolve([Path.basename(path)]) - } else { - // Something else. - resolve([]) + + try { + const pathStats = await lstatAsync(path) + + if (pathStats.isDirectory()) { + const childrenPaths = await readdirAsync(path) + + const files = [] + + for (const childrenPath of childrenPaths) { + try { + const stats = await lstatAsync(join(path, childrenPath)) + if (isExecutableFile(stats)) { + files.push(basename(childrenPath)) + } + } catch (error) { + // Ignore error } } - }) - }) + + return files + } else if (isExecutableFile(pathStats)) { + return [basename(path)] + } + } catch (error) { + // Ignore error + } + + return [] +} + +function isExecutableFile(stats: fs.Stats): boolean { + const isExecutable = !!(1 & parseInt((stats.mode & parseInt('777', 8)).toString(8)[0])) + return stats.isFile() && isExecutable } diff --git a/server/src/util/fs.ts b/server/src/util/fs.ts index ce82d41c2..efdd9c150 100644 --- a/server/src/util/fs.ts +++ b/server/src/util/fs.ts @@ -1,19 +1,6 @@ -import * as Fs from 'fs' import * as glob from 'glob' import * as Os from 'os' -export function getStats(path: string): Promise { - return new Promise((resolve, reject) => { - Fs.lstat(path, (err, stat) => { - if (err) { - reject(err) - } else { - resolve(stat) - } - }) - }) -} - // from https://github.com/sindresorhus/untildify/blob/f85a087418aeaa2beb56fe2684fe3b64fc8c588d/index.js#L11 export function untildify(pathWithTilde: string): string { const homeDirectory = Os.homedir() From b0783aa68e67fdecb02d6c86d6de6e123109c48f Mon Sep 17 00:00:00 2001 From: skovhus Date: Thu, 16 Apr 2020 21:36:17 +0200 Subject: [PATCH 6/7] Update engine package.json spacing --- server/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/package.json b/server/package.json index 8064fc69a..fd3c10d4d 100644 --- a/server/package.json +++ b/server/package.json @@ -15,7 +15,7 @@ "url": "https://github.com/bash-lsp/bash-language-server" }, "engines": { - "node" : ">=8.0.0" + "node": ">=8.0.0" }, "dependencies": { "fuzzy-search": "^3.2.1", From ab50f7daaab747dbd78f5cc35a894411058881c7 Mon Sep 17 00:00:00 2001 From: skovhus Date: Thu, 16 Apr 2020 21:38:11 +0200 Subject: [PATCH 7/7] Prepare server for 1.12.0 release --- server/CHANGELOG.md | 4 ++++ server/package.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/server/CHANGELOG.md b/server/CHANGELOG.md index 6585f025c..b82ef8ba9 100644 --- a/server/CHANGELOG.md +++ b/server/CHANGELOG.md @@ -1,5 +1,9 @@ # Bash Language Server +## 1.12.0 + +* Completion handler improvements: remove duplicates, include symbols from other files, ensure that programs found on the paths are actually executable (https://github.com/bash-lsp/bash-language-server/pull/215) + ## 1.11.3 * Recover from file reading errors (https://github.com/bash-lsp/bash-language-server/pull/211) diff --git a/server/package.json b/server/package.json index fd3c10d4d..1fef46b38 100644 --- a/server/package.json +++ b/server/package.json @@ -3,7 +3,7 @@ "description": "A language server for Bash", "author": "Mads Hartmann", "license": "MIT", - "version": "1.11.3", + "version": "1.12.0", "publisher": "mads-hartmann", "main": "./out/server.js", "typings": "./out/server.d.ts",