├── .czrc ├── .github └── workflows │ └── main.yml ├── .gitignore ├── .no-sublime-package ├── .prettierrc.js ├── .releaserc.js ├── CHANGELOG.md ├── Default.sublime-commands ├── Default.sublime-keymap ├── LICENSE ├── Main.sublime-menu ├── README.md ├── Taskfile ├── backend_run.js ├── import_helper.py ├── import_helper.sublime-settings ├── library ├── common_path.py ├── debug.py ├── exec_command.py ├── find_executable.py ├── get_exclude_patterns.py ├── get_from_paths.py ├── get_import_root.py ├── get_node_executable.py ├── get_setting.py ├── get_source_folders.py ├── identifier_name.py ├── insert_import_command.py ├── list_imports_command.py ├── on_done_func.py ├── panel_items.py ├── paste_import_command.py ├── query_completions_modules.py ├── read_json.py ├── try_typescript_path.py ├── unixify.py ├── update_node_modules.py ├── update_source_modules.py ├── update_typescript_paths.py └── utils.py ├── messages.json ├── package-lock.json ├── package.json ├── screenshots └── insert-import.gif ├── test_playground ├── app │ ├── t1 │ │ ├── src │ │ │ └── component │ │ │ │ └── index.ts │ │ └── volumes │ │ │ └── x │ │ │ └── test.ts │ └── t2 │ │ └── src │ │ └── component │ │ ├── abc.component.ts │ │ ├── about.component.ts │ │ ├── dummy.component.ts │ │ ├── index.ts │ │ ├── x.component.ts │ │ └── x │ │ ├── index.ts │ │ └── x2.component.ts ├── bad_json │ └── package.json ├── component │ ├── abc.component.ts │ ├── about.component.ts │ ├── dummy.component.ts │ ├── index.ts │ ├── x.component.ts │ └── x │ │ ├── index.ts │ │ └── x2.component.ts ├── createname.ts ├── empty_file │ └── package.json ├── empty_pkg │ └── package.json ├── ignored │ ├── folder │ │ └── inject.d.ts │ └── inject.ts ├── lib │ ├── inheritance.js │ ├── inheritance_2.js │ └── mod.mjs ├── main.ts ├── message.tsx ├── typescript_paths.ts └── unused.ts ├── tests ├── test.py └── test_library.py ├── tsconfig.json ├── unittesting.json └── webpack.config.js /.czrc: -------------------------------------------------------------------------------- 1 | { 2 | "path": "./node_modules/cz-conventional-changelog", 3 | "disableScopeLowerCase": false, 4 | "disableSubjectLowerCase": true 5 | } 6 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | name: 'Test on Sublime ${{ matrix.st-version }} and ${{ matrix.os }}' 8 | strategy: 9 | fail-fast: false 10 | matrix: 11 | st-version: [3, 4] 12 | os: ['ubuntu-latest', 'macOS-latest', 'windows-latest'] 13 | runs-on: ${{ matrix.os }} 14 | steps: 15 | - uses: actions/setup-node@v3 16 | with: 17 | node-version: 16 18 | - uses: actions/checkout@v2 19 | - uses: SublimeText/UnitTesting/actions/setup@v1 20 | with: 21 | sublime-text-version: ${{ matrix.st-version }} 22 | package-name: ImportHelper 23 | - uses: SublimeText/UnitTesting/actions/run-tests@v1 24 | with: 25 | package-name: ImportHelper 26 | coverage: true 27 | codecov-upload: false 28 | 29 | release: 30 | name: 'Release' 31 | runs-on: ubuntu-latest 32 | needs: test 33 | if: github.event_name == 'push' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/next') 34 | steps: 35 | - name: 'Checkout repository' 36 | uses: actions/checkout@v3 37 | - name: 'Setup Node' 38 | uses: actions/setup-node@v3 39 | with: 40 | node-version: 16 41 | - name: 'Install depependencies' 42 | run: | 43 | npm install 44 | - name: 'Release' 45 | run: | 46 | npx semantic-release 47 | env: 48 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | ~* 3 | npm-debug.* 4 | -------------------------------------------------------------------------------- /.no-sublime-package: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unlight/sublime-import-helper/8adfbafc74bd2fd147c7c8c983f7603c741a4111/.no-sublime-package -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 100, 3 | trailingComma: 'all', 4 | tabWidth: 4, 5 | semi: true, 6 | singleQuote: true, 7 | overrides: [ 8 | { 9 | files: '*.{json,yml}', 10 | options: { 11 | tabWidth: 2, 12 | }, 13 | }, 14 | ], 15 | }; 16 | -------------------------------------------------------------------------------- /.releaserc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | [ 4 | '@semantic-release/commit-analyzer', 5 | { 6 | preset: 'conventionalcommits', 7 | }, 8 | ], 9 | [ 10 | '@semantic-release/release-notes-generator', 11 | { 12 | preset: 'conventionalcommits', 13 | }, 14 | ], 15 | [ 16 | '@semantic-release/exec', 17 | { 18 | prepareCmd: 'sh Taskfile prepare ${nextRelease.version}', 19 | }, 20 | ], 21 | '@semantic-release/changelog', 22 | '@semantic-release/github', 23 | [ 24 | '@semantic-release/git', 25 | { 26 | assets: ['messages.json', 'CHANGELOG.md'], 27 | }, 28 | ], 29 | ], 30 | }; 31 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [4.2.0](https://github.com/unlight/sublime-import-helper/compare/v4.1.1...v4.2.0) (2022-06-11) 2 | 3 | 4 | ### Features 5 | 6 | * Extension setting ([0dd6a2e](https://github.com/unlight/sublime-import-helper/commit/0dd6a2e90a6dfa9eb200d238dc845d550411893e)), closes [#112](https://github.com/unlight/sublime-import-helper/issues/112) 7 | 8 | ### [4.1.1](https://github.com/unlight/sublime-import-helper/compare/v4.1.0...v4.1.1) (2022-05-04) 9 | 10 | 11 | ### Bug Fixes 12 | 13 | * Improve detect node executable ([a10dcca](https://github.com/unlight/sublime-import-helper/commit/a10dccab287abff938323d7152c9fd2adb419561)), closes [#107](https://github.com/unlight/sublime-import-helper/issues/107) 14 | 15 | ## [4.1.0](https://github.com/unlight/sublime-import-helper/compare/v4.0.0...v4.1.0) (2021-09-16) 16 | 17 | 18 | ### Features 19 | 20 | * Setting for space in braces `insert_space_in_braces` ([df0d246](https://github.com/unlight/sublime-import-helper/commit/df0d246625eae590081e8823bde9666dfd500933)) 21 | 22 | 23 | ### Bug Fixes 24 | 25 | * Exception must inherit BaseException ([7ceb54f](https://github.com/unlight/sublime-import-helper/commit/7ceb54f0798298baf274631c9889f3e48043930d)) 26 | 27 | ## [4.0.0](https://github.com/unlight/sublime-import-helper/compare/v3.1.0...v4.0.0) (2021-09-11) 28 | 29 | 30 | ### ⚠ BREAKING CHANGES 31 | 32 | * Require Node.js 12.0+ 33 | 34 | ### Features 35 | 36 | * Setting to disable semicolon ([4319e8d](https://github.com/unlight/sublime-import-helper/commit/4319e8df9644829b7b6dd6f4ce3e6edabc38fcb2)) 37 | 38 | 39 | ### Miscellaneous Chores 40 | 41 | * Updated packages ([78b8fa1](https://github.com/unlight/sublime-import-helper/commit/78b8fa18e527e89c202f4f5b7d9244a9b858cbd8)) 42 | 43 | # [3.1.0](https://github.com/unlight/sublime-import-helper/compare/v3.0.2...v3.1.0) (2020-06-30) 44 | 45 | 46 | ### Bug Fixes 47 | 48 | * Some imports from ambient modules are missing ([098fe1b](https://github.com/unlight/sublime-import-helper/commit/098fe1bf90a3049dbcc1b45d2788506b22746ef4)) 49 | 50 | 51 | ### Features 52 | 53 | * Sorted named imports ([12e2370](https://github.com/unlight/sublime-import-helper/commit/12e237049ec30a9f4c951bc5bf02a57d7eff0144)), closes [#48](https://github.com/unlight/sublime-import-helper/issues/48) 54 | 55 | ## [3.0.2](https://github.com/unlight/sublime-import-helper/compare/v3.0.1...v3.0.2) (2020-06-12) 56 | 57 | 58 | ### Bug Fixes 59 | 60 | * Export node modules from non-current directory does not work ([bbbd4c4](https://github.com/unlight/sublime-import-helper/commit/bbbd4c429294ec8d5b515d294e6fbec19d182161)) 61 | 62 | ## [3.0.1](https://github.com/unlight/sublime-import-helper/compare/v3.0.0...v3.0.1) (2020-06-10) 63 | 64 | 65 | ### Bug Fixes 66 | 67 | * Added no-warnings argument https://nodejs.org/api/cli.html#cli_no_warnings ([b6d78eb](https://github.com/unlight/sublime-import-helper/commit/b6d78ebc9a405e6afdd5a744c90bf2c824597912)), closes [#80](https://github.com/unlight/sublime-import-helper/issues/80) 68 | 69 | # [3.0.0](https://github.com/unlight/sublime-import-helper/compare/v2.3.2...v3.0.0) (2020-06-09) 70 | 71 | 72 | ### Bug Fixes 73 | 74 | * Added scope TypeScript unit test ([2760b6c](https://github.com/unlight/sublime-import-helper/commit/2760b6ca883e17b4228def45a26cfd1c98d94021)) 75 | * Panel items functions did not get call ([2846934](https://github.com/unlight/sublime-import-helper/commit/284693467d202a1d18e925e01b33a31fced22008)) 76 | 77 | 78 | ### Features 79 | 80 | * Auto detect import root from several source folders ([ed4a646](https://github.com/unlight/sublime-import-helper/commit/ed4a646a512b5f952c899178451278e9a49eba6f)) 81 | * New bundler (webpack) ([e1dd06a](https://github.com/unlight/sublime-import-helper/commit/e1dd06aa259c3ab9b4348f9146ec253b90b1afad)) 82 | * Removed `from_semicolon` setting ([48393dc](https://github.com/unlight/sublime-import-helper/commit/48393dc4123009560356d3a7769dcf0a2c246df6)) 83 | * Removed `space_around_braces` setting ([12c7732](https://github.com/unlight/sublime-import-helper/commit/12c7732f426232031042e097b975137b7a1ff7e5)) 84 | * Removed unused files and settings ([8f78077](https://github.com/unlight/sublime-import-helper/commit/8f78077758f3318a5a0bb25d2a353ab5692fc775)) 85 | * Removed unused imports command ([b11a5de](https://github.com/unlight/sublime-import-helper/commit/b11a5de3797b251a76b2891b5d22e6164cfe6d14)) 86 | * Replaced esm-exports by import-adjutor ([bea6160](https://github.com/unlight/sublime-import-helper/commit/bea61609690c0d5047e33313629bc5f2aa5a6f31)) 87 | * Setting `import_path_mapping` changed to `enabled` by default ([03ff2c3](https://github.com/unlight/sublime-import-helper/commit/03ff2c3c6d1f04ac2de11f447f43ce6ab918b1e2)) 88 | * Setting `insert_position` removed ([01eac3f](https://github.com/unlight/sublime-import-helper/commit/01eac3f6df909986afba1e93e4c4c58de2055686)) 89 | 90 | 91 | ### Performance Improvements 92 | 93 | * Load node_modules one by one from source folders ([fd8dbf1](https://github.com/unlight/sublime-import-helper/commit/fd8dbf13332a9742cc412b3d115796d59dcb0189)) 94 | * Moved cut path outside of loop ([65605c2](https://github.com/unlight/sublime-import-helper/commit/65605c2dee81b50360b4c27a72da2d1126b2c804)) 95 | 96 | 97 | ### BREAKING CHANGES 98 | 99 | * Auto detect import root from several source folders (project file is not required) 100 | * Removed unused `import_no_match_count` setting 101 | * Setting `import_path_mapping` changed to `enabled` by default 102 | * Removed `space_around_braces` setting, now it is always true 103 | * Removed `from_semicolon` setting, now it is always true 104 | * Removed unused imports command, the replacement is eslint/tslint rules with fixers https://github.com/cartant/tslint-etc (no-unused-declaration) 105 | * New bundler (webpack) 106 | * Replaced esm-exports by import-adjutor, required Node.js 10+ 107 | 108 | ### Old Changelog 109 | 110 | | Version | Date | Description | 111 | |:--------|:------------|:----------------------------------------------------------------------------------------------------------------| 112 | | 2.3.2 | 27 May 2020 | Improved import to import default statement | 113 | | 2.3.1 | 03 Nov 2019 | Fixed [#71](https://github.com/unlight/sublime-import-helper/issues/71) | 114 | | 2.3.0 | 14 Sep 2019 | Minor fixes, progress status | 115 | | 2.2.1 | 15 Jun 2019 | Minor fixes | 116 | | 2.2.0 | 22 May 2019 | Support passing file / folder exclude options [#64](https://github.com/unlight/sublime-import-helper/issues/64) | 117 | | 2.1.0 | 31 Mar 2019 | Refactoring, updated esm-exports, allow import default, fixed exclude logic | 118 | | 2.0.5 | 22 Dec 2018 | Fixed [#61](https://github.com/unlight/sublime-import-helper/issues/61) | 119 | | 2.0.4 | 08 Dec 2018 | Fixed #62 (remove unsed imports with dollar sign | 120 | | 2.0.3 | 10 Nov 2018 | Alllow to select import path (typescript paths) | 121 | | 2.0.2 | 13 Oct 2018 | Remove trailing index in import path [#59](https://github.com/unlight/sublime-import-helper/issues/59) | 122 | | | | Null reference [#60](https://github.com/unlight/sublime-import-helper/issues/60) | 123 | | 2.0.1 | 13 Aug 2018 | Added key bind to update source modules [#58](https://github.com/unlight/sublime-import-helper/issues/58) | 124 | | 2.0.0 | 20 Apr 2018 | Path mapping [#54](https://github.com/unlight/sublime-import-helper/issues/54) | 125 | | | | Autocomplete support [#57](https://github.com/unlight/sublime-import-helper/issues/57) | 126 | | 1.8.2 | 14 Apr 2018 | Fixed [#56](https://github.com/unlight/sublime-import-helper/issues/56) | 127 | | 1.8.1 | 22 Feb 2018 | Fixed [#51](https://github.com/unlight/sublime-import-helper/issues/51) | 128 | | 1.8.0 | 22 Feb 2018 | Fixed [#50](https://github.com/unlight/sublime-import-helper/issues/50) | 129 | | 1.7.6 | 12 Dec 2017 | Fixed [#47](https://github.com/unlight/sublime-import-helper/issues/47) | 130 | | 1.7.5 | 11 Dec 2017 | Added `from_semicolon` setting | 131 | | 1.7.4 | 30 Nov 2017 | Delayed initialization | 132 | | 1.7.3 | 30 Nov 2017 | `node_bin` setting to explicitly set path to node binary | 133 | | 1.7.1 | 29 Nov 2017 | Try to find node executable | 134 | | 1.7.0 | 22 Nov 2017 | Updated `esm-exports` to 2.0.0 | 135 | | | | Added shell true #43 | 136 | | 1.6.4 | 03 Nov 2017 | Updated `esm-exports` to v0.8.5 | 137 | | 1.6.3 | 20 Oct 2017 | Fixed #42 settings does not work | 138 | | 1.6.2 | 18 Oct 2017 | Fix for empty projects | 139 | | 1.6.0 | 06 Oct 2017 | New feature remove unused imports | 140 | | 1.5.0 | 24 Jun 2017 | Added update source modules command | 141 | | 1.4.1 | 03 May 2017 | Import tsx/jsx without extension | 142 | | 1.4.0 | 29 Mar 2017 | Settings per project | 143 | | 1.3.0 | 23 Mar 2017 | Prevent fail while parse link to not existing directory | 144 | | 1.1.1 | 09 Mar 2017 | Respect exclude_patterns project settings | 145 | | 1.1.0 | 25 Feb 2017 | Auto update imports when new file saved | 146 | | | | Unit tests, bug fixing | 147 | | | | Parse inner modules | 148 | | 1.0.10 | 30 Jan 2017 | Fixed errors when broken package.json | 149 | | 1.0.8 | 24 Jan 2017 | Added .no-sublime-package | 150 | | 1.0.7 | 21 Jan 2017 | Fixed #10 incorrect adding to `import as` | 151 | | 1.0.6 | 10 Jan 2017 | Updated esm-exports modules to 0.3.2 | 152 | | 1.0.5 | 10 Jan 2017 | Updated esm-exports modules to 0.3.1 | 153 | | 1.0.3 | 26 Dec 2016 | Setting `space_around_braces` | 154 | | 1.0.1 | 19 Dec 2016 | Fixed loading settings bug | 155 | | 1.0.0 | 18 Dec 2016 | First release | 156 | -------------------------------------------------------------------------------- /Default.sublime-commands: -------------------------------------------------------------------------------- 1 | [ 2 | { "caption": "Import Helper: Insert import", "command": "insert_import" }, 3 | { "caption": "Import Helper: List imports", "command": "list_imports" }, 4 | { "caption": "Import Helper: Update source modules", "command": "update_source_modules" }, 5 | { "caption": "Import Helper: Initialize / Setup / Update modules", "command": "initialize_setup" }, 6 | { "caption": "Import Helper: Import from clipboard", "command": "import_from_clipboard" } 7 | ] -------------------------------------------------------------------------------- /Default.sublime-keymap: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "keys": ["ctrl+alt+i"], 4 | "command": "insert_import", 5 | "context": [ 6 | { "key": "selector", "operator": "equal", "operand": "source.ts, source.ts.unittest, source.tsx, source.js, source.jsx" } 7 | ] 8 | }, 9 | { 10 | "keys": ["alt+i", "alt+l"], 11 | "command": "list_imports", 12 | "context": [ 13 | { "key": "selector", "operator": "equal", "operand": "source.ts, source.ts.unittest, source.tsx, source.js, source.jsx" } 14 | ] 15 | }, 16 | { 17 | "keys": ["alt+i", "alt+k"], 18 | "command": "import_from_clipboard", 19 | "context": [ 20 | { "key": "selector", "operator": "equal", "operand": "source.ts, source.ts.unittest, source.tsx, source.js, source.jsx" } 21 | ] 22 | }, 23 | { 24 | "keys": ["alt+i", "alt+s"], 25 | "command": "update_source_modules", 26 | "context": [ 27 | { "key": "selector", "operator": "equal", "operand": "source.ts, source.ts.unittest, source.tsx, source.js, source.jsx" } 28 | ] 29 | } 30 | ] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2022 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /Main.sublime-menu: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "caption": "Preferences", 4 | "mnemonic": "n", 5 | "id": "preferences", 6 | "children": 7 | [ 8 | { 9 | "caption": "Package Settings", 10 | "mnemonic": "P", 11 | "id": "package-settings", 12 | "children": 13 | [ 14 | { 15 | "caption": "Import Helper", 16 | "command": "edit_settings", 17 | "args": { 18 | "base_file": "${packages}/ImportHelper/import_helper.sublime-settings" 19 | } 20 | } 21 | ] 22 | } 23 | ] 24 | } 25 | ] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sublime-import-helper 2 | 3 | A Sublime Text Plugin that helps you to import your modules. 4 | 5 | ## Supported Languages 6 | 7 | - TypeScript 8 | - JavaScript (ES2015) 9 | 10 | ## Requirements 11 | 12 | - Node.JS 12.0+ 13 | 14 | ## Installation 15 | 16 | #### [PackageControl](https://packagecontrol.io/packages/ImportHelper) 17 | 18 | - Select `Package Control: Install Package` from command palette 19 | - Select `ImportHelper` 20 | 21 | #### Manual Installation 22 | 23 | You can install `sublime-import-helper` manually using git by running the following command 24 | within sublime packages directory (Preferences > Browse Packages): 25 | 26 | ``` 27 | git clone https://github.com/unlight/sublime-import-helper ImportHelper 28 | ``` 29 | 30 | #### Install Old version 31 | 32 | Check https://github.com/unlight/sublime-import-helper/releases page, 33 | dowload source code archive, unzip to `Data/Packages/ImportHelper`. 34 | 35 | Or you can use git, see like in manual installation: 36 | 37 | ```sh 38 | git clone https://github.com/unlight/sublime-import-helper ImportHelper 39 | cd ImportHelper 40 | git checkout -f v2.3.2 41 | ``` 42 | 43 | ## Usage 44 | 45 | #### Initialize / Setup / Update modules 46 | 47 | - Restart plugin - update node_modules, source modules 48 | 49 | #### Insert import 50 | 51 | - Set cursor or select word 52 | - Press `ctrl+alt+i`, or select the command from command palette 53 | 54 | #### List imports 55 | 56 | - Press `alt+i, alt+l`, or select the command from command palette 57 | 58 | #### Update source modules 59 | 60 | - Press `alt+i, alt+s`, or select the command from command palette 61 | 62 | #### Import from clipboard 63 | 64 | - Copy text to clipboard `ctrl+c` 65 | - Press `alt+i, alt+k`, or select the command from command palette 66 | 67 | ## Screenshots 68 | 69 | ![](https://raw.githubusercontent.com/unlight/sublime-import-helper/master/screenshots/insert-import.gif) 70 | 71 | ## Settings 72 | 73 | There are some several configuration settings. Open plugin settings file by opening in menu: 74 | Preferences -> Package Settings -> Import Helper 75 | Also, there are some optional project specific settings. 76 | The precedence of getting of value of setting is following: 77 | 78 | 1. Project file 79 | 2. Plugin file settings 80 | 3. Default settings 81 | 82 | #### `from_quote` 83 | 84 | What kind of quotes will be used in import statement. 85 | 86 | - Type: `string` 87 | - Default: `'` 88 | 89 | #### `no_semicolon` 90 | 91 | Remove semicolon at the end of `import` string. 92 | 93 | - Type: `boolean` 94 | - Default: `false` 95 | 96 | #### `insert_space_in_braces` 97 | 98 | Insert space after opening and before closing non empty braces. 99 | 100 | - Type: `boolean` 101 | - Default: `true` 102 | 103 | #### `node_bin` 104 | 105 | Sometimes sublime cannot find node executable, if it happens. Set `node_bin` explicitly (e.g. c:/nodejs/node.exe) 106 | 107 | - Type: `string` 108 | - Default: '' (auto detect) 109 | 110 | #### `import_path_mapping` 111 | 112 | How to apply path mapping (read more about [Module Resolution and Path Mapping](http://www.typescriptlang.org/docs/handbook/module-resolution.html)). 113 | 114 | If `enabled` implementation will try to find first matching alias. 115 | 116 | - Type: `string` 117 | - Enum: `['disabled', 'enabled']` 118 | - Default: `enabled` 119 | 120 | #### `autocomplete_export_names` 121 | 122 | Show all possible export names from sources and node modules in autocomplete menu. 123 | 124 | - Type: `boolean` 125 | - Default: `true` 126 | 127 | #### `autocomplete_auto_import` 128 | 129 | Automatically add import statement if export name was selected from autocomplete menu (Ctrl + Space). 130 | Requires `autocomplete_export_names: true`. 131 | 132 | - Type: `boolean` 133 | - Default: `false` 134 | 135 | #### `remove_trailing_index` 136 | 137 | Remove index suffix ending in file path 138 | 139 | - Type: `boolean` 140 | - Default: `true` 141 | 142 | #### `import_root` (project file only) 143 | 144 | Path to your project root folder (not source folder). If not set, 145 | tries automatically detect. For single souce folder `folders[0].path` will be used, 146 | for serveral source folders common path will be used. 147 | 148 | #### `import_file_extension` 149 | 150 | Option what to do with file extension in import statement. 151 | 152 | - Type: `string` 153 | - Default: `remove` 154 | 155 | Options: 156 | 157 | - **`remove`** js/ts/jsx/tsx extension will be removed 158 | - **`js`** js/ts/jsx/tsx will be replaced to js 159 | - **`as_is`** will stay as is 160 | 161 | #### Example of settings in project file: 162 | 163 | Example of project file: 164 | 165 | ``` 166 | { 167 | "import_root": ".", 168 | "from_quote": "'", 169 | "folders": [ 170 | { 171 | "path": "." 172 | } 173 | ] 174 | } 175 | ``` 176 | 177 | ## Notes for `No imports found for ...` message 178 | 179 | Looks like you do not have opened folders in current Sublime window. 180 | 181 | It is recommended to create project from your working files and folders, you can do it in top menu: 182 | `Project -> Save project as...` 183 | Save project file in any place you want. 184 | Then restart Sublime. 185 | 186 | Currently, it is not posssible to detect when project was switched (Project -> Quick Switch Project), 187 | in this case you need manually re-initialize plugin: 188 | Select `Import Helper: Initialize / Setup / Update modules` from command palette 189 | 190 | ## Dev Notes 191 | 192 | ```python 193 | sublime.log_input(True); sublime.log_commands(True); sublime.log_result_regex(True) 194 | sublime.log_input(False); sublime.log_commands(False); sublime.log_result_regex(False) 195 | python3 -m black . 196 | ``` 197 | -------------------------------------------------------------------------------- /Taskfile: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | PATH="$PWD/node_modules/.bin":$PATH 3 | set -e 4 | 5 | prepare() { 6 | set -x 7 | messages=`cat messages.json` 8 | echo "$messages" | jq --arg v "$1" '.[$v] = "CHANGELOG.md"' | tee messages.json 9 | } 10 | 11 | "$@" 12 | -------------------------------------------------------------------------------- /import_helper.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | import sublime_plugin 3 | import os 4 | 5 | PROJECT_NAME = "Import Helper" 6 | PACKAGE_PATH = os.path.dirname(os.path.realpath(__file__)) 7 | RUN_PATH = os.path.join(PACKAGE_PATH, "backend_run.js") 8 | NODE_BIN = "node" 9 | # Collection of entries 10 | NODE_MODULES = [] 11 | SOURCE_MODULES = [] 12 | TYPESCRIPT_PATHS = [] 13 | 14 | from .library.utils import get_time, status_message 15 | from .library.get_setting import get_setting 16 | from .library.debug import debug 17 | from .library.update_source_modules import update_source_modules 18 | from .library.update_node_modules import update_node_modules 19 | from .library.list_imports_command import list_imports_command 20 | from .library.get_import_root import get_import_root 21 | from .library.insert_import_command import insert_import_command 22 | from .library.paste_import_command import paste_import_command 23 | from .library.update_typescript_paths import update_typescript_paths 24 | from .library.query_completions_modules import query_completions_modules 25 | from .library.get_node_executable import get_node_executable 26 | from .library.exec_command import exec_sync 27 | 28 | 29 | def plugin_loaded(): 30 | print() 31 | debug("Plugin loaded", PROJECT_NAME) 32 | sublime.set_timeout(setup, 0) 33 | 34 | 35 | def setup(): 36 | check_node() 37 | update_source_modules(SOURCE_MODULES) 38 | update_node_modules(NODE_MODULES) 39 | update_typescript_paths(TYPESCRIPT_PATHS) 40 | 41 | 42 | def check_node(): 43 | node_executable = get_node_executable() 44 | (err, out) = exec_sync([node_executable, "--version"], None) 45 | version = float(".".join(out[1:].split(".")[0:2])) 46 | if version < 12: 47 | status_message("Node.js version is {0}, but 12+ is required".format(version)) 48 | 49 | 50 | # window.run_command('update_source_modules') 51 | class UpdateSourceModulesCommand(sublime_plugin.WindowCommand): 52 | def run(self): 53 | update_source_modules(SOURCE_MODULES) 54 | 55 | 56 | # Command list_imports - Show all available imports 57 | # view.run_command('list_imports') 58 | class ListImportsCommand(sublime_plugin.TextCommand): 59 | def run(self, edit): 60 | list_imports_command( 61 | view=self.view, 62 | import_root=get_import_root(), 63 | entry_modules=SOURCE_MODULES + NODE_MODULES, 64 | typescript_paths=TYPESCRIPT_PATHS, 65 | ) 66 | 67 | 68 | # Adds import of identifier near cursor (insert_import) 69 | # view.run_command('insert_import', args=({'name': 'createName'})) 70 | class InsertImportCommand(sublime_plugin.TextCommand): 71 | def run(self, edit, name=None, point=None, notify=True): 72 | insert_import_command( 73 | view=self.view, 74 | name=name, 75 | point=point, 76 | notify=notify, 77 | entry_modules=SOURCE_MODULES + NODE_MODULES, 78 | import_root=get_import_root(), 79 | typescript_paths=TYPESCRIPT_PATHS, 80 | ) 81 | 82 | 83 | class PasteImportCommand(sublime_plugin.TextCommand): 84 | def run( 85 | self, 86 | edit, 87 | item, 88 | typescript_paths=TYPESCRIPT_PATHS, 89 | test_selected_index=-1, 90 | settings={}, 91 | ): 92 | replace_content = paste_import_command( 93 | view=self.view, 94 | item=item, 95 | typescript_paths=typescript_paths, 96 | test_selected_index=test_selected_index, 97 | settings=settings, 98 | ) 99 | if type(replace_content) == str: 100 | self.view.replace( 101 | edit, sublime.Region(0, self.view.size()), replace_content 102 | ) 103 | 104 | 105 | # window.run_command('initialize_setup') 106 | # sublime.active_window().run_command('initialize_setup', args={'a':'bar'}) 107 | class InitializeSetupCommand(sublime_plugin.WindowCommand): 108 | def run(self): 109 | setup() 110 | 111 | 112 | # view.run_command('import_from_clipboard') 113 | class ImportFromClipboardCommand(sublime_plugin.TextCommand): 114 | def run(self, edit): 115 | insert_import_command( 116 | view=self.view, 117 | name=sublime.get_clipboard(), 118 | notify=True, 119 | entry_modules=SOURCE_MODULES + NODE_MODULES, 120 | typescript_paths=TYPESCRIPT_PATHS, 121 | ) 122 | 123 | 124 | class ImportHelperEventListener(sublime_plugin.EventListener): 125 | def __init__(self): 126 | self.viewIds = [] 127 | 128 | def on_new(self, view): 129 | self.viewIds.append(view.id()) 130 | 131 | def on_post_save(self, view): 132 | if view.id() in self.viewIds: 133 | self.viewIds.remove(view.id()) 134 | update_source_modules(SOURCE_MODULES) 135 | 136 | 137 | class ImportHelperViewEventListener(sublime_plugin.ViewEventListener): 138 | def __init__(self, view): 139 | super().__init__(view) 140 | self.completions_info = {"time": -1, "result": [], "prefix": ""} 141 | self.in_auto_complete = False 142 | self.autocomplete_point = 0 143 | self.autocomplete_export_names = get_setting("autocomplete_export_names", True) 144 | self.autocomplete_auto_import = get_setting("autocomplete_auto_import", False) 145 | 146 | def on_query_completions(self, prefix, locations): 147 | if not self.autocomplete_export_names or not ( 148 | len(prefix) > 0 149 | and self.view.match_selector( 150 | self.autocomplete_point, 151 | "source.ts, source.ts.unittest, source.tsx, source.js, source.jsx", 152 | ) 153 | ): 154 | return [] 155 | self.autocomplete_point = locations[0] 156 | if ( 157 | get_time() > self.completions_info["time"] + 1 158 | or prefix != self.completions_info["prefix"] 159 | ): 160 | self.completions_info["time"] = get_time() 161 | self.completions_info["prefix"] = prefix 162 | self.completions_info["result"] = query_completions_modules( 163 | prefix=prefix, source_modules=SOURCE_MODULES, node_modules=NODE_MODULES 164 | ) 165 | return self.completions_info["result"] 166 | 167 | def on_post_text_command(self, command_name, args): 168 | if not (self.autocomplete_auto_import and self.autocomplete_export_names): 169 | return 170 | if self.in_auto_complete and command_name in [ 171 | "insert_best_completion", 172 | "insert_dimensions", 173 | ]: 174 | self.in_auto_complete = False 175 | self.view.run_command( 176 | "insert_import", 177 | args=({"point": self.autocomplete_point - 1, "notify": False}), 178 | ) 179 | elif command_name in [ 180 | "auto_complete", 181 | "replace_completion_with_next_completion", 182 | "replace_completion_with_auto_complete", 183 | ]: 184 | self.in_auto_complete = True 185 | elif command_name == "hide_auto_complete": 186 | self.in_auto_complete = False 187 | 188 | def on_activated(self): 189 | self.in_auto_complete = False 190 | -------------------------------------------------------------------------------- /import_helper.sublime-settings: -------------------------------------------------------------------------------- 1 | { 2 | // What kind of quotes will be used in import statement. 3 | // - Type: `string` 4 | // - Default: `'` 5 | "from_quote": "'", 6 | 7 | // Remove semicolon at the end of `import` string. 8 | // - Type: `boolean` 9 | // - Default: `false` 10 | "no_semicolon": false, 11 | 12 | // Insert space after opening and before closing non empty braces. 13 | // - Type: `boolean` 14 | // - Default: `true` 15 | "insert_space_in_braces": true, 16 | 17 | // Sometimes sublime cannot find node executable, if it happens. Set `node_bin` explicitly (e.g. c:/nodejs/node.exe) 18 | // - Type: `string` 19 | // - Default: `` (auto detect) 20 | "node_bin": "", 21 | 22 | // How to apply path mapping (read more about [Module Resolution and Path Mapping](http://www.typescriptlang.org/docs/handbook/module-resolution.html)). 23 | // Disabled by default (`disabled`). 24 | // If `enabled` implementation will try to find first matching alias. 25 | // - Type: `string` 26 | // - Enum: `['disabled', 'enabled']` 27 | // - Default: `enabled` 28 | "import_path_mapping": "enabled", 29 | 30 | // Show all possible export names from sources and node modules in autocomplete menu. 31 | // - Type: `boolean` 32 | // - Default: `true` 33 | "autocomplete_export_names": true, 34 | 35 | // Automatically add import statement if export name was selected from autocomplete menu (Ctrl + Space). 36 | // Requires `autocomplete_export_names: true`. 37 | // - Type: `boolean` 38 | // - Default: `false` 39 | "autocomplete_auto_import": false, 40 | 41 | // Remove trailing index in import path 42 | // - Type: `boolean` 43 | // - Default: `false` 44 | "remove_trailing_index": true, 45 | 46 | // Option what to do with file extension in import statement. 47 | // - Type: `string` 48 | // - Default: `remove` 49 | // Options: 50 | // - **`remove`** js/ts/jsx/tsx extension will be removed 51 | // - **`js`** js/ts/jsx/tsx will be replaced to js 52 | // - **`as_is`** will stay as is 53 | "import_file_extension": "remove" 54 | } 55 | 56 | -------------------------------------------------------------------------------- /library/common_path.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | # Return the longest common sub-path of the sequence of paths given as input. 4 | # The paths are not normalized before comparing them (this is the 5 | # responsibility of the caller). Any trailing separator is stripped from the 6 | # returned path. 7 | 8 | 9 | def common_path(paths): 10 | """Given a sequence of path names, returns the longest common sub-path.""" 11 | if not paths: 12 | raise ValueError("common_path() arg is an empty sequence") 13 | sep = "/" 14 | curdir = "." 15 | 16 | try: 17 | (isabs,) = set(p[:1] == sep for p in paths) 18 | except ValueError: 19 | raise ValueError("Can't mix absolute and relative paths") from None 20 | 21 | split_paths = [] 22 | for path in paths: 23 | path = path.replace("\\", sep) 24 | if os.name == "nt": 25 | path = path.lower() 26 | parts = path.split(sep) 27 | split_paths.append(parts) 28 | 29 | split_paths = [[c for c in s if c and c != curdir] for s in split_paths] 30 | s1 = min(split_paths) 31 | s2 = max(split_paths) 32 | common = s1 33 | for i, c in enumerate(s1): 34 | if c != s2[i]: 35 | common = s1[:i] 36 | break 37 | 38 | prefix = sep if isabs else sep[:0] 39 | result = prefix + sep.join(common) 40 | return result 41 | -------------------------------------------------------------------------------- /library/debug.py: -------------------------------------------------------------------------------- 1 | is_debug = False 2 | 3 | 4 | def debug(s, data=None, force=False): 5 | if is_debug or force: 6 | message = str(s) 7 | if data is not None: 8 | message = "--- " + message + " ---" + "\n" + str(data) 9 | print(message) 10 | print() 11 | -------------------------------------------------------------------------------- /library/exec_command.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | import os 3 | import subprocess 4 | import threading 5 | import traceback 6 | 7 | from ..import_helper import RUN_PATH, PACKAGE_PATH 8 | from .debug import debug 9 | from .get_node_executable import get_node_executable 10 | from .utils import error_message, status_message 11 | 12 | NODE_BIN = None 13 | 14 | 15 | def run_command(command, data=None, callback=None): 16 | global NODE_BIN 17 | if not NODE_BIN: 18 | NODE_BIN = get_node_executable() 19 | debug("run_command", [NODE_BIN, command, data]) 20 | json = sublime.encode_value({"command": command, "args": data}) 21 | err = None 22 | out = None 23 | try: 24 | (err, out) = exec_sync([NODE_BIN, "--no-warnings", RUN_PATH], json) 25 | except Exception as e: 26 | err = traceback.format_exc() 27 | if bool(err): 28 | if callback is not None: 29 | return callback(err, None) 30 | raise Exception(err) 31 | # debug('run_command: trying to decode', out) 32 | result = sublime.decode_value(out) 33 | if callback is not None: 34 | return callback(None, result) 35 | return result 36 | 37 | 38 | def run_command_async(command, data=None, callback=None): 39 | thread = threading.Thread(target=run_command, args=(command, data, callback)) 40 | thread.start() 41 | 42 | 43 | def exec_sync(cmd, input): 44 | if os.name == "nt": 45 | si = subprocess.STARTUPINFO() 46 | si.dwFlags |= subprocess.SW_HIDE | subprocess.STARTF_USESHOWWINDOW 47 | proc = subprocess.Popen( 48 | cmd, 49 | cwd=PACKAGE_PATH, 50 | stdin=subprocess.PIPE, 51 | stdout=subprocess.PIPE, 52 | stderr=subprocess.PIPE, 53 | startupinfo=si, 54 | ) 55 | else: 56 | proc = subprocess.Popen( 57 | cmd, 58 | cwd=PACKAGE_PATH, 59 | stdin=subprocess.PIPE, 60 | stdout=subprocess.PIPE, 61 | stderr=subprocess.PIPE, 62 | ) 63 | if type(input) == str: 64 | input = input.encode() 65 | outs, errs = proc.communicate(input=input) 66 | err = errs.decode().strip() 67 | if bool(err): 68 | debug("Exec error", err, True) 69 | return (err, outs.decode().strip()) 70 | -------------------------------------------------------------------------------- /library/find_executable.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | # https://gist.github.com/4368898 5 | # Public domain code by anatoly techtonik 6 | # AKA Linux `which` and Windows `where` 7 | def find_executable(executable, path=None): 8 | """Find if 'executable' can be run. Looks for it in 'path' 9 | (string that lists directories separated by 'os.pathsep'; 10 | defaults to os.environ['PATH']). Checks for all executable 11 | extensions. Returns full path or None if no command is found. 12 | """ 13 | if path is None: 14 | path = os.environ["PATH"] 15 | paths = path.split(os.pathsep) 16 | extlist = [""] 17 | if os.name == "os2": 18 | (base, ext) = os.path.splitext(executable) 19 | # executable files on OS/2 can have an arbitrary extension, but 20 | # .exe is automatically appended if no dot is present in the name 21 | if not ext: 22 | executable = executable + ".exe" 23 | elif sys.platform == "win32": 24 | pathext = os.environ["PATHEXT"].lower().split(os.pathsep) 25 | (base, ext) = os.path.splitext(executable) 26 | if ext.lower() not in pathext: 27 | extlist = pathext 28 | for ext in extlist: 29 | execname = executable + ext 30 | if os.path.isfile(execname): 31 | return execname 32 | else: 33 | for p in paths: 34 | f = os.path.join(p, execname) 35 | if os.path.isfile(f): 36 | return f 37 | else: 38 | return None 39 | -------------------------------------------------------------------------------- /library/get_exclude_patterns.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | from .get_setting import get_setting 3 | from .debug import debug 4 | from .utils import norm_path 5 | 6 | # Returns Record 0: 24 | result = folders[0] 25 | if len(folders) != 1: 26 | result = common_path(folders) 27 | if bool(result) and os.path.isdir(result): 28 | result = os.path.dirname(result) 29 | return result 30 | active_view = sublime.active_window().active_view() 31 | result = None 32 | if active_view: 33 | file_name = active_view.file_name() 34 | if file_name: 35 | result = os.path.dirname(file_name) 36 | return result 37 | -------------------------------------------------------------------------------- /library/get_node_executable.py: -------------------------------------------------------------------------------- 1 | from .get_setting import get_setting 2 | from .find_executable import find_executable 3 | 4 | 5 | def get_node_executable(): 6 | node_executable = get_setting("node_bin", "") 7 | if not bool(node_executable): 8 | node_executable = find_executable("node") 9 | if not bool(node_executable): 10 | node_executable = "node" 11 | 12 | return node_executable 13 | -------------------------------------------------------------------------------- /library/get_setting.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | 3 | 4 | def get_setting(name, default, settings={}): 5 | result = settings.get(name) 6 | if result is not None: 7 | return result 8 | project_data = sublime.active_window().project_data() 9 | if project_data is not None: 10 | result = project_data.get(name) 11 | if result is None: 12 | settings = sublime.load_settings("import_helper.sublime-settings") or {} 13 | result = settings.get(name) 14 | if result is None: 15 | preferences = sublime.load_settings("Preferences.sublime-settings") 16 | result = preferences.get(name) 17 | if result is None: 18 | result = default 19 | return result 20 | -------------------------------------------------------------------------------- /library/get_source_folders.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | 3 | from .utils import norm_path 4 | 5 | 6 | def get_source_folders(): 7 | return sublime.active_window().folders() 8 | -------------------------------------------------------------------------------- /library/identifier_name.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | 4 | def identifier_name(text): 5 | return camelcase(text) 6 | 7 | 8 | # https://github.com/ecrmnn/camelcase 9 | def camelcase(*arguments): 10 | if type(arguments[0]) == list: 11 | # Arguments passed as list 12 | string = "_".join(arguments[0]) 13 | elif len(arguments) != 1: 14 | # Multiple arguments passed (variadict) 15 | string = "_".join(list(arguments)) 16 | else: 17 | # Argument was a string 18 | string = arguments[0] 19 | 20 | items = re.split(r"\-|\_|\s", string) 21 | items = list(filter(None, items)) 22 | 23 | titleCased = map(lambda item: item.lower().title(), items[1:]) 24 | 25 | return items[0].lower() + "".join(titleCased) 26 | -------------------------------------------------------------------------------- /library/insert_import_command.py: -------------------------------------------------------------------------------- 1 | import re 2 | import sublime 3 | from functools import partial 4 | 5 | from .debug import debug 6 | from .get_import_root import get_import_root 7 | from .panel_items import panel_items 8 | 9 | 10 | def insert_import_command( 11 | view, 12 | point, 13 | notify, 14 | entry_modules, 15 | import_root, 16 | name=None, 17 | typescript_paths=[], 18 | ): 19 | if not name: 20 | name = get_name_candidate(view, point) 21 | name = re.sub(r"[^\w\-\@\/]", "", name) 22 | if not name: 23 | return 24 | 25 | debug("insert_import: trying to import", "`{0}`".format(name)) 26 | 27 | (items, matches) = panel_items( 28 | name=name, entry_modules=entry_modules, import_root=import_root 29 | ) 30 | 31 | if len(items) == 0: 32 | if notify: 33 | view.show_popup("No imports found for `{0}`".format(name)) 34 | return 35 | if len(items) == 1: 36 | item = matches[0] 37 | view.run_command( 38 | "paste_import", {"item": item, "typescript_paths": typescript_paths} 39 | ) 40 | return 41 | 42 | def on_select(index): 43 | if index == -1: 44 | return 45 | selected_item = matches[index] 46 | debug("insert_import: on_select", selected_item) 47 | view.run_command( 48 | "paste_import", 49 | {"item": selected_item, "typescript_paths": typescript_paths}, 50 | ) 51 | 52 | view.window().show_quick_panel(items, on_select) 53 | 54 | 55 | def get_name_candidate(view, point): 56 | point_region = view.sel()[0] 57 | if point is not None: 58 | point_region = sublime.Region(point, point) 59 | name = view.substr(point_region).strip() 60 | if not name: 61 | cursor_region = view.expand_by_class( 62 | point_region, 63 | sublime.CLASS_WORD_START 64 | | sublime.CLASS_LINE_START 65 | | sublime.CLASS_PUNCTUATION_START 66 | | sublime.CLASS_WORD_END 67 | | sublime.CLASS_PUNCTUATION_END 68 | | sublime.CLASS_LINE_END, 69 | ) 70 | name = view.substr(cursor_region) 71 | return name 72 | -------------------------------------------------------------------------------- /library/list_imports_command.py: -------------------------------------------------------------------------------- 1 | from functools import partial 2 | 3 | from .get_import_root import get_import_root 4 | from .panel_items import panel_items 5 | from .debug import debug 6 | 7 | 8 | def list_imports_command(view, import_root, entry_modules, typescript_paths=[]): 9 | 10 | (items, matches) = panel_items(entry_modules=entry_modules, import_root=import_root) 11 | 12 | def on_select(index): 13 | if index == -1: 14 | return 15 | selected_item = matches[index] 16 | debug("list_imports_command:on_select", selected_item) 17 | view.run_command( 18 | "paste_import", 19 | {"item": selected_item, "typescript_paths": typescript_paths}, 20 | ) 21 | 22 | view.window().show_quick_panel(items, on_select) 23 | -------------------------------------------------------------------------------- /library/on_done_func.py: -------------------------------------------------------------------------------- 1 | # Return a function which is used with sublime list picking. 2 | def on_done_func(choices, func): 3 | def on_done(index): 4 | if index >= 0: 5 | return func(choices[index]) 6 | 7 | return on_done 8 | -------------------------------------------------------------------------------- /library/panel_items.py: -------------------------------------------------------------------------------- 1 | import os 2 | from .unixify import unixify 3 | 4 | 5 | def panel_items(name=None, entry_modules=[], import_root=None): 6 | result = [] 7 | matches = [] 8 | slice_length = 0 9 | if import_root is not None: 10 | slice_length = len(import_root) + 1 11 | for item in entry_modules: 12 | if name is not None and item.get("name") != name: 13 | continue 14 | panel_item = get_panel_item(item, slice_length) 15 | result.append(panel_item) 16 | matches.append(item) 17 | return (result, matches) 18 | 19 | 20 | # Prepare string to show in window's quick panel. 21 | def get_panel_item(item, slice_length): 22 | module = item.get("module") 23 | name = item.get("name") 24 | if module is not None: 25 | if module == name and item.get("isDefault") == True: 26 | return module + "/default" 27 | return module + "/" + name 28 | filepath = os.path.normpath(item["filepath"])[slice_length:] 29 | return unixify(filepath) + "/" + name 30 | -------------------------------------------------------------------------------- /library/paste_import_command.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | 3 | from .get_setting import get_setting 4 | from .get_from_paths import get_from_paths 5 | from .on_done_func import on_done_func 6 | from .debug import debug 7 | from .exec_command import run_command 8 | 9 | # view.run_command('paste_import', args=({'item': {'filepath': 'xxx', 'name': 'aaa', 'isDefault': False}, 'typescript_paths': []})) 10 | # view.run_command('paste_import', args=({'item': {'isDefault': True, 'module': 'worker_threads', 'name': 'worker_threads'}})) 11 | def paste_import_command( 12 | view, item, typescript_paths=[], test_selected_index=-1, settings={} 13 | ): 14 | debug("paste_import_command:item", item) 15 | file_name = view.file_name() or "." 16 | import_file_extension = get_setting("import_file_extension", "remove", settings) 17 | remove_trailing_index = get_setting("remove_trailing_index", True, settings) 18 | from_paths = get_from_paths( 19 | item, file_name, typescript_paths, remove_trailing_index, import_file_extension 20 | ) 21 | 22 | if len(from_paths) > 1: 23 | choices = [{"item": item, "path": path} for path in from_paths] 24 | 25 | def on_select(selected): 26 | item = selected.get("item") 27 | item["module"] = selected.get("path") 28 | view.run_command("paste_import", {"item": item, "typescript_paths": []}) 29 | 30 | if test_selected_index != -1: 31 | on_select(choices[test_selected_index]) 32 | return 33 | view.window().show_quick_panel(from_paths, on_done_func(choices, on_select)) 34 | return 35 | 36 | if len(from_paths) == 0: 37 | raise Exception("len from_paths must be not empty") 38 | 39 | specifier = from_paths[0] 40 | soucefile_content = view.substr(sublime.Region(0, view.size())) 41 | result = run_command( 42 | "insertImport", 43 | { 44 | "declaration": { 45 | "name": item["name"], 46 | "specifier": specifier, 47 | "isDefault": item.get("isDefault"), 48 | }, 49 | "sourceFileContent": soucefile_content, 50 | "manipulationSettings": { 51 | "quoteKind": get_setting("from_quote", "'", settings), 52 | "noSemicolon": get_setting("no_semicolon", False, settings), 53 | "insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces": get_setting( 54 | "insert_space_in_braces", True, settings 55 | ), 56 | }, 57 | "sorted": True, 58 | }, 59 | ) 60 | # debug("paste_import_command:result", result) 61 | return result 62 | -------------------------------------------------------------------------------- /library/query_completions_modules.py: -------------------------------------------------------------------------------- 1 | def query_completions_modules(prefix, source_modules, node_modules): 2 | result = [] 3 | for item in source_modules: 4 | name = item.get("name") 5 | if name is None: 6 | continue 7 | if not name.startswith(prefix): 8 | continue 9 | result.append([name + "\tsource_modules", name]) 10 | for item in node_modules: 11 | name = item.get("name") 12 | module = item.get("module") 13 | if name is None or module is None: 14 | continue 15 | if not name.startswith(prefix): 16 | continue 17 | result.append([name + "\tnode_modules/" + module, name]) 18 | return result 19 | -------------------------------------------------------------------------------- /library/read_json.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | import os 3 | 4 | 5 | def read_json(file): 6 | if not os.path.isfile(file): 7 | return None 8 | fo = open(file, "r") 9 | data = fo.read() 10 | fo.close() 11 | return sublime.decode_value(data) 12 | -------------------------------------------------------------------------------- /library/try_typescript_path.py: -------------------------------------------------------------------------------- 1 | import os 2 | from .get_setting import get_setting 3 | from .debug import debug 4 | 5 | 6 | def try_typescript_path(filepath, typescript_paths): 7 | import_path_mapping = get_setting("import_path_mapping", "none") 8 | if import_path_mapping == "enabled": 9 | (drive, filepath) = os.path.splitdrive(filepath) 10 | filepath = filepath.replace("\\", "/") 11 | # debug("filepath", filepath) 12 | for ts_path in typescript_paths: 13 | base_dir = ts_path["base_dir"] 14 | path_value = ts_path["path_value"] 15 | path_to = ts_path["path_to"] 16 | (drive, test_path) = os.path.splitdrive( 17 | os.path.normpath(os.path.join(base_dir, path_value)).replace("\\", "/") 18 | ) 19 | # debug("test_path", test_path) 20 | # "@app/*" :["app/*"] 21 | if test_path[-2:] == "/*" and path_to[-2:] == "/*": 22 | test_path = test_path[0:-2] 23 | if filepath.startswith(test_path): 24 | test_path = path_to[0:-2] + filepath[len(test_path) :] 25 | return test_path 26 | # "@lib": ["app/lib"] 27 | if filepath == test_path or filepath in [ 28 | test_path + "/index.ts", 29 | test_path + "/index.tsx", 30 | test_path + "/index.js", 31 | test_path + "/index.jsx", 32 | ]: 33 | return path_to 34 | return None 35 | -------------------------------------------------------------------------------- /library/unixify.py: -------------------------------------------------------------------------------- 1 | def unixify(path, extension_option="remove"): 2 | path = path.replace("\\", "/") 3 | 4 | if extension_option == "as_is": 5 | return path 6 | 7 | extension = tjsx_extension(path) 8 | 9 | if type(extension) == str: 10 | if extension_option == "remove": 11 | return path[0 : -len(extension)] 12 | elif extension_option == "js": 13 | return path[0 : -len(extension)] + ".js" 14 | 15 | return path 16 | 17 | 18 | def tjsx_extension(path): 19 | ext3 = path[-3:] 20 | if ext3 == ".ts" or ext3 == ".js": 21 | return ext3 22 | ext4 = path[-4:] 23 | if ext4 == ".tsx" or ext4 == ".jsx": 24 | return ext4 25 | -------------------------------------------------------------------------------- /library/update_node_modules.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | from .debug import debug 3 | from .utils import error_message, status_message 4 | from .get_source_folders import get_source_folders 5 | from .exec_command import run_command_async 6 | 7 | 8 | def update_node_modules(node_modules=[]): 9 | node_modules.clear() 10 | 11 | source_folders = get_source_folders() 12 | 13 | def callback(err, result): 14 | get_modules_callback(err, result, node_modules) 15 | next() 16 | 17 | def next(): 18 | if len(source_folders) > 0: 19 | source_folder = source_folders.pop(0) 20 | run_command_async( 21 | "exportsNodeModules", {"directory": source_folder}, callback 22 | ) 23 | 24 | next() 25 | 26 | 27 | def get_modules_callback(err, result, node_modules): 28 | if err: 29 | return error_message(err) 30 | if type(result) is not list: 31 | return error_message("Unexpected type of result: {0}".format(type(result))) 32 | node_modules.extend(result) 33 | count = len(result) 34 | debug("get_modules_callback:result.length", count) 35 | status_message("+{0} node modules found ({1})".format(count, len(node_modules))) 36 | -------------------------------------------------------------------------------- /library/update_source_modules.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | from .debug import debug 3 | from .get_source_folders import get_source_folders 4 | from .get_exclude_patterns import get_exclude_patterns 5 | from .utils import error_message, status_message 6 | from .exec_command import run_command_async 7 | 8 | 9 | def update_source_modules(source_modules): 10 | source_folders = get_source_folders() 11 | exclude_patterns = get_exclude_patterns() 12 | 13 | debug("update_source_modules:source_folders", source_folders) 14 | 15 | def callback(err, result): 16 | source_modules_callback(err, result, source_modules) 17 | next() 18 | 19 | def next(): 20 | if len(source_folders) > 0: 21 | source_folder = source_folders.pop(0) 22 | folder_patterns = exclude_patterns.get(source_folder) or {} 23 | debug("folder_patterns", folder_patterns) 24 | run_command_async( 25 | "exportsFromDirectory", 26 | { 27 | "directory": source_folder, 28 | "folderExcludePatterns": folder_patterns.get( 29 | "folderExcludePatterns" 30 | ), 31 | "fileExcludePatterns": folder_patterns.get("fileExcludePatterns"), 32 | }, 33 | callback, 34 | ) 35 | 36 | source_modules.clear() 37 | next() 38 | 39 | 40 | def source_modules_callback(err, result, source_modules): 41 | if err: 42 | return error_message(err) 43 | if type(result) is not list: 44 | return error_message("Unexpected type of result: " + type(result)) 45 | for item in result: 46 | filepath = item.get("filepath") 47 | if filepath is None: 48 | continue 49 | source_modules.append(item) 50 | count = len(source_modules) 51 | status_message("{0} source modules found".format(count)) 52 | debug("Update source modules", count) 53 | -------------------------------------------------------------------------------- /library/update_typescript_paths.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from .read_json import read_json 4 | from .get_source_folders import get_source_folders 5 | from .debug import debug 6 | 7 | 8 | def update_typescript_paths(typescript_paths): 9 | typescript_paths.clear() 10 | source_folders = get_source_folders() 11 | for folder in source_folders: 12 | tsconfig_file = os.path.normpath(os.path.join(folder, "tsconfig.json")) 13 | if not os.path.isfile(tsconfig_file): 14 | continue 15 | tsconfig = read_json(tsconfig_file) or {} 16 | compilerOptions = tsconfig.get("compilerOptions") 17 | if compilerOptions is None: 18 | continue 19 | baseUrl = compilerOptions.get("baseUrl") 20 | if baseUrl is None: 21 | continue 22 | base_dir = os.path.normpath( 23 | os.path.join(os.path.dirname(tsconfig_file), baseUrl) 24 | ) 25 | paths = compilerOptions.get("paths") 26 | if not paths: 27 | continue 28 | for path_to, pathValues in paths.items(): 29 | for path_value in pathValues: 30 | typescript_paths.append( 31 | {"base_dir": base_dir, "path_value": path_value, "path_to": path_to} 32 | ) 33 | debug("update_typescript_paths:typescript_paths", typescript_paths) 34 | -------------------------------------------------------------------------------- /library/utils.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | import os 3 | import time 4 | 5 | from ..import_helper import PROJECT_NAME 6 | 7 | 8 | def norm_path(base, to): 9 | return os.path.normpath(os.path.join(os.path.dirname(base), to)) 10 | 11 | 12 | def get_time(): 13 | return time.time() 14 | 15 | 16 | def error_message(err): 17 | sublime.error_message(PROJECT_NAME + "\n" + str(err)) 18 | 19 | 20 | def status_message(string): 21 | sublime.status_message("{0}: {1}".format(PROJECT_NAME, string)) 22 | -------------------------------------------------------------------------------- /messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "3.0.0": "CHANGELOG.md", 3 | "3.0.1": "CHANGELOG.md", 4 | "3.0.2": "CHANGELOG.md", 5 | "3.1.0": "CHANGELOG.md", 6 | "4.0.0": "CHANGELOG.md", 7 | "4.1.0": "CHANGELOG.md", 8 | "4.1.1": "CHANGELOG.md", 9 | "4.2.0": "CHANGELOG.md" 10 | } 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sublime-import-helper", 3 | "version": "0.0.0-dev", 4 | "license": "MIT", 5 | "homepage": "https://github.com/unlight/sublime-import-helper#readme", 6 | "scripts": { 7 | "build": "webpack", 8 | "commit": "cz" 9 | }, 10 | "devDependencies": { 11 | "@semantic-release/changelog": "^6.0.1", 12 | "@semantic-release/exec": "^6.0.3", 13 | "@semantic-release/git": "^10.0.1", 14 | "@types/node": "17.0.31", 15 | "@types/react": "^18.0.8", 16 | "cz-conventional-changelog": "^3.3.0", 17 | "import-adjutor": "^2.2.0", 18 | "prettier": "^2.6.2", 19 | "react": "^18.1.0", 20 | "semantic-release": "^19.0.2", 21 | "webpack": "^5.72.0", 22 | "webpack-cli": "^4.9.2", 23 | "@commitlint/config-conventional": "^16.2.4" 24 | }, 25 | "repository": { 26 | "type": "git", 27 | "url": "https://github.com/unlight/sublime-import-helper.git" 28 | }, 29 | "bugs": { 30 | "url": "https://github.com/unlight/sublime-import-helper/issues" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /screenshots/insert-import.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unlight/sublime-import-helper/8adfbafc74bd2fd147c7c8c983f7603c741a4111/screenshots/insert-import.gif -------------------------------------------------------------------------------- /test_playground/app/t1/src/component/index.ts: -------------------------------------------------------------------------------- 1 | export const test = 5; 2 | -------------------------------------------------------------------------------- /test_playground/app/t1/volumes/x/test.ts: -------------------------------------------------------------------------------- 1 | export const VOLUME = 11; 2 | -------------------------------------------------------------------------------- /test_playground/app/t2/src/component/abc.component.ts: -------------------------------------------------------------------------------- 1 | export class AbcComponent {} 2 | 3 | export const AbcConfig = 1; 4 | -------------------------------------------------------------------------------- /test_playground/app/t2/src/component/about.component.ts: -------------------------------------------------------------------------------- 1 | export class AboutComponent {} 2 | -------------------------------------------------------------------------------- /test_playground/app/t2/src/component/dummy.component.ts: -------------------------------------------------------------------------------- 1 | export class DummyComponent {} 2 | -------------------------------------------------------------------------------- /test_playground/app/t2/src/component/index.ts: -------------------------------------------------------------------------------- 1 | export * from './abc.component'; 2 | export * from './x.component'; 3 | -------------------------------------------------------------------------------- /test_playground/app/t2/src/component/x.component.ts: -------------------------------------------------------------------------------- 1 | export const x1 = 1; 2 | export * from './x'; 3 | -------------------------------------------------------------------------------- /test_playground/app/t2/src/component/x/index.ts: -------------------------------------------------------------------------------- 1 | export * from './x2.component'; 2 | export const index1 = 1; 3 | export const index2 = 2; 4 | -------------------------------------------------------------------------------- /test_playground/app/t2/src/component/x/x2.component.ts: -------------------------------------------------------------------------------- 1 | export function x2() {} 2 | -------------------------------------------------------------------------------- /test_playground/bad_json/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bad_json", 3 | xxxxxxxx 4 | } 5 | -------------------------------------------------------------------------------- /test_playground/component/abc.component.ts: -------------------------------------------------------------------------------- 1 | export class AbcComponent { } 2 | 3 | export const AbcConfig = 1; 4 | -------------------------------------------------------------------------------- /test_playground/component/about.component.ts: -------------------------------------------------------------------------------- 1 | export class AboutComponent { } -------------------------------------------------------------------------------- /test_playground/component/dummy.component.ts: -------------------------------------------------------------------------------- 1 | export class DummyComponent { } -------------------------------------------------------------------------------- /test_playground/component/index.ts: -------------------------------------------------------------------------------- 1 | export * from './abc.component'; 2 | export * from './x.component'; 3 | -------------------------------------------------------------------------------- /test_playground/component/x.component.ts: -------------------------------------------------------------------------------- 1 | export const x1 = 1; 2 | export * from './x'; 3 | -------------------------------------------------------------------------------- /test_playground/component/x/index.ts: -------------------------------------------------------------------------------- 1 | export * from './x2.component'; 2 | export const index1 = 1; 3 | export const index2 = 2; -------------------------------------------------------------------------------- /test_playground/component/x/x2.component.ts: -------------------------------------------------------------------------------- 1 | export function x2() { } 2 | -------------------------------------------------------------------------------- /test_playground/createname.ts: -------------------------------------------------------------------------------- 1 | type NameOrNameArray = string | string[]; 2 | 3 | export const createname = 1; 4 | 5 | export interface FullName { 6 | firstName: string; 7 | lastName: string; 8 | } 9 | 10 | export function createName(name: NameOrNameArray) { 11 | if (typeof name === 'string') { 12 | return name; 13 | } else { 14 | return name.join(' '); 15 | } 16 | } 17 | 18 | var greetingMessage = `Greetings, ${createName(['Sam', 'Smith'])}`; 19 | alert(greetingMessage); 20 | -------------------------------------------------------------------------------- /test_playground/empty_file/package.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unlight/sublime-import-helper/8adfbafc74bd2fd147c7c8c983f7603c741a4111/test_playground/empty_file/package.json -------------------------------------------------------------------------------- /test_playground/empty_pkg/package.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /test_playground/ignored/folder/inject.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Inject a dependency 3 | * @param token The DI-token 4 | * @param factory Factory function that returns the dependency 5 | * @returns The dependency or a mock object if the dependency was mocked using mock() 6 | */ 7 | export declare const inject: (token: string, factory: () => T) => T; 8 | export declare const injector: { 9 | provide: (token: string, value: () => any) => void; 10 | mock: (token: string, value: () => any) => void; 11 | clear: () => void; 12 | }; 13 | -------------------------------------------------------------------------------- /test_playground/ignored/inject.ts: -------------------------------------------------------------------------------- 1 | let dependencies: {[id: string]: () => any} = {}; 2 | 3 | const provide = (token: string, value: () => any) => { 4 | dependencies[token] = value; 5 | }; 6 | 7 | const clear = () => { 8 | dependencies = {}; 9 | }; 10 | /** 11 | * Inject a dependency 12 | * @param token The DI-token 13 | * @param factory Factory function that returns the dependency 14 | * @returns The dependency or a mock object if the dependency was mocked using mock() 15 | */ 16 | export const inject = (token: string, factory: () => T): T => { 17 | if (typeof dependencies[token] !== 'undefined') { 18 | return dependencies[token](); 19 | } 20 | return factory(); 21 | }; 22 | 23 | export const injector = { 24 | provide: provide, 25 | mock: provide, 26 | clear: clear, 27 | }; 28 | -------------------------------------------------------------------------------- /test_playground/lib/inheritance.js: -------------------------------------------------------------------------------- 1 | export class Animal { 2 | constructor(name) { } 3 | move(distanceInMeters = 0) { 4 | console.log(`${this.name} moved ${distanceInMeters}m.`); 5 | } 6 | } 7 | 8 | export class Snake extends Animal { 9 | constructor(name) { super(name); } 10 | move(distanceInMeters = 5) { 11 | console.log("Slithering..."); 12 | super.move(distanceInMeters); 13 | } 14 | } 15 | 16 | export class Horse extends Animal { 17 | constructor(name) { super(name); } 18 | move(distanceInMeters = 45) { 19 | console.log("Galloping..."); 20 | super.move(distanceInMeters); 21 | } 22 | } 23 | 24 | let sam = new Snake("Sammy the Python"); 25 | let tom = new Horse("Tommy the Palomino"); 26 | 27 | sam.move(); 28 | tom.move(34); -------------------------------------------------------------------------------- /test_playground/lib/inheritance_2.js: -------------------------------------------------------------------------------- 1 | // Try import Animal, path must be ./inheritance or @Libs/inheritance 2 | import {Greeter, greeter} from '../greeter' 3 | -------------------------------------------------------------------------------- /test_playground/lib/mod.mjs: -------------------------------------------------------------------------------- 1 | export const modjs = 1; 2 | -------------------------------------------------------------------------------- /test_playground/main.ts: -------------------------------------------------------------------------------- 1 | import { index2 } from '@Components'; 2 | import mocha from 'mocha'; 3 | import { createName, FullName } from './createname'; 4 | import { index1 } from './component/x.component'; 5 | import * as crossSpawn from 'cross-spawn'; 6 | import 'rxjs/operators/map'; 7 | import React, { useState, useCallback } from 'react'; 8 | import { x1 } from './component'; 9 | 10 | // Cases: 11 | // React: useState useCallback 12 | // in {x1} add x2 13 | 14 | export class Greeter { 15 | greeting: T; 16 | constructor(message: T) { 17 | this.greeting = message; 18 | } 19 | greet() { 20 | return this.greeting; 21 | } 22 | } 23 | 24 | export let greeter = new Greeter('Hello, world'); 25 | 26 | let button = document.createElement('button'); 27 | button.textContent = 'Say Hello'; 28 | button.onclick = function () { 29 | alert(greeter.greet()); 30 | }; 31 | // createName FullName index1 index2 worker_threads mocha fs readFileSync copyFileSync 32 | // `cross-spawn` mocha Animal Snake Horse 33 | document.body.appendChild(button); 34 | // HTTP_STATUS_CONTINUE 35 | function foo(res) { 36 | res.json(typeof HTTP2_HEADER_STATUS); 37 | } 38 | 39 | export const $goo = 1; 40 | export const goo$ = 2; 41 | -------------------------------------------------------------------------------- /test_playground/message.tsx: -------------------------------------------------------------------------------- 1 | export class MessageComponent { 2 | } -------------------------------------------------------------------------------- /test_playground/typescript_paths.ts: -------------------------------------------------------------------------------- 1 | // Test 1: Animal -> @Libs/inheritance 2 | // Test 2: DummyComponent -> @Dummy 3 | // Test 3 (Not implemented): AboutComponent -> ambiguous ('@component/any') // "@component/*": ["./test_playground/component"] 4 | // Test 4 Index: AbcComponent -> import {AbcComponent} from '@Components' 5 | -------------------------------------------------------------------------------- /test_playground/unused.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { Greeter as gr1 } from './greeter'; // Unused 3 | import { FullName as f, createname as cr } from './createname'; // Partial Used 4 | import {createname, FullName, createname as xx} from './createname'; // Unused all 5 | import {Greeter} from './greeter'; // Used 6 | import { Greeter as gr } from './greeter'; // Unused 7 | import {greeter as lg} from './greeter'; // Used 8 | import * as someLib1 from 'prettier'; // Unused 9 | import someLib2 from 'prettier'; // Unused 10 | import $x from 'prettier'; // Unused 11 | import { $x$ } from 'prettier'; // Unused 12 | import {$x1, x as $xx, $x as $xxx, $a as x$} from 'd'; // Unused all 13 | 14 | console.log("Greeter", Greeter); 15 | console.log("cr", cr); 16 | console.log("lg", lg); 17 | 18 | console.log(typeof x2); -------------------------------------------------------------------------------- /tests/test.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | import sys 3 | from unittest import TestCase 4 | 5 | import_helper = sys.modules["ImportHelper.import_helper"] 6 | debug = sys.modules["ImportHelper.library.debug"].debug 7 | 8 | class TestExample(TestCase): 9 | def setUp(self): 10 | self.view = sublime.active_window().new_file() 11 | # make sure we have a window to work with 12 | s = sublime.load_settings("Preferences.sublime-settings") 13 | s.set("close_windows_when_empty", False) 14 | 15 | def tearDown(self): 16 | if self.view: 17 | self.view.set_scratch(True) 18 | self.view.window().focus_view(self.view) 19 | self.view.window().run_command("close_file") 20 | 21 | def getRow(self, row): 22 | return self.view.substr(self.view.line(self.view.text_point(row, 0))) 23 | 24 | def test_smoke(self): 25 | self.assertTrue(True) 26 | 27 | def test_hello_world(self): 28 | self.view.run_command("hello_world") 29 | first_row = self.getRow(0) 30 | 31 | def setText(view, string): 32 | view.run_command("select_all") 33 | view.run_command("left_delete") 34 | view.run_command("insert", {"characters": string}) 35 | 36 | class TestDebugDisabled(TestCase): 37 | def test_debug_disabled(self): 38 | self.assertFalse(sys.modules["ImportHelper.library.debug"].is_debug) 39 | 40 | class TestDoInsertImport(TestCase): 41 | def setUp(self): 42 | self.view = sublime.active_window().new_file() 43 | s = sublime.load_settings("Preferences.sublime-settings") 44 | s.set("close_windows_when_empty", False) 45 | 46 | def tearDown(self): 47 | if self.view: 48 | self.view.set_scratch(True) 49 | self.view.window().focus_view(self.view) 50 | self.view.window().run_command("close_file") 51 | 52 | def getRow(self, row): 53 | return self.view.substr(self.view.line(self.view.text_point(row, 0))) 54 | 55 | def getAll(self): 56 | return self.view.substr(sublime.Region(0, self.view.size())) 57 | 58 | def test_smoke(self): 59 | setText(self.view, "") 60 | self.view.run_command( 61 | "paste_import", 62 | { 63 | "item": { 64 | "filepath": "dinah_widdoes", 65 | "name": "Lakia", 66 | "isDefault": False, 67 | } 68 | }, 69 | ) 70 | first_row = self.getRow(0) 71 | self.assertEqual("import { Lakia } from './dinah_widdoes';", first_row) 72 | 73 | def test_side_effect_import(self): 74 | setText(self.view, 'import "rxjs/operators/map"\n') 75 | self.view.run_command( 76 | "paste_import", 77 | {"item": {"filepath": "side/effect", "name": "effect", "isDefault": False}}, 78 | ) 79 | self.assertIn("./side/effect", self.getRow(1)) 80 | 81 | def test_paste_import_if_imports_statements_in_the_middle(self): 82 | setText(self.view, ("\n" * 14) + 'import x from "x"\n') 83 | self.view.run_command( 84 | "paste_import", 85 | {"item": {"filepath": "filepath", "name": "name", "isDefault": False}}, 86 | ) 87 | first_row = self.getRow(0) 88 | self.assertNotIn("./filepath", first_row) 89 | self.assertNotIn("name", first_row) 90 | 91 | def test_add_specifier_to_default_import(self): 92 | setText(self.view, "import React from 'react'\n") 93 | self.view.run_command( 94 | "paste_import", 95 | {"item": {"name": "useCallback", "module": "react", "isDefault": False}}, 96 | ) 97 | self.assertIn("import React, { useCallback } from 'react", self.getRow(0)) 98 | 99 | def test_add_specifier_to_mixed_import(self): 100 | setText(self.view, "import React, { useCallback } from 'react'\n") 101 | self.view.run_command( 102 | "paste_import", 103 | {"item": {"name": "useState", "module": "react", "isDefault": False}}, 104 | ) 105 | self.assertIn( 106 | "import React, { useCallback, useState } from 'react", self.getRow(0) 107 | ) 108 | 109 | # These tests shows additional popup 110 | def test_typescript_paths(self): 111 | typescript_paths = [ 112 | { 113 | "path_to": "@Libs/*", 114 | "path_value": "./test_playground/lib/*", 115 | "base_dir": "/base_dir", 116 | }, 117 | ] 118 | setText(self.view, "") 119 | self.view.run_command( 120 | "paste_import", 121 | { 122 | "item": { 123 | "filepath": "/base_dir/test_playground/lib/a/b/c.ts", 124 | "name": "name", 125 | "isDefault": False, 126 | }, 127 | "typescript_paths": typescript_paths, 128 | "test_selected_index": 0, 129 | }, 130 | ) 131 | self.assertIn("@Libs/a/b/c", self.getRow(0)) 132 | 133 | def test_typescript_paths_2(self): 134 | typescript_paths = [ 135 | { 136 | "path_to": "@z_component", 137 | "path_value": "./app/components/z.ts", 138 | "base_dir": "/base_dir", 139 | }, 140 | ] 141 | setText(self.view, "") 142 | self.view.run_command( 143 | "paste_import", 144 | { 145 | "item": { 146 | "filepath": "/base_dir/app/components/z.ts", 147 | "name": "zoo", 148 | "isDefault": False, 149 | }, 150 | "typescript_paths": typescript_paths, 151 | "test_selected_index": 0, 152 | }, 153 | ) 154 | self.assertIn("import { zoo } from '@z_component'", self.getRow(0)) 155 | 156 | def test_typescript_paths_3(self): 157 | typescript_paths = [ 158 | { 159 | "path_to": "@components", 160 | "path_value": "./app/components", 161 | "base_dir": "/base_dir", 162 | }, 163 | ] 164 | setText(self.view, "") 165 | self.view.run_command( 166 | "paste_import", 167 | { 168 | "item": { 169 | "filepath": "/base_dir/app/components/index.ts", 170 | "name": "koo", 171 | "isDefault": False, 172 | }, 173 | "typescript_paths": typescript_paths, 174 | "test_selected_index": 0, 175 | }, 176 | ) 177 | self.assertIn("import { koo } from '@components'", self.getRow(0)) 178 | 179 | def test_remove_importpath_index(self): 180 | setText(self.view, "") 181 | self.view.run_command( 182 | "paste_import", 183 | { 184 | "item": { 185 | "filepath": "./component/x/index", 186 | "name": "x1", 187 | "isDefault": False, 188 | } 189 | }, 190 | ) 191 | self.assertIn("import { x1 } from './component/x'", self.getRow(0)) 192 | self.view.run_command( 193 | "paste_import", 194 | { 195 | "item": { 196 | "filepath": "./component/x/index", 197 | "name": "x2", 198 | "isDefault": False, 199 | } 200 | }, 201 | ) 202 | self.assertIn("import { x1, x2 } from './component/x'", self.getRow(0)) 203 | 204 | def test_paste_import_module(self): 205 | setText(self.view, "") 206 | self.view.run_command( 207 | "paste_import", 208 | { 209 | "item": { 210 | "module": "@angular/core", 211 | "isDefault": False, 212 | "name": "Inject", 213 | } 214 | }, 215 | ) 216 | self.assertIn("import { Inject } from '@angular/core'", self.getRow(0)) 217 | 218 | def test_paste_import_no_semicolon(self): 219 | setText(self.view, "") 220 | self.view.run_command( 221 | "paste_import", 222 | { 223 | "item": { 224 | "filepath": "mod", 225 | "name": "x", 226 | "isDefault": True, 227 | }, 228 | "settings": {"no_semicolon": True}, 229 | }, 230 | ) 231 | self.assertEqual("import x from './mod'", self.getRow(0)) 232 | 233 | def test_paste_import_no_spaces_in_braces(self): 234 | setText(self.view, "") 235 | self.view.run_command( 236 | "paste_import", 237 | { 238 | "item": { 239 | "filepath": "file", 240 | "name": "a", 241 | "isDefault": False, 242 | }, 243 | "settings": {"insert_space_in_braces": False, "no_semicolon": True}, 244 | }, 245 | ) 246 | self.assertEqual("import {a} from './file'", self.getRow(0)) 247 | 248 | 249 | class TestInitializeSetup(TestCase): 250 | def setUp(self): 251 | self.window = sublime.active_window() 252 | self.window.run_command("initialize_setup") 253 | 254 | def test_check_node_modules(self): 255 | yield 5000 256 | self.assertNotEqual(len(import_helper.NODE_MODULES), 0) 257 | 258 | def test_check_source_modules(self): 259 | yield 1000 260 | self.assertNotEqual(len(import_helper.SOURCE_MODULES), 0) 261 | 262 | 263 | class TestPasteImport(TestCase): 264 | def setUp(self): 265 | self.view = sublime.active_window().new_file() 266 | s = sublime.load_settings("Preferences.sublime-settings") 267 | s.set("close_windows_when_empty", False) 268 | 269 | def tearDown(self): 270 | if self.view: 271 | self.view.set_scratch(True) 272 | self.view.window().focus_view(self.view) 273 | self.view.window().run_command("close_file") 274 | 275 | def getRow(self, row): 276 | return self.view.substr(self.view.line(self.view.text_point(row, 0))) 277 | 278 | def getAll(self): 279 | return self.view.substr(sublime.Region(0, self.view.size())) 280 | 281 | def test_paste_import_extension_remove(self): 282 | setText(self.view, "") 283 | self.view.run_command( 284 | "paste_import", 285 | { 286 | "item": {"filepath": "file.tsx", "name": "a"}, 287 | "settings": {}, 288 | }, 289 | ) 290 | self.assertEqual("import { a } from './file';", self.getRow(0)) 291 | 292 | def test_paste_import_extension_js(self): 293 | setText(self.view, "") 294 | self.view.run_command( 295 | "paste_import", 296 | { 297 | "item": {"filepath": "file.tsx", "name": "a"}, 298 | "settings": {"import_file_extension": "js"}, 299 | }, 300 | ) 301 | self.assertEqual("import { a } from './file.js';", self.getRow(0)) 302 | 303 | def test_paste_import_extension_js_should_not_remove_index(self): 304 | setText(self.view, "") 305 | self.view.run_command( 306 | "paste_import", 307 | { 308 | "item": {"filepath": "./x/index.tsx", "name": "x"}, 309 | "settings": { 310 | "import_file_extension": "js", 311 | "remove_trailing_index": True, 312 | }, 313 | }, 314 | ) 315 | self.assertEqual("import { x } from './x/index.js';", self.getRow(0)) 316 | 317 | def test_paste_import_extension_as_is(self): 318 | setText(self.view, "") 319 | self.view.run_command( 320 | "paste_import", 321 | { 322 | "item": {"filepath": "./a.jsx", "name": "b"}, 323 | "settings": { 324 | "import_file_extension": "as_is", 325 | "remove_trailing_index": True, 326 | }, 327 | }, 328 | ) 329 | self.assertEqual("import { b } from './a.jsx';", self.getRow(0)) -------------------------------------------------------------------------------- /tests/test_library.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | import sys 3 | from unittest import TestCase 4 | 5 | import_helper = sys.modules["ImportHelper.import_helper"] 6 | get_import_root = sys.modules["ImportHelper.library.get_import_root"].get_import_root 7 | common_path = sys.modules["ImportHelper.library.common_path"].common_path 8 | unixify = sys.modules["ImportHelper.library.unixify"].unixify 9 | panel_items = sys.modules["ImportHelper.library.panel_items"].panel_items 10 | query_completions_modules = sys.modules[ 11 | "ImportHelper.library.query_completions_modules" 12 | ].query_completions_modules 13 | find_executable = sys.modules["ImportHelper.library.find_executable"].find_executable 14 | get_setting = sys.modules["ImportHelper.library.get_setting"].get_setting 15 | get_exclude_patterns = sys.modules[ 16 | "ImportHelper.library.get_exclude_patterns" 17 | ].get_exclude_patterns 18 | 19 | 20 | class TestLibraryFunctions(TestCase): 21 | def test_common_path_raises(self): 22 | self.assertRaises(ValueError, common_path, []) 23 | 24 | def test_common_path_1(self): 25 | result = common_path(["/usr/project1", "/usr/project2"]) 26 | self.assertEqual(result, "/usr") 27 | 28 | def test_common_path_2(self): 29 | result = common_path( 30 | ["d:\\dev\\project\\server\\do", "d:\\dev\\project\\client"] 31 | ) 32 | self.assertEqual(result, "d:/dev/project") 33 | 34 | def test_common_path_single_arg(self): 35 | result = common_path(["/usr/project"]) 36 | self.assertEqual(result, "/usr/project") 37 | 38 | def test_get_import_root(self): 39 | get_import_root() 40 | 41 | def test_panel_items(self): 42 | import_root = "/usr" 43 | entry_modules = [ 44 | {"name": "entry1", "filepath": "/usr/src/1"}, 45 | {"name": "entry2", "filepath": "/usr/src/2"}, 46 | {"name": "entry3", "filepath": "/usr/src/3"}, 47 | ] 48 | (items, matches) = panel_items( 49 | entry_modules=entry_modules, import_root=import_root 50 | ) 51 | self.assertEqual(len(items), 3) 52 | self.assertEqual(len(matches), 3) 53 | 54 | def test_panel_items_filter_by_name(self): 55 | import_root = "/usr" 56 | entry_modules = [ 57 | {"name": "entry1", "filepath": "/usr/src/1"}, 58 | {"name": "entry2", "filepath": "/usr/src/2"}, 59 | ] 60 | (items, matches) = panel_items( 61 | name="entry1", entry_modules=entry_modules, import_root=import_root 62 | ) 63 | self.assertEqual(len(items), 1) 64 | self.assertEqual(items[0], "src/1/entry1") 65 | self.assertEqual(len(matches), 1) 66 | 67 | def test_unixify(self): 68 | testFile = "\\local\\some\\file" 69 | self.assertEqual(unixify(testFile), "/local/some/file") 70 | 71 | def test_unixify_ts(self): 72 | self.assertEqual(unixify("some\\file.ts"), "some/file") 73 | 74 | def test_unixify_tsx(self): 75 | self.assertEqual(unixify("d/file.tsx"), "d/file") 76 | 77 | def test_unixify_js(self): 78 | self.assertEqual(unixify("some\\file.js"), "some/file") 79 | 80 | def test_get_setting(self): 81 | self.assertEqual(get_setting("from_quote", None), "'") 82 | self.assertEqual(get_setting("unknown", "default_value"), "default_value") 83 | 84 | def test_find_executable(self): 85 | result = find_executable("node") 86 | self.assertNotEqual("node", result) 87 | 88 | def test_query_completions_modules(self): 89 | source_modules = [ 90 | {"name": "good", "filepath": "/usr/home/good"}, 91 | {"name": "ugly", "filepath": "/usr/home/ugly"}, 92 | ] 93 | node_modules = [{"name": "Chicky", "module": "chicken"}] 94 | result = query_completions_modules("goo", source_modules, node_modules) 95 | self.assertListEqual(result, [["good\tsource_modules", "good"]]) 96 | result = query_completions_modules("Chic", source_modules, node_modules) 97 | self.assertListEqual(result, [["Chicky\tnode_modules/chicken", "Chicky"]]) 98 | 99 | def test_get_exclude_patterns_fault_tollerance(self): 100 | result = get_exclude_patterns({"folders": {}}) 101 | self.assertDictEqual(result, {}) 102 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "lib": ["es2017", "dom"], 5 | "jsx": "react", 6 | "jsxFactory": "React.createElement", 7 | "baseUrl": ".", 8 | "paths": { 9 | "@Libs/*": ["./test_playground/lib/*"], 10 | "@Dummy": ["./test_playground/component/dummy.component.ts"], 11 | "@Components": ["./test_playground/component"] 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /unittesting.json: -------------------------------------------------------------------------------- 1 | { 2 | "tests_dir": "tests", 3 | "pattern": "*.py", 4 | "async": false, 5 | "deferred": true, 6 | "verbosity": 2, 7 | "capture_console": true, 8 | "output": null 9 | } 10 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const path = require('path'); 3 | 4 | const config = { 5 | entry: 'import-adjutor/cli.js', 6 | output: { 7 | path: process.cwd(), 8 | filename: 'backend_run.js', 9 | }, 10 | target: 'node', 11 | mode: 'development', 12 | devtool: false, 13 | module: { 14 | rules: [], 15 | }, 16 | resolve: { 17 | extensions: ['.js'], 18 | }, 19 | }; 20 | 21 | module.exports = config; 22 | --------------------------------------------------------------------------------