├── .gitignore ├── .travis.yml ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── .vscodeignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── images ├── auto_complete.gif └── icon.png ├── package.json ├── src ├── extension.ts ├── provider.ts └── resolve.ts ├── test ├── extension.test.ts └── index.ts ├── tsconfig.json ├── tslint.json └── vsc-extension-quickstart.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | out 40 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | install: 4 | - npm install 5 | - npm run vscode:prepublish 6 | 7 | script: 8 | - npm test 9 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | { 3 | "version": "0.1.0", 4 | "configurations": [ 5 | { 6 | "name": "Launch Extension", 7 | "type": "extensionHost", 8 | "request": "launch", 9 | "runtimeExecutable": "${execPath}", 10 | "args": ["--extensionDevelopmentPath=${workspaceRoot}" ], 11 | "stopOnEntry": false, 12 | "sourceMaps": true, 13 | "outDir": "${workspaceRoot}/out/src", 14 | "preLaunchTask": "npm" 15 | }, 16 | { 17 | "name": "Launch Tests", 18 | "type": "extensionHost", 19 | "request": "launch", 20 | "runtimeExecutable": "${execPath}", 21 | "args": ["--extensionDevelopmentPath=${workspaceRoot}", "--extensionTestsPath=${workspaceRoot}/out/test" ], 22 | "stopOnEntry": false, 23 | "sourceMaps": true, 24 | "outDir": "${workspaceRoot}/out/test", 25 | "preLaunchTask": "npm" 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.exclude": { 4 | "out": false // set this to true to hide the "out" folder with the compiled JS files 5 | }, 6 | "search.exclude": { 7 | "out": true // set this to false to include "out" folder in search results 8 | }, 9 | "typescript.tsdk": "./node_modules/typescript/lib" // we want to use the TS server from our node_modules folder to control its version 10 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // Available variables which can be used inside of strings. 2 | // ${workspaceRoot}: the root folder of the team 3 | // ${file}: the current opened file 4 | // ${fileBasename}: the current opened file's basename 5 | // ${fileDirname}: the current opened file's dirname 6 | // ${fileExtname}: the current opened file's extension 7 | // ${cwd}: the current working directory of the spawned process 8 | 9 | // A task runner that calls a custom npm script that compiles the extension. 10 | { 11 | "version": "0.1.0", 12 | 13 | // we want to run npm 14 | "command": "npm", 15 | 16 | // the command is a shell script 17 | "isShellCommand": true, 18 | 19 | // show the output window only if unrecognized errors occur. 20 | "showOutput": "silent", 21 | 22 | // we run the custom script "compile" as defined in package.json 23 | "args": ["run", "compile", "--loglevel", "silent"], 24 | 25 | // The tsc compiler is started in watching mode 26 | "isBackground": true, 27 | 28 | // use the standard tsc in watch mode problem matcher to find compile problems in the output. 29 | "problemMatcher": "$tsc-watch" 30 | } -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | out/test/** 4 | test/** 5 | src/** 6 | **/*.map 7 | .gitignore 8 | tsconfig.json 9 | vsc-extension-quickstart.md 10 | images/*.gif 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | + v1.5.0 - Updates: 4 | + Fix issue [#10](https://github.com/leizongmin/vscode-node-module-intellisense/issues/10) Add ability to resolve `@scoped/packages` subdirectories 5 | + Support all file extensions 6 | + v1.4.0 - Support for scans alternative module paths (useful when using packages like (https://www.npmjs.com/package/app-module-path) to manage require paths folder) 7 | + v1.3.0 - Fix problem when open a single file (not in workspace) 8 | + v1.2.0 - Add support for package sub path such as `ts-node/register` (issue #4) 9 | + v1.1.0 - Fix some problem: 10 | + Support `export ... from ...` statement (issue #1) 11 | + Add support for TypeScript `/// 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Marketplace Version](https://vsmarketplacebadge.apphb.com/version/leizongmin.node-module-intellisense.svg)](https://marketplace.visualstudio.com/items?itemName=leizongmin.node-module-intellisense) 2 | [![Installs](https://vsmarketplacebadge.apphb.com/installs/leizongmin.node-module-intellisense.svg)](https://marketplace.visualstudio.com/items?itemName=leizongmin.node-module-intellisense) 3 | [![Rating](https://vsmarketplacebadge.apphb.com/rating/leizongmin.node-module-intellisense.svg)](https://marketplace.visualstudio.com/items?itemName=leizongmin.node-module-intellisense) 4 | [![The MIT License](https://img.shields.io/badge/license-MIT-orange.svg?style=flat-square)](https://opensource.org/licenses/MIT) 5 | [![Build Status](https://img.shields.io/travis/leizongmin/vscode-node-module-intellisense.svg)](https://travis-ci.org/leizongmin/vscode-node-module-intellisense) 6 | [![David](https://img.shields.io/david/leizongmin/vscode-node-module-intellisense.svg?style=flat-square)](https://david-dm.org/leizongmin/vscode-node-module-intellisense) 7 | 8 | # Node.js Modules Intellisense 9 | 10 | [![Greenkeeper badge](https://badges.greenkeeper.io/leizongmin/vscode-node-module-intellisense.svg)](https://greenkeeper.io/) 11 | 12 | Visual Studio Code plugin that autocompletes JavaScript / TypeScript modules in import statements. 13 | 14 | This plugin was inspired by [Npm Intellisense](https://github.com/ChristianKohler/NpmIntellisense) and [AutoFileName](https://github.com/s6323859/vscode-autofilename). 15 | 16 | ![auto complete](https://github.com/leizongmin/vscode-node-module-intellisense/raw/master/images/auto_complete.gif) 17 | 18 | ## Installation 19 | 20 | Launch VS Code Quick Open (⌘+P), paste the following command, and press enter. 21 | 22 | ```bash 23 | ext install node-module-intellisense 24 | ``` 25 | 26 | View detail on [Visual Studio Code Marketplace](https://marketplace.visualstudio.com/items?itemName=leizongmin.node-module-intellisense) 27 | 28 | ## Issues & Contribution 29 | 30 | If there is any bug, create a pull request or an issue please. 31 | [Github](https://github.com/leizongmin/vscode-node-module-intellisense) 32 | 33 | ## Configuration 34 | 35 | Node.js Module Intellisense scans builtin modules, dependencies, devDependencies and file modules by default. 36 | Set scanBuiltinModules, scanDevDependencies and scanFileModules to false to disable it. 37 | 38 | ```javascript 39 | { 40 | // Scans builtin modules as well 41 | "node-module-intellisense.scanBuiltinModules": true, 42 | 43 | // Scans devDependencies as well 44 | "node-module-intellisense.scanDevDependencies": true, 45 | 46 | // Scans file modules as well 47 | "node-module-intellisense.scanFileModules": true, 48 | 49 | /** 50 | * Scans alternative module paths (eg. Search on ${workspaceFolder}/lib). 51 | * Useful when using packages like (https://www.npmjs.com/package/app-module-path) to manage require paths folder. 52 | **/ 53 | "node-module-intellisense.modulePaths": [], 54 | 55 | // Auto strip module extensions 56 | "node-module-intellisense.autoStripExtensions": [ 57 | ".js", 58 | ".jsx", 59 | ".ts", 60 | ".d.ts", 61 | ".tsx" 62 | ], 63 | } 64 | ``` 65 | 66 | ## Changelog 67 | 68 | * v1.5.0 - Updates: 69 | * Fix issue [#10](https://github.com/leizongmin/vscode-node-module-intellisense/issues/10) Add ability to resolve `@scoped/packages` subdirectories 70 | * Support all file extensions 71 | * v1.4.0 - Support for scans alternative module paths (useful when using packages like (https://www.npmjs.com/package/app-module-path) to manage require paths folder) 72 | * v1.3.0 - Fix problem when open a single file (not in workspace) 73 | * v1.2.0 - Add support for package sub path such as `ts-node/register` (issue #4) 74 | * v1.1.0 - Fix some problem: 75 | * Support `export ... from ...` statement (issue #1) 76 | * Add support for TypeScript `/// 89 | 90 | Permission is hereby granted, free of charge, to any person obtaining a copy 91 | of this software and associated documentation files (the "Software"), to deal 92 | in the Software without restriction, including without limitation the rights 93 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 94 | copies of the Software, and to permit persons to whom the Software is 95 | furnished to do so, subject to the following conditions: 96 | 97 | The above copyright notice and this permission notice shall be included in all 98 | copies or substantial portions of the Software. 99 | 100 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 101 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 102 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 103 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 104 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 105 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 106 | SOFTWARE. 107 | ``` 108 | -------------------------------------------------------------------------------- /images/auto_complete.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leizongmin/vscode-node-module-intellisense/66014f21292ff6870d159fc39f5085250f5e355c/images/auto_complete.gif -------------------------------------------------------------------------------- /images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leizongmin/vscode-node-module-intellisense/66014f21292ff6870d159fc39f5085250f5e355c/images/icon.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-module-intellisense", 3 | "displayName": "Node.js Modules Intellisense (with Coffee)", 4 | "description": "Autocompletes Node.js modules in import statements", 5 | "version": "1.5.0", 6 | "publisher": "jhessin", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/jhessin/vscode-node-module-intellisense.git" 10 | }, 11 | "license": "MIT", 12 | "bugs": { 13 | "url": 14 | "https://github.com/leizongmin/vscode-node-module-intellisense/issues" 15 | }, 16 | "homepage": 17 | "https://github.com/jhessin/vscode-node-module-intellisense.git#readme", 18 | "engines": { 19 | "vscode": "^1.5.0" 20 | }, 21 | "categories": ["Other", "Languages"], 22 | "activationEvents": [ 23 | "onLanguage:javascript", 24 | "onLanguage:javascriptreact", 25 | "onLanguage:typescript", 26 | "onLanguage:typescriptreact", 27 | "onLanguage:html", 28 | "onLanguage:coffeescript" 29 | ], 30 | "main": "./out/src/extension", 31 | "icon": "images/icon.png", 32 | "contributes": { 33 | "configuration": { 34 | "type": "object", 35 | "title": "node-module-intellisense", 36 | "properties": { 37 | "node-module-intellisense.scanBuiltinModules": { 38 | "type": "boolean", 39 | "default": true, 40 | "description": "Scans builtin modules as well" 41 | }, 42 | "node-module-intellisense.scanDevDependencies": { 43 | "type": "boolean", 44 | "default": true, 45 | "description": "Scans devDependencies as well" 46 | }, 47 | "node-module-intellisense.scanFileModules": { 48 | "type": "boolean", 49 | "default": true, 50 | "description": "Scans file modules as well" 51 | }, 52 | "node-module-intellisense.autoStripExtensions": { 53 | "type": "array", 54 | "default": [".js", ".jsx", ".ts", ".d.ts", ".tsx"], 55 | "description": "Auto strip module extensions" 56 | }, 57 | "node-module-intellisense.modulePaths": { 58 | "type": "array", 59 | "default": [], 60 | "description": 61 | "Module Paths to scan. Use ${workspaceRoot} for workspace lookup." 62 | } 63 | } 64 | } 65 | }, 66 | "scripts": { 67 | "clean": "rm -rf out", 68 | "vscode:prepublish": "npm run clean && tsc -p ./", 69 | "compile": "npm run clean && tsc -watch -p ./", 70 | "postinstall": "node ./node_modules/vscode/bin/install" 71 | }, 72 | "devDependencies": { 73 | "@types/mocha": "^2.2.44", 74 | "@types/node": "^8.5.2", 75 | "@types/resolve": "0.0.4", 76 | "mocha": "^4.0.1", 77 | "typescript": "^2.6.2", 78 | "vscode": "^1.1.10" 79 | }, 80 | "dependencies": {}, 81 | "__metadata": { 82 | "id": "1bb92b2c-526c-4bfd-bb38-5bae1b278c89", 83 | "publisherId": "leizongmin.node-module-intellisense", 84 | "publisherDisplayName": "leizongmin" 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Zongmin Lei All rights reserved. 3 | * Licensed under the MIT License. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as vscode from "vscode"; 7 | import IntellisenseProvider from "./provider"; 8 | 9 | export function activate(context: vscode.ExtensionContext) { 10 | 11 | const provider = new IntellisenseProvider(); 12 | provider.activate(context); 13 | 14 | } 15 | 16 | export function deactivate() {} 17 | -------------------------------------------------------------------------------- /src/provider.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Zongmin Lei All rights reserved. 3 | * Licensed under the MIT License. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as fs from "fs"; 7 | import * as path from "path"; 8 | import * as vscode from "vscode"; 9 | import { 10 | CancellationToken, 11 | Command, 12 | CompletionItem, 13 | CompletionItemKind, 14 | CompletionItemProvider, 15 | Disposable, 16 | ExtensionContext, 17 | FileSystemWatcher, 18 | Position, 19 | TextDocument, 20 | TextEdit, 21 | Uri, 22 | WorkspaceConfiguration, 23 | } from "vscode"; 24 | import resolvePackage from "./resolve"; 25 | 26 | export default class IntellisenseProvider implements CompletionItemProvider { 27 | 28 | /** 29 | * Builtin Node.js modules 30 | */ 31 | public static readonly builtinModules: string[] = getBuiltinModules(); 32 | 33 | public static readonly configPath: string = "node-module-intellisense"; 34 | public static readonly defaultAutoStripExtensions: string[] = [ ".js", ".jsx", ".ts", ".d.ts", ".tsx" ]; 35 | public static readonly languageSelector: string[] = [ "javascript", "javascriptreact", "typescript", "typescriptreact", "html", "coffeescript" ]; 36 | public static readonly triggerCharacters: string[] = [ "'", "\"", "/" ]; 37 | 38 | private context: ExtensionContext; 39 | 40 | private dependencies: string[] = []; 41 | private packageJsonFile: string = this.resolveWorkspacePath("package.json"); 42 | private packageJsonWatcher: FileSystemWatcher; 43 | 44 | private config: WorkspaceConfiguration; 45 | private enableDevDependencies: boolean = true; 46 | private enableFileModules: boolean = true; 47 | private modulePaths: string[] = []; 48 | private enableBuiltinModules: boolean = true; 49 | private autoStripExtensions: string[] = IntellisenseProvider.defaultAutoStripExtensions; 50 | 51 | private readonly disposables: Disposable[] = []; 52 | 53 | public activate(context: ExtensionContext) { 54 | this.context = context; 55 | context.subscriptions.push(this); 56 | 57 | // load configuration 58 | const loadConfig = () => { 59 | this.config = vscode.workspace.getConfiguration(IntellisenseProvider.configPath); 60 | this.enableBuiltinModules = this.config.get("scanBuiltinModules", true); 61 | this.enableDevDependencies = this.config.get("scanDevDependencies", true); 62 | this.enableFileModules = this.config.get("scanFileModules", true); 63 | this.modulePaths = this.config.get("modulePaths", []); 64 | this.autoStripExtensions = this.config.get("autoStripExtensions", IntellisenseProvider.defaultAutoStripExtensions); 65 | this.autoStripExtensions.sort((a, b) => b.length - a.length); 66 | // this.debug(this.autoStripExtensions); 67 | }; 68 | vscode.workspace.onDidChangeConfiguration((e) => { 69 | loadConfig(); 70 | // this.debug("reload config", this.config); 71 | }); 72 | loadConfig(); 73 | // this.debug("load config", this.config); 74 | 75 | // create completion provider 76 | vscode.languages.registerCompletionItemProvider(IntellisenseProvider.languageSelector, this, ...IntellisenseProvider.triggerCharacters); 77 | // this.debug("activate"); 78 | // this.debug("builtinModules", IntellisenseProvider.builtinModules); 79 | 80 | // load dependencies from package.json file 81 | this.updateDependenciesFromPackageJson(); 82 | // watching package.json and auto update dependencies info 83 | this.packageJsonWatcher = vscode.workspace.createFileSystemWatcher("**/package.json"); 84 | this.disposables.push(this.packageJsonWatcher); 85 | const onPackageJsonFileChange = (e: Uri) => { 86 | // this.debug("workspace file change:", e); 87 | if (e.fsPath === this.packageJsonFile) { 88 | this.updateDependenciesFromPackageJson(); 89 | } 90 | }; 91 | this.packageJsonWatcher.onDidChange(onPackageJsonFileChange); 92 | this.packageJsonWatcher.onDidCreate(onPackageJsonFileChange); 93 | this.packageJsonWatcher.onDidDelete(onPackageJsonFileChange); 94 | } 95 | 96 | public dispose() { 97 | // this.debug("dispose"); 98 | this.disposables.forEach((item) => { 99 | try { 100 | item.dispose(); 101 | } catch (err) { 102 | // this.debug("dispose", err); 103 | } 104 | }); 105 | } 106 | 107 | /** 108 | * Provide completion items for the given position and document. 109 | * 110 | * @param document The document in which the command was invoked. 111 | * @param position The position at which the command was invoked. 112 | * @param token A cancellation token. 113 | * @return An array of completions, a [completion list](#CompletionList), or a thenable that resolves to either. 114 | * The lack of a result can be signaled by returning `undefined`, `null`, or an empty array. 115 | */ 116 | public async provideCompletionItems(document: TextDocument, position: Position, token: CancellationToken): Promise { 117 | const info = parseLine(document, position); 118 | if (!info) { 119 | return []; 120 | } 121 | 122 | // this.debug("provideCompletionItems: parseLine", position, info); 123 | 124 | let list: CompletionItem[] = []; 125 | 126 | const isShowPackageSubPath = info.isPackagePath && info.search.indexOf("/") > 0; 127 | const isShowPackage = info.isPackagePath || info.search === ""; 128 | const isShowFile = info.isAbsoultePath || info.isRelativePath || info.search === ""; 129 | const isIncludeExtname = info.type === "reference"; 130 | 131 | if (isShowPackageSubPath) { 132 | 133 | // package sub path 134 | let pkgDir; 135 | try { 136 | pkgDir = await resolvePackageDirectory(info.packageName, document.uri.fsPath); 137 | const currentDir = path.resolve(pkgDir, info.packageSubPath); 138 | const files = await this.readCurrentDirectory(currentDir, info.search, false); 139 | // fix insertText 140 | files.forEach((item) => { 141 | item.insertText = item.label.slice(info.search.length); 142 | }); 143 | list = list.concat(files); 144 | } catch (err) { 145 | this.debug("resolvePackageDirectory", err); 146 | } 147 | 148 | } else { 149 | 150 | // builtin modules 151 | if (isShowPackage && this.enableBuiltinModules) { 152 | list = IntellisenseProvider.builtinModules.map((name) => { 153 | return createCompletionItem(name, CompletionItemKind.Module, { detail: "builtin module" }); 154 | }); 155 | } 156 | 157 | // packages npm dependencies 158 | if (isShowPackage) { 159 | list = list.concat(this.dependencies.map((name) => { 160 | return createCompletionItem(name, CompletionItemKind.Module, { detail: "npm dependencies" }); 161 | })); 162 | } 163 | } 164 | 165 | // packages from relative path 166 | if (isShowFile && this.enableFileModules) { 167 | const currentDir = path.resolve(path.dirname(document.uri.fsPath), info.search); 168 | const files = await this.readCurrentDirectory(currentDir, info.search || "./", isIncludeExtname); 169 | // fix insertText 170 | files.forEach((item) => { 171 | item.insertText = item.label.slice(info.search.length); 172 | }); 173 | list = list.concat(files); 174 | } 175 | 176 | // packages from relative path 177 | if (this.modulePaths.length > 0) { 178 | for (const modulePath of this.modulePaths) { 179 | const currentDir = this.resolveWorkspacePath(modulePath.replace("${workspaceRoot}", ""), info.search || ""); 180 | const files = await this.readCurrentDirectory(currentDir, info.search || "", isIncludeExtname); 181 | // fix insertText 182 | files.forEach((item) => { 183 | item.insertText = item.label.slice(info.search.length); 184 | }); 185 | list = list.concat(files); 186 | } 187 | } 188 | 189 | // this.debug("provideCompletionItems", list); 190 | return list; 191 | } 192 | 193 | /** 194 | * Given a completion item fill in more data, like [doc-comment](#CompletionItem.documentation) 195 | * or [details](#CompletionItem.detail). 196 | * 197 | * The editor will only resolve a completion item once. 198 | * 199 | * @param item A completion item currently active in the UI. 200 | * @param token A cancellation token. 201 | * @return The resolved completion item or a thenable that resolves to of such. It is OK to return the given 202 | * `item`. When no result is returned, the given `item` will be used. 203 | */ 204 | public resolveCompletionItem?(item: CompletionItem, token: CancellationToken): CompletionItem | Thenable { 205 | // this.debug("resolveCompletionItem", item); 206 | return item; 207 | } 208 | 209 | private debug(...data: any[]): void { 210 | // tslint:disable-next-line:no-console 211 | console.log("IntellisenseProvider debug:", ...data); 212 | } 213 | 214 | private showWarning(msg: string): void { 215 | vscode.window.showWarningMessage(`node-module-intellisense: ${ msg }`); 216 | } 217 | 218 | private resolveWorkspacePath(...paths: string[]): string { 219 | if (vscode.workspace.rootPath) { 220 | return path.resolve(vscode.workspace.rootPath, ...paths); 221 | } 222 | return path.resolve(...paths); 223 | } 224 | 225 | private async updateDependenciesFromPackageJson(): Promise { 226 | // check if file exists 227 | const exists = await isFileExists(this.packageJsonFile); 228 | if (!exists) { 229 | // this.debug("package.json file not exists"); 230 | return; 231 | } 232 | // get file content 233 | let data: string; 234 | try { 235 | data = (await readFileContent(this.packageJsonFile)).toString(); 236 | } catch (err) { 237 | return this.showWarning(err.message); 238 | } 239 | // parse JSON file 240 | let json; 241 | try { 242 | json = JSON.parse(data.toString()); 243 | } catch (err) { 244 | return this.showWarning(`parsing package.json file error: ${ err.message }`); 245 | } 246 | // get dependencies 247 | const list = new Set(); 248 | if (json.dependencies) { 249 | Object.keys(json.dependencies).forEach((name) => list.add(name)); 250 | } 251 | if (this.enableDevDependencies && json.devDependencies) { 252 | Object.keys(json.devDependencies).forEach((name) => list.add(name)); 253 | } 254 | this.dependencies = Array.from(list.values()); 255 | // this.debug("load dependencies from package.json:", this.dependencies); 256 | } 257 | private async readCurrentDirectory(dir: string, prefix: string, isIncludeExtname: boolean): Promise { 258 | const names = await readdir(dir); 259 | const list: CompletionItem[] = []; 260 | const fileMap = new Map(); 261 | 262 | const relativePathInfo = (p) => { 263 | if (vscode.workspace.rootPath) { 264 | return `relative to workspace: ${ path.relative(vscode.workspace.rootPath, p) }`; 265 | } 266 | return `absolute path: ${ p }`; 267 | }; 268 | 269 | list.push(createCompletionItem("..", CompletionItemKind.Module, { 270 | detail: "directory", 271 | documentation: relativePathInfo(path.dirname(dir)), 272 | })); 273 | 274 | for (const name of names) { 275 | const realPath = path.join(dir, name); 276 | const stats = await readFileStats(realPath); 277 | if (stats.isDirectory()) { 278 | // directory 279 | list.push(createCompletionItem(`${ prefix }${ name }`, CompletionItemKind.Module, { 280 | detail: "directory", 281 | documentation: relativePathInfo(realPath), 282 | })); 283 | } else if (stats.isFile()) { 284 | // file 285 | const [ strip, ext ] = parseFileExtensionName(name, this.autoStripExtensions); 286 | this.debug("FILE", name, strip, ext); 287 | let n = name; 288 | if (!isIncludeExtname && strip) { 289 | n = name.slice(0, name.length - ext.length); 290 | } 291 | if (!fileMap.has(n)) { 292 | fileMap.set(n, true); 293 | list.push(createCompletionItem(`${ prefix }${ n }`, CompletionItemKind.File, { 294 | detail: "file module", 295 | documentation: relativePathInfo(realPath), 296 | })); 297 | } 298 | } 299 | } 300 | return list; 301 | } 302 | 303 | } 304 | 305 | /** 306 | * returns builtin modules 307 | */ 308 | function getBuiltinModules(): string[] { 309 | return Object.keys((process as any).binding("natives")).filter((n) => { 310 | if (n.indexOf("_") !== -1) { 311 | return false; 312 | } 313 | if (n.indexOf("/") !== -1) { 314 | return false; 315 | } 316 | if (n.indexOf("-") !== -1) { 317 | return false; 318 | } 319 | return true; 320 | }); 321 | } 322 | 323 | interface ExtraCompletionInfo { 324 | label?: string; 325 | kind?: CompletionItemKind; 326 | detail?: string; 327 | documentation?: string; 328 | sortText?: string; 329 | filterText?: string; 330 | insertText?: string; 331 | command?: Command; 332 | textEdit?: TextEdit; 333 | additionalTextEdits?: TextEdit; 334 | } 335 | 336 | /** 337 | * create CompletionItem 338 | */ 339 | function createCompletionItem(name: string, kind: CompletionItemKind, info: ExtraCompletionInfo): CompletionItem { 340 | const item = new CompletionItem(name, kind); 341 | Object.assign(item, info); 342 | return item; 343 | } 344 | 345 | /** 346 | * returns true if file is exists 347 | */ 348 | function isFileExists(filename: string): Promise { 349 | return new Promise((resolve, reject) => { 350 | fs.exists(filename, resolve); 351 | }); 352 | } 353 | 354 | /** 355 | * returns file content 356 | */ 357 | function readFileContent(filename: string): Promise { 358 | return new Promise((resolve, reject) => { 359 | fs.readFile(filename, (err, data) => { 360 | if (err) { 361 | return reject(err); 362 | } 363 | resolve(data); 364 | }); 365 | }); 366 | } 367 | 368 | /** 369 | * returns file stats 370 | */ 371 | function readFileStats(filename: string): Promise { 372 | return new Promise((resolve, reject) => { 373 | fs.stat(filename, (err, stats) => { 374 | if (err) { 375 | return reject(err); 376 | } 377 | resolve(stats); 378 | }); 379 | }); 380 | } 381 | 382 | /** 383 | * returns directory files 384 | */ 385 | function readdir(dir: string): Promise { 386 | return new Promise((resolve, reject) => { 387 | fs.readdir(dir, (err, list) => { 388 | if (err) { 389 | return reject(err); 390 | } 391 | resolve(list); 392 | }); 393 | }); 394 | } 395 | 396 | interface IntellisenseLineInfo { 397 | line?: string; 398 | quotation?: string; 399 | quotationStart?: number; 400 | search?: string; 401 | isAbsoultePath?: boolean; 402 | isRelativePath?: boolean; 403 | isPackagePath?: boolean; 404 | packageName?: string; 405 | packageSubPath?: string; 406 | position?: Position; 407 | type?: StatementType; 408 | } 409 | 410 | type StatementType = "require" | "import" | "export" | "reference" | false; 411 | 412 | /** 413 | * Parse current line 414 | */ 415 | function parseLine(document: TextDocument, position: Position): IntellisenseLineInfo { 416 | const info: IntellisenseLineInfo = { 417 | position, 418 | }; 419 | 420 | const line = document.getText(document.lineAt(position).range); 421 | info.type = getStatementType(line); 422 | if (!info.type) { 423 | return; 424 | } 425 | 426 | const [ i, quotation ] = getForwardQuotation(line, position.character); 427 | info.quotation = quotation; 428 | info.quotationStart = i; 429 | info.search = line.slice(i + 1, position.character); 430 | 431 | if (info.search[0] === ".") { 432 | info.isRelativePath = true; 433 | } else if (info.search[0] === "/") { 434 | info.isAbsoultePath = true; 435 | } else { 436 | info.isPackagePath = true; 437 | let j = info.search.indexOf(path.sep); 438 | if (j !== -1 && info.search[0] === "@") { 439 | j = info.search.indexOf(path.sep, j + 1); 440 | } 441 | if (j === -1) { 442 | info.packageName = info.search; 443 | info.packageSubPath = ""; 444 | } else { 445 | info.packageName = info.search.slice(0, j); 446 | info.packageSubPath = info.search.slice(j + 1); 447 | } 448 | } 449 | return info; 450 | } 451 | 452 | /** 453 | * Returns statement type 454 | */ 455 | function getStatementType(line: string): StatementType { 456 | line = line.trim(); 457 | if (line.indexOf("import ") === 0) { 458 | return "import"; 459 | } 460 | if (line.indexOf("require(") !== -1) { 461 | return "require"; 462 | } 463 | if (line.indexOf("export ") === 0 && line.indexOf(" from ") !== -1) { 464 | return "export"; 465 | } 466 | if (line.trim().indexOf("/// j) { 479 | return [ i, "\"" ]; 480 | } 481 | return [ j, "'" ]; 482 | } 483 | 484 | /** 485 | * Parse File extension name 486 | */ 487 | function parseFileExtensionName(filename: string, autoStripExtensions: string[]): [ boolean, string ] { 488 | const len = filename.length; 489 | for (const ext of autoStripExtensions) { 490 | if (filename.slice(len - ext.length) === ext) { 491 | return [ true, ext ]; 492 | } 493 | } 494 | return [ false, "" ]; 495 | } 496 | 497 | /** 498 | * Returns require package directory from current path 499 | */ 500 | function resolvePackageDirectory(pkgName: string, filename: string): Promise { 501 | return resolvePackage(pkgName, path.dirname(filename)); 502 | } 503 | -------------------------------------------------------------------------------- /src/resolve.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Zongmin Lei All rights reserved. 3 | * Licensed under the MIT License. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import * as fs from "fs"; 7 | import * as path from "path"; 8 | 9 | function isDirExists(file: string): Promise { 10 | return new Promise((resolve, reject) => { 11 | fs.stat(file, (err, stats) => { 12 | if (err) { 13 | return resolve(false); 14 | } 15 | resolve(stats.isDirectory()); 16 | }); 17 | }); 18 | } 19 | 20 | function getAllParentNodeModulesDir(dir: string): string[] { 21 | const dirs = [ path.resolve(dir, "node_modules") ]; 22 | while (true) { 23 | const parent = path.dirname(dir); 24 | if (parent === dir) { 25 | break; 26 | } 27 | dirs.push(path.resolve(parent, "node_modules")); 28 | dir = parent; 29 | } 30 | return dirs; 31 | } 32 | 33 | export default async function resolvePackage(name: string, dir: string): Promise { 34 | const dirs = getAllParentNodeModulesDir(dir); 35 | for (const item of dirs) { 36 | const p = path.resolve(item, name); 37 | if (await isDirExists(p)) { 38 | return p; 39 | } 40 | } 41 | const err = new Error(`cannot find module "${name} in ${dir}"`); 42 | (err as any).code = "MODULE_NOT_FOUND"; 43 | throw err; 44 | } 45 | -------------------------------------------------------------------------------- /test/extension.test.ts: -------------------------------------------------------------------------------- 1 | // 2 | // Note: This example test is leveraging the Mocha test framework. 3 | // Please refer to their documentation on https://mochajs.org/ for help. 4 | // 5 | 6 | // The module 'assert' provides assertion methods from node 7 | import * as assert from 'assert'; 8 | 9 | // You can import and use all API from the 'vscode' module 10 | // as well as import your extension to test it 11 | import * as vscode from 'vscode'; 12 | import * as myExtension from '../src/extension'; 13 | 14 | // Defines a Mocha test suite to group tests of similar kind together 15 | suite("Extension Tests", () => { 16 | 17 | // Defines a Mocha unit test 18 | test("Something 1", () => { 19 | assert.equal(-1, [1, 2, 3].indexOf(5)); 20 | assert.equal(-1, [1, 2, 3].indexOf(0)); 21 | }); 22 | }); -------------------------------------------------------------------------------- /test/index.ts: -------------------------------------------------------------------------------- 1 | // 2 | // PLEASE DO NOT MODIFY / DELETE UNLESS YOU KNOW WHAT YOU ARE DOING 3 | // 4 | // This file is providing the test runner to use when running extension tests. 5 | // By default the test runner in use is Mocha based. 6 | // 7 | // You can provide your own test runner if you want to override it by exporting 8 | // a function run(testRoot: string, clb: (error:Error) => void) that the extension 9 | // host can call to run the tests. The test runner is expected to use console.log 10 | // to report the results back to the caller. When the tests are finished, return 11 | // a possible error to the callback or null if none. 12 | 13 | var testRunner = require('vscode/lib/testrunner'); 14 | 15 | // You can directly control Mocha options by uncommenting the following lines 16 | // See https://github.com/mochajs/mocha/wiki/Using-mocha-programmatically#set-options for more info 17 | testRunner.configure({ 18 | ui: 'tdd', // the TDD UI is being used in extension.test.ts (suite, test, etc.) 19 | useColors: true // colored output from test results 20 | }); 21 | 22 | module.exports = testRunner; -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "outDir": "out", 6 | "lib": [ 7 | "es6" 8 | ], 9 | "sourceMap": true, 10 | "rootDir": "." 11 | }, 12 | "exclude": [ 13 | "node_modules", 14 | ".vscode-test" 15 | ] 16 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "tslint:recommended" 4 | ], 5 | "rules": { 6 | "no-empty": false, 7 | "max-line-length": [ 8 | false 9 | ], 10 | "interface-name": [ 11 | false 12 | ] 13 | } 14 | } -------------------------------------------------------------------------------- /vsc-extension-quickstart.md: -------------------------------------------------------------------------------- 1 | # Welcome to your first VS Code Extension 2 | 3 | ## What's in the folder 4 | * This folder contains all of the files necessary for your extension 5 | * `package.json` - this is the manifest file in which you declare your extension and command. 6 | The sample plugin registers a command and defines its title and command name. With this information 7 | VS Code can show the command in the command palette. It doesn’t yet need to load the plugin. 8 | * `src/extension.ts` - this is the main file where you will provide the implementation of your command. 9 | The file exports one function, `activate`, which is called the very first time your extension is 10 | activated (in this case by executing the command). Inside the `activate` function we call `registerCommand`. 11 | We pass the function containing the implementation of the command as the second parameter to 12 | `registerCommand`. 13 | 14 | ## Get up and running straight away 15 | * press `F5` to open a new window with your extension loaded 16 | * run your command from the command palette by pressing (`Ctrl+Shift+P` or `Cmd+Shift+P` on Mac) and typing `Hello World` 17 | * set breakpoints in your code inside `src/extension.ts` to debug your extension 18 | * find output from your extension in the debug console 19 | 20 | ## Make changes 21 | * you can relaunch the extension from the debug toolbar after changing code in `src/extension.ts` 22 | * you can also reload (`Ctrl+R` or `Cmd+R` on Mac) the VS Code window with your extension to load your changes 23 | 24 | ## Explore the API 25 | * you can open the full set of our API when you open the file `node_modules/vscode/vscode.d.ts` 26 | 27 | ## Run tests 28 | * open the debug viewlet (`Ctrl+Shift+D` or `Cmd+Shift+D` on Mac) and from the launch configuration dropdown pick `Launch Tests` 29 | * press `F5` to run the tests in a new window with your extension loaded 30 | * see the output of the test result in the debug console 31 | * make changes to `test/extension.test.ts` or create new test files inside the `test` folder 32 | * by convention, the test runner will only consider files matching the name pattern `**.test.ts` 33 | * you can create folders inside the `test` folder to structure your tests any way you want --------------------------------------------------------------------------------