├── .editorconfig ├── .github ├── ISSUE_TEMPLATE │ ├── ask-for-help.md │ ├── bug_report.md │ └── feature_request.md └── templates.md ├── .gitignore ├── .jsbeautifyrc ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── .vscodeignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── _config.yml ├── icons ├── activity_bar_icon.svg └── favorites.png ├── images ├── add_dark.svg ├── add_light.svg ├── collapse_dark.svg ├── collapse_light.svg ├── collapsevs_dark.svg ├── delete_all_dark.svg ├── delete_all_light.svg ├── group_dark.svg ├── group_light.svg ├── icon-small.png ├── refresh_dark.svg ├── refresh_light.svg ├── sort_dark_az.svg ├── sort_dark_za.svg ├── sort_light_az.svg └── sort_light_za.svg ├── media └── icon.svg ├── package-lock.json ├── package.json ├── preview ├── adding-favorite.jpg ├── adding.jpg ├── browsing.gif ├── operations.jpg ├── preview-promo.gif ├── selectRegistry.jpg ├── statusRegistry.jpg └── using.jpg ├── src ├── class │ ├── clipboard.ts │ ├── dataProvider.ts │ ├── exclude-check.ts │ ├── favorites-browser.ts │ ├── favorites-navigator.ts │ ├── favorites.ts │ ├── filesystem.ts │ ├── fs-watcher.ts │ ├── global.ts │ ├── group-color.ts │ ├── storage.ts │ ├── tree.ts │ ├── view-item.ts │ └── workspace.ts ├── command │ └── index.ts ├── index.ts └── types │ ├── index.ts │ └── trash.d.ts ├── tsconfig.json ├── tslint.json └── webpack.config.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 4 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/ask-for-help.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Ask for help 3 | about: Get help using extension 4 | 5 | --- 6 | 7 | **Describe your use case.** 8 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | **Describe the bug** 8 | Description of what the bug is. 9 | 10 | **To Reproduce** 11 | Steps to reproduce the behavior: 12 | 1. Go to '...' 13 | 2. Click on '....' 14 | 3. Scroll down to '....' 15 | 4. See error 16 | 17 | **Expected behavior** 18 | What you expected to happen. 19 | 20 | **Screenshots** 21 | If it will help describe behaviour, add screenshots to help explain your problem. 22 | 23 | **System info (please complete the following information):** 24 | - OS version: [e.g. Windows 10, Debian 9, MacOS 10.14.1] 25 | - Favorites extension version 26 | - vscode version 27 | 28 | **Additional info** 29 | Add any other info about the problem here. 30 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | **Is your feature request related to a problem? Please describe.** 8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 9 | 10 | **Describe the solution you'd like** 11 | A clear and concise description of what you want to happen. 12 | -------------------------------------------------------------------------------- /.github/templates.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kdcro101/vscode-favorites/bbf4ca0ead1ef60ea28c4e08868437271b6bbc83/.github/templates.md -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | packed 3 | node_modules 4 | .DS_Store 5 | promo/** 6 | svg-generated/** 7 | .favorites.json 8 | -------------------------------------------------------------------------------- /.jsbeautifyrc: -------------------------------------------------------------------------------- 1 | { 2 | "indent_inner_html": true, 3 | "indent_size": 4, 4 | "css": { 5 | "indent_size": 4, 6 | "newline_between_rules": true 7 | }, 8 | "html": { 9 | "wrap_attributes": "force-aligned" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.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": "Extension", 7 | "type": "extensionHost", 8 | "request": "launch", 9 | "runtimeExecutable": "${execPath}", 10 | "args": ["--extensionDevelopmentPath=${workspaceRoot}"], 11 | "stopOnEntry": false, 12 | "sourceMaps": true, 13 | "outFiles": ["${workspaceRoot}/packed/**/*.js"], 14 | // "preLaunchTask": "npm: webpack" 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.exclude": { 4 | "**/.git": true, 5 | "**/.svn": true, 6 | "**/.hg": true, 7 | "**/CVS": true, 8 | "**/.DS_Store": true, 9 | // "out": true, 10 | // "node_modules": true, 11 | ".vscode": true 12 | } 13 | // "typescript.referencesCodeLens.enabled": true 14 | 15 | } 16 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // See https://go.microsoft.com/fwlink/?LinkId=733558 2 | // for the documentation about the tasks.json format 3 | { 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "watch", 9 | "problemMatcher": "$tsc-watch", 10 | "isBackground": true, 11 | "presentation": { 12 | "reveal": "never" 13 | }, 14 | "group": { 15 | "kind": "build", 16 | "isDefault": true 17 | } 18 | }, 19 | { 20 | "type": "npm", 21 | "script": "webpack", 22 | "problemMatcher": [] 23 | }, 24 | { 25 | "type": "npm", 26 | "script": "webpack-dev", 27 | "problemMatcher": [ 28 | "$tsc-watch" 29 | ], 30 | "isBackground": true 31 | } 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .github/** 3 | node_modules 4 | !node_modules/arch 5 | !node_modules/clipboardy 6 | !node_modules/cross-spawn 7 | !node_modules/execa 8 | !node_modules/get-stream 9 | !node_modules/is-stream 10 | !node_modules/isexe 11 | !node_modules/lru-cache 12 | !node_modules/nice-try 13 | !node_modules/npm-run-path 14 | !node_modules/p-finally 15 | !node_modules/path-key 16 | !node_modules/pseudomap 17 | !node_modules/shebang-command 18 | !node_modules/shebang-regex 19 | !node_modules/signal-exit 20 | !node_modules/strip-eof 21 | !node_modules/semver 22 | !node_modules/which 23 | !node_modules/yallist 24 | out/** 25 | test/** 26 | src/** 27 | promo/** 28 | preview/** 29 | svg-generated/** 30 | **/*.map 31 | .gitignore 32 | tsconfig.json 33 | .jsbeautifyrc 34 | .editorconfig 35 | tslint.json 36 | media/** 37 | webpack.config.js 38 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 2.4.6 | 2022/09/15 2 | 3 | - enabled the "Add to Favorites" and "Add to Favorites Group" context menu items in additional context menus such as when right clicking an open file tab, or in the text of an open editor. 4 | 5 | 6 | ## 2.4.5 | 2018/11/29 7 | 8 | - fixed vulnerability issue 9 | 10 | ## 2.4.4 | 2018/11/25 11 | 12 | - repo rename 13 | 14 | ## 2.4.3 | 2018/11/18 15 | 16 | - added browsing using keyboard only (mouseless) of favorites via palette `Favorites: Browse`. [#31](https://github.com/kdcro101/vscode-favorites/issues/31) 17 | 18 | ## 2.3.4 | 2018/11/16 19 | 20 | - fixed `Copy path` not working with webpack 21 | 22 | ## 2.3.2 | 2018/11/16 23 | 24 | - extension packed using `webpack.js` for faster loading 25 | - fixed `duplicate` file when inside group 26 | 27 | ## 2.3.1 | 2018/11/04 28 | 29 | - README 30 | 31 | ## 2.3.0 | 2018/11/03 32 | 33 | - added support for multiple/switchable favorite storage per workspace [#28](https://github.com/kdcro101/vscode-favorites/issues/28) 34 | - fixed adding file from editor using command `Favorites: Add to group of favorites` from palette [#26](https://github.com/kdcro101/vscode-favorites/issues/26) 35 | 36 | ## 2.2.5 | 2018/10/29 37 | 38 | - merging update observables 39 | 40 | ## 2.2.4 | 2018/10/27 41 | 42 | - unsubscribe observables on extension deactivation 43 | 44 | ## 2.2.2 | 2018/10/26 45 | 46 | - fixed creating `.vscode/setting.json` when folder gets opened [#25](https://github.com/kdcro101/vscode-favorites/issues/25) 47 | 48 | ## 2.2.1 | 2018/10/26 49 | 50 | - Favorites refresh on FileSystemWatcher events [#24](https://github.com/kdcro101/vscode-favorites/issues/24) 51 | - added `refresh` button 52 | 53 | ## 2.2.0 | 2018/10/24 54 | 55 | - fixed "Remove everything from favorites" 56 | - added configuration `favorites.useWorkspace` 57 | - added workspace folder selection for storage of favorites 58 | 59 | ## 2.1.5 | 2018/10/22 60 | 61 | - fixed `Path must be string. Received null` error [#20](https://github.com/kdcro101/vscode-favorites/issues/20) 62 | 63 | ## 2.1.2 | 2018/10/21 64 | 65 | - added `Copy Name` to copy file name 66 | - added support for `files.exclude` 67 | 68 | ## 2.0.5 | 2018/10/18 69 | 70 | - Icon + Marketplace banner 71 | - Favorites items are stored, by default, in workspace root as `favorites.json` In case of multiroot workspace, file is located in first workspace. [#13](https://github.com/kdcro101/vscode-favorites/issues/13) [#18](https://github.com/kdcro101/vscode-favorites/issues/18) [#19](https://github.com/kdcro101/vscode-favorites/issues/19) 72 | - added configuration item `favorites.storageFilePath` to overrride storage file path relative to workspace. Default is `.favorites.json` 73 | 74 | ## 2.0.1 | 2018/10/17 75 | 76 | - Favorites items are stored in `.vscode/favorites.json`. In case of multiroot workspace, file is located in first workspace. [#13](https://github.com/kdcro101/vscode-favorites/issues/13) [#18](https://github.com/kdcro101/vscode-favorites/issues/18) 77 | 78 | ## 2.0.0 | 2018/10/17 79 | 80 | - Favorites items are stored in workspace root as file `.favorites.json`. In case of multiroot workspace, file is located in first workspace. [#13](https://github.com/kdcro101/vscode-favorites/issues/13) 81 | 82 | 83 | ## 1.9.10 | 2018/10/15 84 | 85 | - Marketplace icon + activity bar icon 86 | 87 | ## 1.9.9 | 2018/10/11 88 | 89 | - Fix favorites explorer focusing on acitve editor change. [#16](https://github.com/kdcro101/vscode-favorites/issues/16) 90 | 91 | ## 1.9.8 | 2018/10/09 92 | 93 | - README 94 | - `Add to favorites` executed from command palette adds active document to favorites root 95 | 96 | ## 1.9.7 | 2018/09/25 97 | 98 | - favorites.useTrash -> README 99 | - remove obsolete code 100 | 101 | ## 1.9.6 | 2018/09/19 102 | 103 | - Colored icons for groups are generated when needed if not exists 104 | 105 | ## 1.9.5 | 2018/09/18 106 | 107 | - activity bar icon name change (caching problem - full restart was required) 108 | - README 109 | 110 | ## 1.9.3 | 2018/09/18 111 | 112 | - remove unicode characters from context menu when adding to favorites [#11](https://github.com/kdcro101/vscode-favorites/issues/11) 113 | - change activity bar icon 114 | - README 115 | 116 | ## 1.9.2 | 2018/09/15 117 | 118 | - change prompt to include `PERMANENTLY` when deleting from file system 119 | 120 | ## 1.9.1 | 2018/09/12 121 | 122 | - Fix reporting of "Unable to identify path" when adding file/directory that is already in favorites 123 | 124 | ## 1.9.0 | 2018/09/08 125 | 126 | - Copy Path 127 | 128 | ## 1.8.0 | 2018/08/30 129 | 130 | - Trash support 131 | 132 | ## 1.7.3 | 2018/08/21 133 | 134 | - view/item context menu fix 135 | 136 | ## 1.7.2 | 2018/08/18 137 | 138 | - code cleanup 139 | 140 | ## 1.7.1 | 2018/08/16 141 | 142 | - file uri fix for external resources 143 | 144 | ## 1.7.0 | 2018/08/15 145 | 146 | - minor bugs 147 | - create group in root from context menu (right click on empty area) 148 | - external resources (files/directories out of workspace) can be added 149 | - minor context menu reordering 150 | 151 | ## 1.6.0 | 2018/08/08 152 | 153 | - reveal file in "favorites" view (if visible) when active editor changes 154 | 155 | ## 1.5.5 | 2018/08/06 156 | 157 | - fs.move -> fs.rename 158 | 159 | ## 1.5.4 | 2018/08/06 160 | 161 | - minor bugs 162 | 163 | ## 1.5.3 | 2018/08/06 164 | 165 | - fs-extra 7.0.0 upgrade 166 | 167 | ## 1.5.2 | 2018/07/13 168 | 169 | - "window.titleBarStyle": "custom" - temporary fix 170 | 171 | ## 1.5.1 | 2018/07/10 172 | 173 | - minor bugs 174 | 175 | ## 1.5.0 | 2018/07/10 176 | 177 | - multiple selection can be added to favorites 178 | 179 | ## 1.4.4 | 2018/05/25 180 | 181 | - fixed file creation bug when user cancel file name input 182 | 183 | ## 1.4.3 | 2018/05/09 184 | 185 | - activationEvents 186 | - fixed command labels 187 | 188 | ## 1.4.2 | 2018/05/09 189 | 190 | - icon 191 | - readme update 192 | 193 | ## 1.4.1 | 2018/05/09 194 | 195 | - fixed bug that may corrupt current favorites data 196 | - remove all icon changed 197 | - missing command labels fixed 198 | 199 | ## 1.4.0 | 2018/05/09 200 | 201 | - groups colors can be changed 202 | - filesystem operations in favorites view 203 | - copy/cut/paste operations in favorites view 204 | 205 | ## 1.3.1 | 2018/05/08 206 | 207 | - minor bugs 208 | - README update 209 | 210 | ## 1.3.0 | 2018/05/08 211 | 212 | - renaming group 213 | - alias for favorite items (custom label) 214 | - activity bar contribution 215 | - minimum vscode version = 1.23.0 216 | 217 | ## 1.2.1 | 2018/05/07 218 | 219 | - minor bugs 220 | 221 | ## 1.2.0 | 2018/05/02 222 | 223 | - added subgroup support 224 | 225 | 226 | ## 1.0.3 | 2018/03/30 227 | 228 | - icon change 229 | 230 | ## 1.0.2 | 2018/03/29 231 | 232 | - minor bugs 233 | - README 234 | 235 | ## 1.0.0 | 2018/03/24 236 | 237 | - added option to delete everything 238 | 239 | ## 0.1.8 | 2018/03/22 240 | 241 | - fixed `delete item` menu showing on file system item 242 | 243 | ## 0.1.4 | 2018/03/21 244 | 245 | ## 0.1.3 | 2018/03/21 246 | 247 | - reduce extension size 248 | 249 | ## 0.1.2 | 2018/03/20 250 | 251 | - update minimum engine to `^1.13.0` 252 | 253 | ## 0.1.1 | 2018/03/19 254 | 255 | - fixed badges link 256 | 257 | ## 0.1.0 | 2018/03/19 258 | 259 | - added ability to group favorites 260 | 261 | 262 | ## 0.0.5 | 2018/03/19 263 | 264 | - initial commit 265 | - collapse function 266 | - fixed sorting 267 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 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 | # Favorites 2 | 3 | [![Visual Studio Marketplace](https://vsmarketplacebadge.apphb.com/version/kdcro101.favorites.svg)](https://marketplace.visualstudio.com/items?itemName=kdcro101.favorites) 4 | [![Installs](https://vsmarketplacebadge.apphb.com/installs-short/kdcro101.favorites.svg)](https://marketplace.visualstudio.com/items?itemName=kdcro101.favorites) 5 | [![Rating](https://vsmarketplacebadge.apphb.com/rating-short/kdcro101.favorites.svg)](https://marketplace.visualstudio.com/items?itemName=kdcro101.favorites#review-details) 6 | 7 | 8 | Add files and directories to **workspace** favorites. You can create groups (and subgroups) of favorite items with files and folders. 9 | Time saver for complex projects. 10 | 11 | 12 | ## Features 13 | 14 | - [browsing using keyboard](#keyboard-browsing) only via `Favorites: Browse` palette command 15 | - add resources within workspace 16 | - add external resources (files or directories out of workspace) 17 | - organize favorites in groups and subgroups (nesting not limited) 18 | - have [multiple sets](#multiple-sets) of favorites, per workspace, depending of context, using setting `favorites.storageRegistry` 19 | - group icons can have their color changed 20 | - basic file system operations within Favorites explorer: 21 | - copy/cut -> paste 22 | - create file 23 | - delete file/directory 24 | - rename file/directory 25 | - duplicate file/directory 26 | - favorite items can have alias (different label) 27 | - items are accesible via activity bar and, optionally, as File explorer subview (see [Using Favorites explorer section](#using)) 28 | - `files.exclude` supported (see [configuration](#configuration)) 29 | - language independent 30 | 31 | 32 | ## Adding to favorites 33 |

34 | 35 |

36 | 37 | - to add file or directory to favorites, right-click item in ***File explorer*** and select: 38 | - `add to favorites` - to add item to root of favorites tree. 39 | - `add to group of favorites` - to add item to group of favorites you previously created. 40 | 41 | 42 | 43 | ## Using Favorites explorer 44 | 45 |

46 | 47 |

48 | 49 | you can **turn off** subview in **File explorer** by clicking on its header and deselecting it. 50 | 51 | 52 | ## Install 53 | 54 | Open Visual Studio Code press CTRL+p and type or copy and paste: 55 | 56 | `ext install kdcro101.favorites` 57 | 58 | 59 | ## Configuration 60 | 61 | `favorites.useWorkspace` : number - default is `0` 62 | - index of workspace to use as root when composing storage file path 63 | 64 | `favorites.useFilesExclude` : boolean 65 | - should `files.exclude` setting be used. Default is `true` 66 | 67 | `favorites.storageFilePath` : string 68 | - overrride storage file path relative to workspace. Default is `.favorites.json` 69 | 70 | `favorites.storageRegistry` : string[] 71 | - List of storage file paths relative to workspace to make available for switching using command `Favorites: Select alternative storage from registry`. ([multiple sets](#multiple-sets)). Default is `[]` 72 | 73 | `favorites.groupsFirst` : boolean 74 | - if set to `true`, groups will be listed before directories and files, if `false`, groups will appear after directories and files. 75 | 76 | `favorites.sortDirection ` : string, `ASC` or `DESC` 77 | 78 | `favorites.useTrash`: boolean (default `false`) 79 | - if set to `true`, extension will try to use system trash when resource (file or directory is deleted) 80 | 81 | `favorites.includeInDocumentBodyContextMenu` : boolean (default `false`) 82 | - if set to `true`, the two "Add to * favorites" commands will be included in the editor context menu that appears when right-clicking the body of an open document. 83 | 84 | `favorites.includeInEditorTabContextMenu` : boolean (default `true`) 85 | - if set to `true`, the two "Add to * favorites" commands will be included in the context menu for the tab of a specific file (e.g. the menu that appears when right-clicking the tab of an open document). 86 | 87 | ## Keyboard browsing 88 | 89 | You can browse favorites using **keyboard only** by executing command `Favorites: Browse` command from palette. Assign keyboard shortcut if needed. 90 | 91 |

92 | 93 |

94 | 95 | ## Usage 96 | 97 | #### Adding to favorites 98 | Right-click item in File explorer, an open file tab, or the background of an open editor and select `Add to favorites`. 99 | #### Adding to favorites group or subgroup 100 | Right-click item in File explorer, an open file tab, or the background of an open editor and select `Add to favorites group`, then select group from list. 101 | #### Removing from favorites 102 | Right-click item in Favorites view and select `Remove from favorites` 103 | #### Create favorites group 104 | Right-click on empty area and select `Create group` 105 | #### Create favorites subgroup 106 | Right-click on group item and choose `Create group` 107 | #### Delete favorites group 108 | Right-click group item in Favorites view and select `Remove group` 109 | #### Remove everything from favorites 110 | Click on trash bin icon on Favorites view title, type "yes" to confirm 111 | 112 | ## Multiple sets 113 | 114 | You can have multiple sets of favorites per workspace. This allows you to build independent set of favorites, depending on context. 115 | To achieve this you need to setup storage registry. 116 | 117 | Add favorites.storageRegistry to your workspace settings, for example: 118 | ```ts 119 | // paths are relative to workspace 120 | "favorites.storageRegistry": [ 121 | "favorites/system.json", 122 | "favorites/classes.json", 123 | "favorites/services.json", 124 | ], 125 | ``` 126 | 127 | Select active storage file from registry by clicking status bar element ![select](https://raw.githubusercontent.com/kdcro101/vscode-favorites/master/preview/statusRegistry.jpg) or by executing command `Favorites: Select alternative storage from registry` from command palette and then selecting item from list: 128 | 129 | ![list](https://raw.githubusercontent.com/kdcro101/vscode-favorites/master/preview/selectRegistry.jpg) 130 | 131 | All `add favorite` operations will be written in currently selected storage file. 132 | 133 | 134 | ## LICENSE 135 | 136 | [GPL v3 License](https://raw.githubusercontent.com/kdcro101/vscode-favorites/master/LICENSE) 137 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /icons/activity_bar_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /icons/favorites.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kdcro101/vscode-favorites/bbf4ca0ead1ef60ea28c4e08868437271b6bbc83/icons/favorites.png -------------------------------------------------------------------------------- /images/add_dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | image/svg+xml 27 | -------------------------------------------------------------------------------- /images/add_light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | image/svg+xml -------------------------------------------------------------------------------- /images/collapse_dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /images/collapse_light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /images/collapsevs_dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /images/delete_all_dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /images/delete_all_light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /images/group_dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /images/group_light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /images/icon-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kdcro101/vscode-favorites/bbf4ca0ead1ef60ea28c4e08868437271b6bbc83/images/icon-small.png -------------------------------------------------------------------------------- /images/refresh_dark.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /images/refresh_light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /images/sort_dark_az.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Layer 1 5 | 6 | 7 | -------------------------------------------------------------------------------- /images/sort_dark_za.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /images/sort_light_az.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Layer 1 5 | 6 | 7 | -------------------------------------------------------------------------------- /images/sort_light_za.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /media/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 42 | 49 | 56 | 63 | 70 | 77 | 78 | 80 | 81 | 83 | image/svg+xml 84 | 86 | 87 | 88 | 89 | 90 | 95 | 99 | 106 | 111 | 116 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "favorites", 3 | "displayName": "Favorites", 4 | "description": "Add files and directories to workspace favorites. Create groups of directories and files. Time saver for complex projects.", 5 | "version": "2.4.6", 6 | "categories": [ 7 | "Other" 8 | ], 9 | "keywords": [ 10 | "bookmarks", 11 | "favorites", 12 | "groups", 13 | "favorite files", 14 | "favorite directories" 15 | ], 16 | "publisher": "kdcro101", 17 | "engines": { 18 | "vscode": "^1.29.0" 19 | }, 20 | "galleryBanner": { 21 | "color": "#283593", 22 | "theme": "dark" 23 | }, 24 | "scripts": { 25 | "vscode:prepublish": "webpack --mode production", 26 | "webpack": "webpack --mode development", 27 | "webpack-dev": "webpack --mode development --watch", 28 | "postinstall": "node ./node_modules/vscode/bin/install" 29 | }, 30 | "icon": "icons/favorites.png", 31 | "bugs": { 32 | "url": "https://github.com/kdcro101/vscode-favorites/issues" 33 | }, 34 | "homepage": "https://github.com/kdcro101/vscode-favorites/blob/master/README.md", 35 | "repository": { 36 | "type": "git", 37 | "url": "https://github.com/kdcro101/vscode-favorites.git" 38 | }, 39 | "activationEvents": [ 40 | "*" 41 | ], 42 | "license": "GPL-3.0", 43 | "main": "./packed/", 44 | "contributes": { 45 | "keybindings": [ 46 | { 47 | "command": "workbench.view.extension.favorites-explorer", 48 | "key": "ctrl+alt+f", 49 | "mac": "cmd+alt+f", 50 | "when": "editorTextFocus" 51 | } 52 | ], 53 | "configuration": { 54 | "type": "object", 55 | "title": "Favorite-items configuration", 56 | "properties": { 57 | "favorites.useTrash": { 58 | "type": "boolean", 59 | "default": false, 60 | "description": "Move to trash when resource gets deleted" 61 | }, 62 | "favorites.useWorkspace": { 63 | "type": "number", 64 | "default": 0, 65 | "description": "Index of workspace where to look for storage file" 66 | }, 67 | "favorites.useFilesExclude": { 68 | "type": "boolean", 69 | "default": true, 70 | "description": "Should 'files.exclude' setting be used " 71 | }, 72 | "favorites.storageFilePath": { 73 | "type": "string", 74 | "default": ".favorites.json", 75 | "description": "Storage file path relative to workspace" 76 | }, 77 | "favorites.storageRegistry": { 78 | "type": "array", 79 | "default": [], 80 | "description": "List of alternative storage paths relative to workspace", 81 | "items": { 82 | "type": "string", 83 | "title": "Path", 84 | "description": "Path to alternative storage .json file" 85 | } 86 | }, 87 | "favorites.groupsFirst": { 88 | "type": "boolean", 89 | "default": true, 90 | "description": "Display favorite groups before (above) favorite files and directories" 91 | }, 92 | "favorites.sortDirection": { 93 | "type": "string", 94 | "enum": [ 95 | "ASC", 96 | "DESC" 97 | ], 98 | "default": "ASC", 99 | "description": "Specify an order for all favorites" 100 | }, 101 | "favorites.includeInDocumentBodyContextMenu": { 102 | "type": "boolean", 103 | "default": false, 104 | "description": "Display the 'Favorites: Add to * favorites' commands in the editor context menu" 105 | }, 106 | "favorites.includeInEditorTabContextMenu": { 107 | "type": "boolean", 108 | "default": true, 109 | "description": "Display the 'Favorites: Add to * favorites' commands in the context menu for the tab of a specific file" 110 | } 111 | } 112 | }, 113 | "viewsContainers": { 114 | "activitybar": [ 115 | { 116 | "id": "favorites-explorer", 117 | "title": "Favorites", 118 | "icon": "icons/activity_bar_icon.svg" 119 | } 120 | ] 121 | }, 122 | "views": { 123 | "favorites-explorer": [ 124 | { 125 | "id": "favoritesActivity", 126 | "name": "Explorer" 127 | } 128 | ], 129 | "explorer": [ 130 | { 131 | "id": "favorites", 132 | "name": "Favorites" 133 | } 134 | ] 135 | }, 136 | "menus": { 137 | "explorer/context": [ 138 | { 139 | "command": "favorites.addToFavorites", 140 | "group": "favorites" 141 | }, 142 | { 143 | "command": "favorites.addToFavoritesGroup", 144 | "group": "favorites" 145 | } 146 | ], 147 | "editor/title": [ 148 | { 149 | "command": "favorites.addToFavorites", 150 | "group": "favorites" 151 | }, 152 | { 153 | "command": "favorites.addToFavoritesGroup", 154 | "group": "favorites" 155 | } 156 | ], 157 | "editor/title/context": [ 158 | { 159 | "when": "config.favorites.includeInEditorTabContextMenu", 160 | "command": "favorites.addToFavorites", 161 | "group": "favorites" 162 | }, 163 | { 164 | "when": "config.favorites.includeInEditorTabContextMenu", 165 | "command": "favorites.addToFavoritesGroup", 166 | "group": "favorites" 167 | } 168 | ], 169 | "editor/context": [ 170 | { 171 | "when": "config.favorites.includeInDocumentBodyContextMenu && resourceScheme == file", 172 | "command": "favorites.addFileToFavorites", 173 | "group": "favorites" 174 | }, 175 | { 176 | "when": "config.favorites.includeInDocumentBodyContextMenu && resourceScheme == file", 177 | "command": "favorites.addFileToFavoritesGroup", 178 | "group": "favorites" 179 | } 180 | ], 181 | "view/title": [ 182 | { 183 | "command": "favorites.collapse", 184 | "when": "view == favorites", 185 | "group": "navigation@4" 186 | }, 187 | { 188 | "command": "favorites.refresh", 189 | "when": "view == favorites", 190 | "group": "navigation@3" 191 | }, 192 | { 193 | "command": "favorites.nav.sort.az", 194 | "when": "view == favorites && sort==DESC", 195 | "group": "navigation@2" 196 | }, 197 | { 198 | "command": "favorites.nav.sort.za", 199 | "when": "view == favorites && sort==ASC", 200 | "group": "navigation@2" 201 | }, 202 | { 203 | "command": "favorites.group.create", 204 | "when": "view == favorites", 205 | "group": "navigation@1" 206 | }, 207 | { 208 | "command": "favorites.delete.all", 209 | "when": "view == favorites", 210 | "group": "navigation@0" 211 | }, 212 | { 213 | "command": "favorites.collapse.activity", 214 | "when": "view == favoritesActivity", 215 | "group": "navigation@4" 216 | }, 217 | { 218 | "command": "favorites.refresh", 219 | "when": "view == favoritesActivity", 220 | "group": "navigation@3" 221 | }, 222 | { 223 | "command": "favorites.nav.sort.az", 224 | "when": "view == favoritesActivity && sort==DESC", 225 | "group": "navigation@2" 226 | }, 227 | { 228 | "command": "favorites.nav.sort.za", 229 | "when": "view == favoritesActivity && sort==ASC", 230 | "group": "navigation@2" 231 | }, 232 | { 233 | "command": "favorites.group.create", 234 | "when": "view == favoritesActivity", 235 | "group": "navigation@1" 236 | }, 237 | { 238 | "command": "favorites.delete.all", 239 | "when": "view == favoritesActivity", 240 | "group": "navigation@0" 241 | } 242 | ], 243 | "view/item/context": [ 244 | { 245 | "command": "favorites.deleteFavorite", 246 | "when": "viewItem == FAVORITE_FILE", 247 | "group": "operation" 248 | }, 249 | { 250 | "command": "favorites.deleteFavorite", 251 | "when": "viewItem == FAVORITE_DIRECTORY", 252 | "group": "operation" 253 | }, 254 | { 255 | "command": "favorites.alias.modify", 256 | "when": "viewItem == FAVORITE_FILE", 257 | "group": "operation" 258 | }, 259 | { 260 | "command": "favorites.alias.modify", 261 | "when": "viewItem == FAVORITE_DIRECTORY", 262 | "group": "operation" 263 | }, 264 | { 265 | "command": "favorites.alias.remove", 266 | "when": "viewItem == FAVORITE_FILE", 267 | "group": "operation" 268 | }, 269 | { 270 | "command": "favorites.alias.remove", 271 | "when": "viewItem == FAVORITE_DIRECTORY", 272 | "group": "operation" 273 | }, 274 | { 275 | "command": "favorites.group.delete", 276 | "when": "viewItem == FAVORITE_GROUP", 277 | "group": "1_modification" 278 | }, 279 | { 280 | "command": "favorites.group.rename", 281 | "when": "viewItem == FAVORITE_GROUP", 282 | "group": "1_modification" 283 | }, 284 | { 285 | "command": "favorites.group.color.set", 286 | "when": "viewItem == FAVORITE_GROUP", 287 | "group": "6_modification" 288 | }, 289 | { 290 | "command": "favorites.group.create", 291 | "when": "viewItem == FAVORITE_GROUP", 292 | "group": "1_modification" 293 | }, 294 | { 295 | "command": "favorites.group.create", 296 | "when": "view == favoritesActivity && viewItem != FAVORITE_GROUP && viewItem != FAVORITE_DIRECTORY && viewItem != FAVORITE_FILE && viewItem != FS_FILE && viewItem != FS_DIRECTORY", 297 | "group": "1_modification" 298 | }, 299 | { 300 | "command": "favorites.group.create", 301 | "when": "view == favorites && viewItem != FAVORITE_GROUP && viewItem != FAVORITE_DIRECTORY && viewItem != FAVORITE_FILE && viewItem != FS_FILE && viewItem != FS_DIRECTORY", 302 | "group": "1_modification" 303 | }, 304 | { 305 | "command": "filesystem.create.file", 306 | "when": "viewItem == FS_DIRECTORY", 307 | "group": "1_modification" 308 | }, 309 | { 310 | "command": "filesystem.create.directory", 311 | "when": "viewItem == FS_DIRECTORY", 312 | "group": "1_modification" 313 | }, 314 | { 315 | "command": "filesystem.duplicate", 316 | "when": "viewItem == FS_DIRECTORY", 317 | "group": "1_modification" 318 | }, 319 | { 320 | "command": "filesystem.copy", 321 | "when": "viewItem == FS_DIRECTORY", 322 | "group": "9_cutcopypaste" 323 | }, 324 | { 325 | "command": "filesystem.cut", 326 | "when": "viewItem == FS_DIRECTORY", 327 | "group": "9_cutcopypaste" 328 | }, 329 | { 330 | "command": "filesystem.paste", 331 | "when": "viewItem == FS_DIRECTORY && clipboard == true", 332 | "group": "9_cutcopypaste" 333 | }, 334 | { 335 | "command": "filesystem.delete", 336 | "when": "viewItem == FS_DIRECTORY", 337 | "group": "7_modification" 338 | }, 339 | { 340 | "command": "filesystem.rename", 341 | "when": "viewItem == FS_DIRECTORY", 342 | "group": "7_modification" 343 | }, 344 | { 345 | "command": "filesystem.duplicate", 346 | "when": "viewItem == FS_FILE", 347 | "group": "1_modification" 348 | }, 349 | { 350 | "command": "filesystem.copy", 351 | "when": "viewItem == FS_FILE", 352 | "group": "9_cutcopypaste" 353 | }, 354 | { 355 | "command": "filesystem.cut", 356 | "when": "viewItem == FS_FILE", 357 | "group": "9_cutcopypaste" 358 | }, 359 | { 360 | "command": "filesystem.delete", 361 | "when": "viewItem == FS_FILE", 362 | "group": "7_modification" 363 | }, 364 | { 365 | "command": "filesystem.rename", 366 | "when": "viewItem == FS_FILE", 367 | "group": "7_modification" 368 | }, 369 | { 370 | "command": "filesystem.duplicate", 371 | "when": "viewItem == FAVORITE_FILE", 372 | "group": "1_modification" 373 | }, 374 | { 375 | "command": "filesystem.copy", 376 | "when": "viewItem == FAVORITE_FILE", 377 | "group": "9_cutcopypaste" 378 | }, 379 | { 380 | "command": "filesystem.cut", 381 | "when": "viewItem == FAVORITE_FILE", 382 | "group": "9_cutcopypaste" 383 | }, 384 | { 385 | "command": "filesystem.delete", 386 | "when": "viewItem == FAVORITE_FILE", 387 | "group": "7_modification" 388 | }, 389 | { 390 | "command": "filesystem.rename", 391 | "when": "viewItem == FAVORITE_FILE", 392 | "group": "7_modification" 393 | }, 394 | { 395 | "command": "filesystem.create.file", 396 | "when": "viewItem == FAVORITE_DIRECTORY", 397 | "group": "1_modification" 398 | }, 399 | { 400 | "command": "filesystem.create.directory", 401 | "when": "viewItem == FAVORITE_DIRECTORY", 402 | "group": "1_modification" 403 | }, 404 | { 405 | "command": "filesystem.duplicate", 406 | "when": "viewItem == FAVORITE_DIRECTORY", 407 | "group": "1_modification" 408 | }, 409 | { 410 | "command": "filesystem.copy", 411 | "when": "viewItem == FAVORITE_DIRECTORY", 412 | "group": "9_cutcopypaste" 413 | }, 414 | { 415 | "command": "filesystem.cut", 416 | "when": "viewItem == FAVORITE_DIRECTORY", 417 | "group": "9_cutcopypaste" 418 | }, 419 | { 420 | "command": "filesystem.paste", 421 | "when": "viewItem == FAVORITE_DIRECTORY && clipboard == true", 422 | "group": "9_cutcopypaste" 423 | }, 424 | { 425 | "command": "filesystem.delete", 426 | "when": "viewItem == FAVORITE_DIRECTORY", 427 | "group": "7_modification" 428 | }, 429 | { 430 | "command": "filesystem.rename", 431 | "when": "viewItem == FAVORITE_DIRECTORY", 432 | "group": "7_modification" 433 | }, 434 | { 435 | "command": "favorites.addExternal", 436 | "when": "view == favoritesActivity && viewItem != FAVORITE_DIRECTORY && viewItem != FAVORITE_FILE && viewItem != FS_FILE && viewItem != FS_DIRECTORY", 437 | "group": "7_modification" 438 | }, 439 | { 440 | "command": "favorites.addExternal", 441 | "when": "view == favorites && viewItem != FAVORITE_DIRECTORY && viewItem != FAVORITE_FILE && viewItem != FS_FILE && viewItem != FS_DIRECTORY", 442 | "group": "7_modification" 443 | }, 444 | { 445 | "command": "favorites.copy.path", 446 | "when": "viewItem == FAVORITE_FILE", 447 | "group": "8_copyNameOrPath" 448 | }, 449 | { 450 | "command": "favorites.copy.path", 451 | "when": "viewItem == FS_FILE", 452 | "group": "8_copyNameOrPath" 453 | }, 454 | { 455 | "command": "favorites.copy.path", 456 | "when": "viewItem == FAVORITE_DIRECTORY", 457 | "group": "8_copyNameOrPath" 458 | }, 459 | { 460 | "command": "favorites.copy.path", 461 | "when": "viewItem == FS_DIRECTORY", 462 | "group": "8_copyNameOrPath" 463 | }, 464 | { 465 | "command": "favorites.copy.name", 466 | "when": "viewItem == FAVORITE_FILE", 467 | "group": "8_copyNameOrPath" 468 | }, 469 | { 470 | "command": "favorites.copy.name", 471 | "when": "viewItem == FS_FILE", 472 | "group": "8_copyNameOrPath" 473 | }, 474 | { 475 | "command": "favorites.copy.name", 476 | "when": "viewItem == FAVORITE_DIRECTORY", 477 | "group": "8_copyNameOrPath" 478 | }, 479 | { 480 | "command": "favorites.copy.name", 481 | "when": "viewItem == FS_DIRECTORY", 482 | "group": "8_copyNameOrPath" 483 | } 484 | ] 485 | }, 486 | "commands": [ 487 | { 488 | "command": "favorites.selectWorkspace", 489 | "title": "Favorites: Select Workspace for Favorites" 490 | }, 491 | { 492 | "command": "favorites.selectFromRegistry", 493 | "title": "Favorites: Select alternative storage from registry" 494 | }, 495 | { 496 | "command": "favorites.nav.sort.az", 497 | "title": "Sort ascending", 498 | "icon": { 499 | "dark": "images/sort_dark_za.svg", 500 | "light": "images/sort_light_za.svg" 501 | } 502 | }, 503 | { 504 | "command": "favorites.nav.sort.za", 505 | "title": "Sort descending", 506 | "icon": { 507 | "dark": "images/sort_dark_az.svg", 508 | "light": "images/sort_light_az.svg" 509 | } 510 | }, 511 | { 512 | "command": "favorites.collapse", 513 | "title": "Collapse all", 514 | "icon": { 515 | "dark": "images/collapse_dark.svg", 516 | "light": "images/collapse_light.svg" 517 | } 518 | }, 519 | { 520 | "command": "favorites.collapse.activity", 521 | "title": "Collapse all", 522 | "icon": { 523 | "dark": "images/collapse_dark.svg", 524 | "light": "images/collapse_light.svg" 525 | } 526 | }, 527 | { 528 | "command": "favorites.addToFavorites", 529 | "title": "Favorites: Add to favorites" 530 | }, 531 | { 532 | "command": "favorites.addToFavoritesGroup", 533 | "title": "Favorites: Add to group of favorites" 534 | }, 535 | { 536 | "command": "favorites.addFileToFavorites", 537 | "title": "Favorites: Add file to favorites" 538 | }, 539 | { 540 | "command": "favorites.addFileToFavoritesGroup", 541 | "title": "Favorites: Add file to group of favorites" 542 | }, 543 | { 544 | "command": "favorites.deleteFavorite", 545 | "title": "Remove from favorites" 546 | }, 547 | { 548 | "command": "favorites.group.rename", 549 | "title": "Rename group" 550 | }, 551 | { 552 | "command": "favorites.alias.modify", 553 | "title": "Modify alias" 554 | }, 555 | { 556 | "command": "favorites.alias.remove", 557 | "title": "Remove alias" 558 | }, 559 | { 560 | "command": "favorites.group.create", 561 | "title": "Create group", 562 | "icon": { 563 | "dark": "images/add_dark.svg", 564 | "light": "images/add_light.svg" 565 | } 566 | }, 567 | { 568 | "command": "favorites.delete.all", 569 | "title": "Remove everything from favorites", 570 | "icon": { 571 | "dark": "images/delete_all_dark.svg", 572 | "light": "images/delete_all_light.svg" 573 | } 574 | }, 575 | { 576 | "command": "favorites.refresh", 577 | "title": "Refresh list", 578 | "icon": { 579 | "dark": "images/refresh_dark.svg", 580 | "light": "images/refresh_light.svg" 581 | } 582 | }, 583 | { 584 | "command": "favorites.group.delete", 585 | "title": "Remove group" 586 | }, 587 | { 588 | "command": "favorites.group.color.set", 589 | "title": "Group color" 590 | }, 591 | { 592 | "command": "filesystem.create.file", 593 | "title": "Create file" 594 | }, 595 | { 596 | "command": "filesystem.create.directory", 597 | "title": "Create directory" 598 | }, 599 | { 600 | "command": "filesystem.copy", 601 | "title": "Copy" 602 | }, 603 | { 604 | "command": "filesystem.cut", 605 | "title": "Cut" 606 | }, 607 | { 608 | "command": "filesystem.paste", 609 | "title": "Paste" 610 | }, 611 | { 612 | "command": "filesystem.delete", 613 | "title": "Delete" 614 | }, 615 | { 616 | "command": "filesystem.rename", 617 | "title": "Rename" 618 | }, 619 | { 620 | "command": "filesystem.duplicate", 621 | "title": "Duplicate" 622 | }, 623 | { 624 | "command": "favorites.addExternal", 625 | "title": "Add external resource" 626 | }, 627 | { 628 | "command": "favorites.copy.path", 629 | "title": "Copy Path" 630 | }, 631 | { 632 | "command": "favorites.copy.name", 633 | "title": "Copy Name" 634 | }, 635 | { 636 | "command": "favorites.browse", 637 | "title": "Favorites: Browse" 638 | } 639 | ] 640 | }, 641 | "devDependencies": { 642 | "@types/clipboardy": "^1.1.0", 643 | "@types/color": "^3.0.0", 644 | "@types/fs-extra": "^5.0.2", 645 | "@types/lodash": "^4.14.108", 646 | "@types/lodash-es": "^4.17.0", 647 | "@types/md5": "^2.1.32", 648 | "@types/minimatch": "^3.0.3", 649 | "@types/nconf": "0.0.37", 650 | "@types/node": "^9.4.7", 651 | "del": "^3.0.0", 652 | "gulp": "^3.9.1", 653 | "gulp-rename": "^1.2.2", 654 | "ts-loader": "^5.3.0", 655 | "tslint": "^5.9.1", 656 | "typescript": "^2.7.2", 657 | "vscode": "^1.1.13", 658 | "vscode-nls-dev": "^2.1.6", 659 | "webpack": "^4.25.1", 660 | "webpack-cli": "^3.1.2" 661 | }, 662 | "dependencies": { 663 | "clipboardy": "^1.2.3", 664 | "color": "^3.0.0", 665 | "cryptiles": "^4.1.2", 666 | "fs-extra": "^7.0.0", 667 | "gulp-remote-src-vscode": "^0.5.1", 668 | "html-colors": "0.0.6", 669 | "lodash": "^4.17.5", 670 | "lodash-es": "^4.17.10", 671 | "md5": "^2.2.1", 672 | "minimatch": "^3.0.4", 673 | "nconf": "^0.10.0", 674 | "rxjs": "^6.2.2", 675 | "spawn-sync": "^2.0.0", 676 | "trash": "^4.3.0", 677 | "url-parse": "^1.4.3" 678 | } 679 | } 680 | -------------------------------------------------------------------------------- /preview/adding-favorite.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kdcro101/vscode-favorites/bbf4ca0ead1ef60ea28c4e08868437271b6bbc83/preview/adding-favorite.jpg -------------------------------------------------------------------------------- /preview/adding.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kdcro101/vscode-favorites/bbf4ca0ead1ef60ea28c4e08868437271b6bbc83/preview/adding.jpg -------------------------------------------------------------------------------- /preview/browsing.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kdcro101/vscode-favorites/bbf4ca0ead1ef60ea28c4e08868437271b6bbc83/preview/browsing.gif -------------------------------------------------------------------------------- /preview/operations.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kdcro101/vscode-favorites/bbf4ca0ead1ef60ea28c4e08868437271b6bbc83/preview/operations.jpg -------------------------------------------------------------------------------- /preview/preview-promo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kdcro101/vscode-favorites/bbf4ca0ead1ef60ea28c4e08868437271b6bbc83/preview/preview-promo.gif -------------------------------------------------------------------------------- /preview/selectRegistry.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kdcro101/vscode-favorites/bbf4ca0ead1ef60ea28c4e08868437271b6bbc83/preview/selectRegistry.jpg -------------------------------------------------------------------------------- /preview/statusRegistry.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kdcro101/vscode-favorites/bbf4ca0ead1ef60ea28c4e08868437271b6bbc83/preview/statusRegistry.jpg -------------------------------------------------------------------------------- /preview/using.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kdcro101/vscode-favorites/bbf4ca0ead1ef60ea28c4e08868437271b6bbc83/preview/using.jpg -------------------------------------------------------------------------------- /src/class/clipboard.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import { ClipboardBuffer } from "../types"; 3 | import { ViewItem } from "./view-item"; 4 | 5 | export class Clipboard { 6 | private static buffer: ClipboardBuffer = null; 7 | 8 | public copy(item: ViewItem) { 9 | Clipboard.buffer = { 10 | operation: "copy", 11 | item, 12 | }; 13 | vscode.commands.executeCommand("setContext", "clipboard", true); 14 | } 15 | public cut(item: ViewItem) { 16 | Clipboard.buffer = { 17 | operation: "cut", 18 | item, 19 | }; 20 | vscode.commands.executeCommand("setContext", "clipboard", true); 21 | } 22 | public reset() { 23 | Clipboard.buffer = null; 24 | vscode.commands.executeCommand("setContext", "clipboard", false); 25 | } 26 | public get(): ClipboardBuffer { 27 | return Clipboard.buffer; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/class/dataProvider.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import { Favorites } from "./favorites"; 3 | import { FavoritesBrowser } from "./favorites-browser"; 4 | import { ViewItem } from "./view-item"; 5 | 6 | export class DataProvider implements vscode.TreeDataProvider { 7 | public onDidChangeTreeDataEmmiter = new vscode.EventEmitter(); 8 | public readonly onDidChangeTreeData: vscode.Event = this.onDidChangeTreeDataEmmiter.event; 9 | public returnEmpty: boolean = false; 10 | private browser: FavoritesBrowser = null; 11 | 12 | constructor(private favorites: Favorites) { 13 | this.browser = new FavoritesBrowser(favorites); 14 | } 15 | 16 | public getParent(item: ViewItem): Thenable { 17 | return this.browser.getParent(item); 18 | } 19 | public refresh(): void { 20 | this.onDidChangeTreeDataEmmiter.fire(); 21 | } 22 | public getTreeItem(item: ViewItem): vscode.TreeItem { 23 | return item; 24 | } 25 | 26 | public getChildren(item?: ViewItem): Thenable { 27 | if (this.returnEmpty) { 28 | return Promise.resolve([]); 29 | } 30 | return this.browser.getChildren(item); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/class/exclude-check.ts: -------------------------------------------------------------------------------- 1 | // 2 | import * as minimatch from "minimatch"; 3 | import { Workspace } from "./workspace"; 4 | 5 | export class ExcludeCheck { 6 | private excludeList: string[] = []; 7 | private useExclude: boolean = true; 8 | 9 | constructor(private workspace: Workspace) { 10 | this.collectExcludeList(); 11 | this.useExclude = workspace.get("useFilesExclude"); 12 | } 13 | 14 | public isExcluded(fsPath: string): boolean { 15 | 16 | if (!this.useExclude) { 17 | return false; 18 | } 19 | 20 | let relative = this.workspace.workspacePath(fsPath); 21 | if (!relative) { 22 | relative = fsPath; 23 | } 24 | 25 | const excluded = this.excludeList.reduce((acc, cur) => { 26 | const res = minimatch(relative, cur, { 27 | dot: true, 28 | }); 29 | if (res) { 30 | acc = true; 31 | return acc; 32 | } 33 | return acc; 34 | }, false); 35 | 36 | return excluded; 37 | } 38 | private collectExcludeList() { 39 | const filesExclude = this.workspace.getGlobal("files.exclude"); 40 | const filesExcludeList = Object.keys(filesExclude); 41 | 42 | this.excludeList = filesExcludeList.reduce((acc, cur, i) => { 43 | const val = filesExclude[cur]; 44 | if (val === true) { 45 | acc.push(cur); 46 | return acc; 47 | } 48 | return acc; 49 | }, []); 50 | 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/class/favorites-browser.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | import * as path from "path"; 3 | import * as vscode from "vscode"; 4 | import { FilesystemResource, ResourceType } from "../types"; 5 | import { Favorites } from "./favorites"; 6 | import { ViewItem } from "./view-item"; 7 | import workspace from "./workspace"; 8 | 9 | export class FavoritesBrowser { 10 | constructor(private favorites: Favorites) { 11 | 12 | } 13 | public getChildren(item: ViewItem): Promise { 14 | return new Promise((resolve, reject) => { 15 | 16 | if (!item) { 17 | this.favorites.groupViewItems(null) 18 | .then((result) => { 19 | resolve(result); 20 | }).catch((e) => { 21 | reject(e); 22 | }); 23 | return; 24 | } 25 | 26 | if (item.resourceType === ResourceType.Group) { 27 | this.favorites.groupViewItems(item) 28 | .then((result) => { 29 | resolve(result); 30 | }) 31 | .catch((e) => { 32 | reject(e); 33 | }); 34 | return; 35 | } 36 | if (item.resourceType === ResourceType.Directory) { 37 | this.getDirectoryItems(item) 38 | .then((result) => { 39 | resolve(result); 40 | }) 41 | .catch((e) => { 42 | reject(e); 43 | }); 44 | 45 | return; 46 | } 47 | 48 | resolve([]); 49 | 50 | }); 51 | } 52 | public getParent(item: ViewItem): Promise { 53 | if (item == null) { 54 | return null; 55 | } 56 | return item.getParent(); 57 | } 58 | private getDirectoryItems(parentItem: ViewItem): Promise { 59 | return new Promise((resolve, reject) => { 60 | const fsPath = parentItem.resourceName; 61 | const sortDirection = workspace.get("sortDirection"); 62 | 63 | fs.readdir(workspace.pathResolve(fsPath), (err, items) => { 64 | if (err) { 65 | resolve([]); 66 | return; 67 | } 68 | 69 | const resolved = items.map((i) => workspace.pathResolve(path.join(fsPath, i))) 70 | .filter((item) => { 71 | return !workspace.excludeCheck.isExcluded(item); 72 | }); 73 | 74 | Promise.all(resolved.map((i) => this.favorites.identify(i))) 75 | .then((result) => { 76 | 77 | const typed = resolved.map((p, i) => { 78 | const o: FilesystemResource = { 79 | path: p, 80 | type: result[i], 81 | }; 82 | return o; 83 | }); 84 | 85 | const dirs = typed.filter((i) => i.type === ResourceType.Directory); 86 | const files = typed.filter((i) => i.type === ResourceType.File); 87 | 88 | const dirsAZ = dirs.sort((a, b) => { 89 | const aBasename = path.basename(a.path).toLocaleLowerCase(); 90 | const bBasename = path.basename(b.path).toLocaleLowerCase(); 91 | if (aBasename < bBasename) { return -1; } 92 | if (aBasename === bBasename) { return 0; } 93 | if (aBasename > bBasename) { return 1; } 94 | 95 | }); 96 | const filesAZ = files.sort((a, b) => { 97 | const aBasename = path.basename(a.path).toLocaleLowerCase(); 98 | const bBasename = path.basename(b.path).toLocaleLowerCase(); 99 | if (aBasename < bBasename) { return -1; } 100 | if (aBasename === bBasename) { return 0; } 101 | if (aBasename > bBasename) { return 1; } 102 | 103 | }); 104 | let fsItems: FilesystemResource[]; 105 | if (sortDirection === "ASC") { 106 | fsItems = dirsAZ.concat(filesAZ); 107 | } else { 108 | fsItems = dirsAZ.reverse().concat(filesAZ.reverse()); 109 | } 110 | 111 | Promise.all(fsItems.map((i) => this.favorites.viewItemForPath(i.path))) 112 | .then((views) => { 113 | resolve(views); 114 | return; 115 | }) 116 | .catch((e) => { 117 | reject(e); 118 | }); 119 | 120 | }) 121 | .catch((e) => { 122 | reject(e); 123 | }); 124 | 125 | }); 126 | 127 | }); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/class/favorites-navigator.ts: -------------------------------------------------------------------------------- 1 | import { from } from "rxjs"; 2 | import { map } from "rxjs/operators"; 3 | import * as vscode from "vscode"; 4 | import { ResourceType } from "../types"; 5 | import { Favorites } from "./favorites"; 6 | import { FavoritesBrowser } from "./favorites-browser"; 7 | import { ViewItem } from "./view-item"; 8 | 9 | export interface FavoritesNavigatorQuickPickItem extends vscode.QuickPickItem { 10 | viewItem: ViewItem; 11 | parent?: ViewItem; 12 | upLevel: boolean; 13 | } 14 | 15 | export class FavoritesNavigator { 16 | private browser: FavoritesBrowser = null; 17 | constructor(private favorites: Favorites) { 18 | this.browser = new FavoritesBrowser(this.favorites); 19 | } 20 | 21 | public build(item: ViewItem): Promise { 22 | return new Promise((resolve, reject) => { 23 | 24 | from(Promise.all([ 25 | this.browser.getChildren(item), 26 | this.browser.getParent(item), 27 | ])).pipe( 28 | map<[ViewItem[], ViewItem], FavoritesNavigatorQuickPickItem[]>((result) => { 29 | 30 | const listItems = result[0] ? result[0] : []; 31 | const parent = result[1]; 32 | 33 | let pickItems: FavoritesNavigatorQuickPickItem[] = item ? [{ 34 | label: "↰ ..", 35 | viewItem: null, 36 | upLevel: true, 37 | parent, 38 | }] : []; 39 | 40 | const contents = listItems.map((i) => { 41 | let label = ""; 42 | let desc = ""; 43 | if (i.resourceType === ResourceType.Group) { 44 | label = `■ ${i.label}`; 45 | desc = "group"; 46 | } 47 | if (i.resourceType === ResourceType.Directory) { 48 | label = `□ ${i.label}`; 49 | desc = "directory"; 50 | } 51 | if (i.resourceType === ResourceType.File) { 52 | label = `${i.label}`; 53 | desc = "file"; 54 | } 55 | 56 | const out: FavoritesNavigatorQuickPickItem = { 57 | label, 58 | description: desc, 59 | viewItem: i, 60 | upLevel: false, 61 | }; 62 | return out; 63 | }); 64 | 65 | pickItems = pickItems.concat(contents); 66 | 67 | return pickItems; 68 | }), 69 | ).subscribe((list) => { 70 | resolve(list); 71 | }, (e) => { 72 | reject(e); 73 | }); 74 | 75 | }); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/class/favorites.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | import * as _ from "lodash"; 3 | import * as path from "path"; 4 | import { from, ReplaySubject, zip } from "rxjs"; 5 | import { tap } from "rxjs/operators"; 6 | import * as vscode from "vscode"; 7 | import { GroupQuickPick, ResourceType, StoredResource } from "../types/index"; 8 | import { GroupColor } from "./group-color"; 9 | import { FavoriteStorage } from "./storage"; 10 | import { ViewItem } from "./view-item"; 11 | import workspace from "./workspace"; 12 | 13 | export class Favorites { 14 | public stateList = new ReplaySubject(1); 15 | public groupColor: GroupColor; 16 | 17 | constructor(private context: vscode.ExtensionContext, private storage: FavoriteStorage) { 18 | this.groupColor = new GroupColor(this, context); 19 | this.get() 20 | .then((result) => { 21 | console.log(`kdcro101.favorites > loaded ${result.length} stored items`); 22 | }).catch((e) => { 23 | console.log(e); 24 | }); 25 | } 26 | 27 | public updateWithPath(id: string, absPath: string): Promise { 28 | return new Promise((resolve, reject) => { 29 | Promise.all([ 30 | this.get(), 31 | ]).then((results) => { 32 | const all = results[0]; 33 | const i = all.findIndex((d) => d.id === id); 34 | 35 | if (i < 0) { 36 | reject("no_item"); 37 | return; 38 | } 39 | all[i].workspacePath = workspace.pathForWorkspace(absPath); 40 | 41 | return this.save(all); 42 | 43 | }).then(() => { 44 | resolve(); 45 | }).catch((e) => { 46 | reject(e); 47 | }); 48 | }); 49 | } 50 | public duplicateWithPath(id: string, absPath: string): Promise { 51 | return new Promise((resolve, reject) => { 52 | Promise.all([ 53 | this.get(), 54 | ]).then((results) => { 55 | const all = results[0]; 56 | const i = all.findIndex((d) => d.id === id); 57 | 58 | if (i < 0) { 59 | reject("no_item"); 60 | return; 61 | } 62 | const o = all[i]; 63 | 64 | const workspaceRoot = workspace.workspaceRoot(absPath); 65 | if (workspaceRoot) { 66 | return this.addPathToGroup(o.parent_id, absPath); 67 | } else { 68 | return this.addExternalPathToGroup(o.parent_id, absPath); 69 | } 70 | 71 | }).then(() => { 72 | resolve(); 73 | }).catch((e) => { 74 | reject(e); 75 | }); 76 | }); 77 | } 78 | public generateGroupQuickPickList(): Promise { 79 | return new Promise((resolve, reject) => { 80 | 81 | const out: GroupQuickPick[] = []; 82 | 83 | Promise.all([ 84 | this.get(), 85 | ]).then((result) => { 86 | const all = result[0]; 87 | const root = all.filter((i) => i.type === ResourceType.Group && i.parent_id == null); 88 | 89 | const addChildren = (lastDepth: number, parentId: string) => { 90 | const children = all.filter((i) => i.type === ResourceType.Group && i.parent_id === parentId); 91 | const paddingLen: number = lastDepth + 1; 92 | const padding = "".padStart(paddingLen * 2, " ") + "▹"; 93 | 94 | children.forEach((c, i) => { 95 | const label = `${padding} ${c.name}`; 96 | const o: GroupQuickPick = { 97 | id: c.id, 98 | label, 99 | description: "", 100 | }; 101 | out.push(o); 102 | addChildren(lastDepth + 1, c.id); 103 | }); 104 | }; 105 | 106 | root.forEach((g, i) => { 107 | const o: GroupQuickPick = { 108 | id: g.id, 109 | label: g.name, 110 | description: "", 111 | }; 112 | out.push(o); 113 | addChildren(0, g.id); 114 | }); 115 | 116 | resolve(out); 117 | }); 118 | }); 119 | } 120 | public removeResource(id: string) { 121 | this.get() 122 | .then((result) => { 123 | 124 | const toDelete: string[] = []; 125 | toDelete.push(id); 126 | 127 | const collectChildren = (resourceId: string) => { 128 | toDelete.push(resourceId); 129 | const cc = result.filter((i) => i.parent_id === resourceId); 130 | cc.forEach((sc) => { 131 | collectChildren(sc.id); 132 | }); 133 | }; 134 | 135 | const c = result.filter((i) => i.parent_id === id); 136 | 137 | c.forEach((e) => { 138 | collectChildren(e.id); 139 | }); 140 | 141 | const final = result.filter((i) => toDelete.find((x) => x === i.id) == null); 142 | 143 | this.save(final); 144 | 145 | }) 146 | .catch((e) => { 147 | console.log(e); 148 | }); 149 | } 150 | public addExternalPathToGroup(groupId: string, itemPath: string) { 151 | return new Promise((resolve, reject) => { 152 | 153 | let all: StoredResource[] = null; 154 | Promise.all([ 155 | this.get(), 156 | ]).then((result) => { 157 | all = result[0]; 158 | 159 | const groupContents = all.filter((i) => i.parent_id === groupId); 160 | const hasPath = groupContents.filter((i) => i.type !== ResourceType.Group && i.name === itemPath); 161 | 162 | if (hasPath.length > 0) { 163 | resolve(); 164 | return; 165 | } 166 | 167 | return this.identify(itemPath); 168 | 169 | }).then((t) => { 170 | 171 | if (t == null) { 172 | vscode.window.showErrorMessage(`Unable to identify type of ${itemPath}`); 173 | reject("identify_error"); 174 | return; 175 | } 176 | 177 | const o = this.createStoredResource(groupId, itemPath, t, true); 178 | all.push(o); 179 | return this.save(all); 180 | 181 | }).then(() => { 182 | resolve(); 183 | }).catch((e) => { 184 | reject(e); 185 | }); 186 | 187 | }); 188 | } 189 | public addPathToGroup(groupId: string, itemPath: string) { 190 | return new Promise((resolve, reject) => { 191 | 192 | let all: StoredResource[] = null; 193 | Promise.all([ 194 | this.get(), 195 | ]).then((result) => { 196 | all = result[0]; 197 | const isMultiRoot = workspace.isMultiRootWorkspace(); 198 | const rPath = isMultiRoot ? itemPath : itemPath.substr(workspace.getSingleRootPath().length + 1); 199 | const groupContents = all.filter((i) => i.parent_id === groupId); 200 | const hasPath = groupContents.filter((i) => i.type !== ResourceType.Group && i.name === itemPath); 201 | 202 | if (hasPath.length > 0) { 203 | return Promise.resolve("exists"); 204 | } 205 | 206 | return this.identify(itemPath); 207 | 208 | }).then((t: ResourceType) => { 209 | 210 | if ((t as string) === "exists") { 211 | vscode.window.showWarningMessage(`${itemPath} already in favorites`); 212 | resolve(); 213 | return; 214 | } 215 | 216 | if (t == null) { 217 | vscode.window.showErrorMessage(`Unable to identify type of ${itemPath}`); 218 | reject("identify_error"); 219 | return; 220 | } 221 | 222 | const o = this.createStoredResource(groupId, itemPath, t); 223 | all.push(o); 224 | return this.save(all); 225 | 226 | }).then(() => { 227 | resolve(); 228 | }).catch((e) => { 229 | reject(e); 230 | }); 231 | 232 | }); 233 | } 234 | public labelModify(id: string, name: string) { 235 | return new Promise((resolve, reject) => { 236 | 237 | Promise.all([ 238 | this.get(), 239 | ]).then((result) => { 240 | const all = result[0]; 241 | const i = all.findIndex((r) => r.id === id); 242 | if (i === -1) { 243 | resolve(); 244 | } 245 | all[i].label = name; 246 | return this.save(all); 247 | 248 | }).then(() => { 249 | resolve(); 250 | }).catch((e) => { 251 | reject(e); 252 | }); 253 | 254 | }); 255 | } 256 | public groupRename(id: string, name: string) { 257 | return new Promise((resolve, reject) => { 258 | 259 | Promise.all([ 260 | this.get(), 261 | ]).then((result) => { 262 | const all = result[0]; 263 | const i = all.findIndex((r) => r.id === id); 264 | if (i === -1) { 265 | resolve(); 266 | } 267 | all[i].name = name; 268 | return this.save(all); 269 | 270 | }).then(() => { 271 | resolve(); 272 | }).catch((e) => { 273 | reject(e); 274 | }); 275 | 276 | }); 277 | } 278 | public addGroup(parent_id: string, name: string) { 279 | return new Promise((resolve, reject) => { 280 | 281 | Promise.all([ 282 | this.get(), 283 | ]).then((result) => { 284 | const all = result[0]; 285 | 286 | const o = this.createStoredResource(parent_id, name, ResourceType.Group); 287 | const newList: StoredResource[] = all.concat([o]); 288 | return this.save(newList); 289 | 290 | }).then(() => { 291 | resolve(); 292 | }).catch((e) => { 293 | reject(e); 294 | }); 295 | 296 | }); 297 | } 298 | public get(): Promise { 299 | return new Promise((resolve, reject) => { 300 | 301 | from(this.storage.get()).pipe( 302 | tap((list) => this.stateList.next(list)), 303 | ).subscribe((list) => { 304 | resolve(list); 305 | }, (e) => { 306 | console.log(e); 307 | }); 308 | 309 | }); 310 | } 311 | 312 | public save(list: StoredResource[]): Promise { 313 | this.stateList.next(list); 314 | return this.storage.save(list); 315 | } 316 | public identify(itemPath: string): Promise { 317 | return new Promise((resolve, reject) => { 318 | fs.stat(workspace.pathResolve(itemPath), (err, stat: fs.Stats) => { 319 | 320 | if (err) { 321 | resolve(null); 322 | return; 323 | } 324 | 325 | const isDir = stat.isDirectory(); 326 | const isFile = stat.isFile(); 327 | 328 | if (isDir) { 329 | resolve(ResourceType.Directory); 330 | return; 331 | } 332 | if (isFile) { 333 | resolve(ResourceType.File); 334 | return; 335 | } 336 | 337 | resolve(null); 338 | 339 | }); 340 | }); 341 | } 342 | public groupViewItems(parentItem: ViewItem): Promise { 343 | 344 | const parentId = parentItem == null ? null : parentItem.id; 345 | 346 | return new Promise((resolve, reject) => { 347 | Promise.all([ 348 | this.get(), 349 | ]).then((result) => { 350 | const all = result[0]; 351 | // tslint:disable-next-line:triple-equals 352 | const list: StoredResource[] = all.filter((i) => i.parent_id == parentId) 353 | .filter((item) => { 354 | if (item.type === ResourceType.Group) { 355 | return true; 356 | } 357 | const checkPath = item.fsPath || path.join(item.workspaceRoot, item.workspacePath); 358 | return !workspace.excludeCheck.isExcluded(checkPath); 359 | }); 360 | 361 | this.sortStoredResources(list) 362 | .then((sorted) => { 363 | Promise.all(sorted.map((i) => this.asViewItem(i))) 364 | .then((views) => { 365 | resolve(views); 366 | }) 367 | .catch((e) => { 368 | reject(e); 369 | }); 370 | }) 371 | .catch((e) => { 372 | reject(e); 373 | }); 374 | 375 | }).catch((e) => { 376 | reject(e); 377 | }); 378 | }); 379 | } 380 | public sortStoredResources(list: StoredResource[]): Promise { 381 | return new Promise((resolve, reject) => { 382 | try { 383 | 384 | const dirs = list.filter((i) => i.type === ResourceType.Directory); 385 | const files = list.filter((i) => i.type === ResourceType.File); 386 | const groups = list.filter((i) => i.type === ResourceType.Group); 387 | 388 | const sortDirection = workspace.get("sortDirection"); 389 | const groupsFirst = workspace.get("groupsFirst"); 390 | 391 | const groupsAZ = groups.sort((a, b) => { 392 | const aBasename = a.name; 393 | const bBasename = b.name; 394 | if (aBasename < bBasename) { return -1; } 395 | if (aBasename === bBasename) { return 0; } 396 | if (aBasename > bBasename) { return 1; } 397 | }); 398 | 399 | const dirsAZ = dirs.sort((a, b) => { 400 | const aBasename = path.basename(a.name).toLocaleLowerCase(); 401 | const bBasename = path.basename(b.name).toLocaleLowerCase(); 402 | if (aBasename < bBasename) { return -1; } 403 | if (aBasename === bBasename) { return 0; } 404 | if (aBasename > bBasename) { return 1; } 405 | }); 406 | 407 | const filesAZ = files.sort((a, b) => { 408 | const aBasename = path.basename(a.name).toLocaleLowerCase(); 409 | const bBasename = path.basename(b.name).toLocaleLowerCase(); 410 | if (aBasename < bBasename) { return -1; } 411 | if (aBasename === bBasename) { return 0; } 412 | if (aBasename > bBasename) { return 1; } 413 | 414 | }); 415 | 416 | let fsItems: StoredResource[]; 417 | let groupsPrepared: StoredResource[]; 418 | 419 | if (sortDirection === "ASC") { 420 | fsItems = dirsAZ.concat(filesAZ); 421 | groupsPrepared = groupsAZ; 422 | } else { 423 | fsItems = dirsAZ.reverse().concat(filesAZ.reverse()); 424 | groupsPrepared = groupsAZ.reverse(); 425 | } 426 | let final: StoredResource[]; 427 | 428 | if (groupsFirst) { 429 | final = groupsPrepared.concat(fsItems); 430 | } else { 431 | final = fsItems.concat(groupsPrepared); 432 | } 433 | 434 | resolve(final); 435 | } catch (e) { 436 | reject(e); 437 | } 438 | 439 | }); 440 | } 441 | 442 | public viewItemForPath(fsPath: string): Promise { 443 | return new Promise((resolve, reject) => { 444 | const enablePreview = vscode.workspace.getConfiguration("workbench.editor").get("enablePreview") as boolean; 445 | Promise.all([this.identify(fsPath)]) 446 | .then((result) => { 447 | let o: ViewItem; 448 | switch (result[0]) { 449 | case ResourceType.File: 450 | const fUri = workspace.pathAsUri(fsPath); 451 | o = new ViewItem( 452 | path.basename(fsPath), 453 | vscode.TreeItemCollapsibleState.None, 454 | fsPath, 455 | "FS_FILE", 456 | fsPath, 457 | ResourceType.File 458 | , null, 459 | { 460 | command: "vscode.open", 461 | title: "", 462 | arguments: [fUri, { preview: enablePreview }], 463 | }, 464 | ); 465 | break; 466 | case ResourceType.Directory: 467 | o = new ViewItem( 468 | path.basename(fsPath), 469 | vscode.TreeItemCollapsibleState.Collapsed, 470 | fsPath, 471 | "FS_DIRECTORY", 472 | fsPath, 473 | ResourceType.Directory 474 | , null); 475 | break; 476 | } 477 | 478 | resolve(o); 479 | }) 480 | .catch((e) => { 481 | reject(e); 482 | }); 483 | 484 | }); 485 | } 486 | public asViewItem(i: StoredResource): ViewItem { 487 | const enablePreview = vscode.workspace.getConfiguration("workbench.editor").get("enablePreview") as boolean; 488 | 489 | let o: ViewItem = null; 490 | switch (i.type) { 491 | case ResourceType.File: 492 | 493 | if (i.fsPath == null) { 494 | 495 | // const fPath = workspace.pathAbsolute(i.workspacePath); 496 | const fPath = path.join(i.workspaceRoot, i.workspacePath); 497 | const fUri = workspace.pathAsUri(fPath); 498 | o = new ViewItem( 499 | (i.label != null) ? i.label : path.basename(i.workspacePath), 500 | vscode.TreeItemCollapsibleState.None, 501 | fPath, 502 | "FAVORITE_FILE", 503 | fPath, 504 | i.type, 505 | null, // NO ICON 506 | { 507 | command: "vscode.open", 508 | title: "", 509 | arguments: [fUri, { preview: enablePreview }], 510 | }, 511 | i.id, 512 | i.parent_id, 513 | (i.label != null) ? `[alias] ${path.basename(i.name)}` : null, 514 | ); 515 | } else { 516 | const fPath = i.fsPath; 517 | const fUri = vscode.Uri.file(fPath); 518 | o = new ViewItem( 519 | (i.label != null) ? i.label : path.basename(fPath), 520 | vscode.TreeItemCollapsibleState.None, 521 | fPath, 522 | "FAVORITE_FILE", 523 | fPath, 524 | i.type, 525 | null, // NO ICON 526 | { 527 | command: "vscode.open", 528 | title: "", 529 | arguments: [fUri, { preview: enablePreview }], 530 | }, 531 | i.id, 532 | i.parent_id, 533 | (i.label != null) ? `[alias] ${path.basename(i.name)}` : null, 534 | ); 535 | } 536 | 537 | break; 538 | case ResourceType.Directory: 539 | 540 | if (i.fsPath == null) { 541 | // const wPath = workspace.pathAbsolute(i.workspacePath); 542 | const wPath = path.join(i.workspaceRoot, i.workspacePath); 543 | o = new ViewItem( 544 | (i.label != null) ? i.label : path.basename(wPath), 545 | vscode.TreeItemCollapsibleState.Collapsed, 546 | wPath, 547 | "FAVORITE_DIRECTORY", 548 | wPath, 549 | i.type, 550 | null, 551 | null, 552 | i.id, 553 | i.parent_id, 554 | (i.label != null) ? `[alias] ${i.name}` : null, 555 | ); 556 | 557 | } else { 558 | const fsPath = i.fsPath; 559 | o = new ViewItem( 560 | (i.label != null) ? i.label : path.basename(fsPath), 561 | vscode.TreeItemCollapsibleState.Collapsed, 562 | fsPath, 563 | "FAVORITE_DIRECTORY", 564 | fsPath, 565 | i.type, 566 | null, 567 | null, 568 | i.id, 569 | i.parent_id, 570 | (i.label != null) ? `[alias] ${i.name}` : null, 571 | ); 572 | } 573 | 574 | break; 575 | case ResourceType.Group: 576 | const iconLight: string = i.iconColor == null ? 577 | // this.context.asAbsolutePath(path.join("images", "group_light.svg")) : i.iconPath; 578 | this.context.asAbsolutePath(path.join("images", "group_light.svg")) : this.groupColor.getIconPath(i.iconColor); 579 | const iconDark: string = i.iconColor == null ? 580 | // this.context.asAbsolutePath(path.join("images", "group_dark.svg")) : i.iconPath; 581 | this.context.asAbsolutePath(path.join("images", "group_dark.svg")) : this.groupColor.getIconPath(i.iconColor); 582 | 583 | o = new ViewItem( 584 | i.name, 585 | vscode.TreeItemCollapsibleState.Collapsed, 586 | i.name, 587 | "FAVORITE_GROUP", 588 | i.name, 589 | i.type, 590 | { 591 | light: iconLight, 592 | dark: iconDark, 593 | }, 594 | null, 595 | i.id, 596 | i.parent_id, 597 | 598 | ); 599 | break; 600 | } 601 | 602 | // o.parentViewItem = parentItem; 603 | return o; 604 | } 605 | 606 | public isPartOfFavorites(fsPath: string): Promise { 607 | return new Promise((resolve, reject) => { 608 | 609 | if (!fsPath) { 610 | resolve(false); 611 | return; 612 | } 613 | 614 | zip(this.stateList, this.identify(fsPath)) 615 | .subscribe((result) => { 616 | const list = result[0]; 617 | const type = result[1]; 618 | 619 | if (!fsPath || !list || list.length === 0) { 620 | resolve(false); 621 | return; 622 | } 623 | 624 | let dir: string = ""; 625 | 626 | if (type === ResourceType.Directory) { 627 | dir = fsPath; 628 | } 629 | if (type === ResourceType.File) { 630 | dir = path.dirname(fsPath); 631 | } 632 | 633 | const splits = dir.split(path.sep); 634 | const findDirect = list.find((sr) => sr.name === dir && sr.type === ResourceType.Directory); 635 | 636 | if (findDirect) { 637 | resolve(true); 638 | } 639 | let currentDir = dir; 640 | for (let i = 0; i < splits.length; i++) { 641 | const p = path.join(currentDir, ".."); 642 | const f = list.find((sr) => sr.name === p && sr.type === ResourceType.Directory); 643 | if (f) { 644 | resolve(true); 645 | } 646 | currentDir = p; 647 | } 648 | 649 | resolve(false); 650 | 651 | }, (e) => { 652 | reject(e); 653 | }); 654 | 655 | }); 656 | } 657 | 658 | private createStoredResource(parent_id: string, name: string, type: ResourceType, external: boolean = false): StoredResource { 659 | let o: StoredResource = null; 660 | switch (type) { 661 | case ResourceType.Group: 662 | o = { 663 | type, 664 | name, 665 | parent_id, 666 | workspaceRoot: null, 667 | workspacePath: null, 668 | id: this.generateId(), 669 | }; 670 | break; 671 | case ResourceType.Directory: 672 | if (external) { 673 | o = { 674 | type, 675 | name, 676 | parent_id, 677 | fsPath: name, 678 | workspaceRoot: null, 679 | workspacePath: null, 680 | id: this.generateId(), 681 | }; 682 | } else { 683 | o = { 684 | type, 685 | name, 686 | parent_id, 687 | // workspacePath: workspace.pathForWorkspace(name), 688 | workspaceRoot: workspace.workspaceRoot(name), 689 | workspacePath: workspace.workspacePath(name), 690 | id: this.generateId(), 691 | }; 692 | 693 | } 694 | break; 695 | case ResourceType.File: 696 | if (external) { 697 | o = { 698 | type, 699 | name, 700 | parent_id, 701 | fsPath: name, 702 | workspaceRoot: null, 703 | workspacePath: null, 704 | id: this.generateId(), 705 | }; 706 | } else { 707 | o = { 708 | type, 709 | name, 710 | parent_id, 711 | // workspacePath: workspace.pathForWorkspace(name), 712 | workspaceRoot: workspace.workspaceRoot(name), 713 | workspacePath: workspace.workspacePath(name), 714 | id: this.generateId(), 715 | }; 716 | 717 | } 718 | break; 719 | } 720 | 721 | return o; 722 | } 723 | private generateId(): string { 724 | return _.sampleSize("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 16).join(""); 725 | } 726 | } 727 | -------------------------------------------------------------------------------- /src/class/filesystem.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs-extra"; 2 | import * as path from "path"; 3 | import { from } from "rxjs"; 4 | import { concatMap } from "rxjs/operators"; 5 | import * as vscode from "vscode"; 6 | import { Favorites } from "./favorites"; 7 | import { ViewItem } from "./view-item"; 8 | import workspace from "./workspace"; 9 | 10 | import trash = require("trash"); 11 | 12 | export class FilesystemUtils { 13 | constructor(private favorites: Favorites) { 14 | 15 | } 16 | public duplicate(item: ViewItem) { 17 | 18 | return new Promise((resolve, reject) => { 19 | const isFav = item.isFavorite; 20 | const aPath = item.value; 21 | const base = path.basename(aPath); 22 | const dir = path.dirname(aPath); 23 | 24 | Promise.all([]) 25 | .then(() => { 26 | 27 | vscode.window.showInputBox({ 28 | prompt: "Enter name for duplicate", 29 | placeHolder: "Type duplicate name", 30 | value: base, 31 | }).then((val) => { 32 | if (val == null || val.trim() === "") { 33 | resolve(); 34 | return; 35 | } 36 | if (val === base) { 37 | vscode.window.showWarningMessage("New name must be different"); 38 | reject("not_different"); 39 | return; 40 | } 41 | const newBase = val.trim(); 42 | const newPath = path.join(dir, newBase); 43 | fs.copy(aPath, newPath) 44 | .then(() => { 45 | return isFav ? this.favorites.duplicateWithPath(item.id, newPath) : Promise.resolve(); 46 | }).then((result) => { 47 | vscode.window.showInformationMessage(`Duplication successful`); 48 | resolve(); 49 | }).catch((e) => { 50 | vscode.window.showErrorMessage(`Error duplicating ${base}`); 51 | reject(e); 52 | }); 53 | 54 | }); 55 | }) 56 | .catch((e) => { 57 | reject(e); 58 | }); 59 | }); 60 | } 61 | public delete(item: ViewItem) { 62 | return new Promise((resolve, reject) => { 63 | const useTrash = workspace.get("useTrash") as boolean; 64 | const aPath = item.value; 65 | const base = path.basename(aPath); 66 | 67 | vscode.window.showQuickPick( 68 | ["Yes", "No"], { 69 | placeHolder: `Are you sure you want to PERMANENTLY delete '${base}' ?`, 70 | }, 71 | ).then((val) => { 72 | if (val == null || val.toLocaleLowerCase() === "no") { 73 | resolve(); 74 | return; 75 | } 76 | 77 | const p = useTrash ? this.deleteToTrash(item) : this.deletePermanently(item); 78 | 79 | p.then((result) => { 80 | resolve(); 81 | }).catch((e) => { 82 | vscode.window.showErrorMessage(`Error deleting ${base}`); 83 | reject(e); 84 | }); 85 | 86 | }); 87 | }); 88 | } 89 | public deletePermanently(item: ViewItem) { 90 | return new Promise((resolve, reject) => { 91 | 92 | const isFav = item.isFavorite; 93 | const aPath = item.value; 94 | const base = path.basename(aPath); 95 | 96 | fs.remove(aPath).then((result) => { 97 | return isFav ? this.favorites.removeResource(item.id) : Promise.resolve(); 98 | }).then((result) => { 99 | resolve(); 100 | }).catch((e) => { 101 | vscode.window.showErrorMessage(`Error deleting ${base}`); 102 | reject(e); 103 | }); 104 | 105 | }); 106 | 107 | } 108 | public deleteToTrash(item: ViewItem): Promise { 109 | return new Promise((resolve, reject) => { 110 | const isFav = item.isFavorite; 111 | const resourcePath = item.value; 112 | const base = path.basename(resourcePath); 113 | 114 | from(trash([resourcePath])).pipe( 115 | concatMap(() => { 116 | return isFav ? Promise.resolve(this.favorites.removeResource(item.id)) : Promise.resolve(); 117 | }), 118 | ).subscribe(() => { 119 | resolve(); 120 | }, (e) => { 121 | vscode.window.showQuickPick( 122 | ["Yes", "No"], 123 | { 124 | placeHolder: `Trash is not accessible. Do you want to permanently delete '${base}' ?`, 125 | }, 126 | ).then((response) => { 127 | 128 | if (response == null || response === "No") { 129 | resolve(); 130 | return; 131 | } 132 | fs.remove(resourcePath) 133 | .then(() => { 134 | return isFav ? Promise.resolve(this.favorites.removeResource(item.id)) : Promise.resolve(); 135 | }) 136 | .then(() => { 137 | resolve(); 138 | }).catch((fsError) => { 139 | reject(fsError); 140 | }); 141 | 142 | }); 143 | 144 | }); 145 | 146 | }); 147 | } 148 | public rename(item: ViewItem) { 149 | return new Promise((resolve, reject) => { 150 | const isFav = item.isFavorite; 151 | const aPath = item.value; 152 | const base = path.basename(aPath); 153 | const dir = path.dirname(aPath); 154 | vscode.window.showInputBox({ 155 | prompt: "Enter new name", 156 | placeHolder: "Type new name", 157 | value: base, 158 | }).then((val) => { 159 | if (val == null || val.trim() === "") { 160 | return; 161 | } 162 | if (val === base) { 163 | vscode.window.showWarningMessage("New name must be different"); 164 | reject("not_different"); 165 | return; 166 | } 167 | const newBase = val.trim(); 168 | const newPath = path.join(dir, newBase); 169 | console.log(`renaming: [${aPath}]->[${newPath}]`); 170 | fs.rename(aPath, newPath) 171 | .then(() => { 172 | return isFav ? this.favorites.updateWithPath(item.id, newPath) : Promise.resolve(); 173 | }).then(() => { 174 | resolve(); 175 | }).catch((e) => { 176 | console.log(e); 177 | console.error(e); 178 | vscode.window.showErrorMessage(`Error renaming ${base}`); 179 | reject(e); 180 | }); 181 | 182 | }); 183 | }); 184 | } 185 | public createFile(item: ViewItem) { 186 | return new Promise((resolve, reject) => { 187 | const aPath = item.value; 188 | let newPath: string = null; 189 | let newName: string = null; 190 | 191 | vscode.window.showInputBox({ 192 | prompt: "Enter file name", 193 | placeHolder: "Type file name", 194 | }).then((val) => { 195 | if (val == null || val.trim() === "") { 196 | newPath = null; 197 | resolve(); 198 | return; 199 | } 200 | newName = val; 201 | newPath = path.join(aPath, val); 202 | return fs.pathExists(newPath); 203 | }).then((b) => { 204 | if (newPath == null) { 205 | // do nothing 206 | return; 207 | } 208 | 209 | if (b === true) { 210 | vscode.window.showWarningMessage("File exists"); 211 | reject("exists"); 212 | return; 213 | } 214 | 215 | fs.createFile(newPath) 216 | .then((result) => { 217 | resolve(); 218 | }) 219 | .catch((e) => { 220 | vscode.window.showErrorMessage(`Error creating file ${newName}`); 221 | reject(e); 222 | }); 223 | 224 | }); 225 | 226 | }); 227 | } 228 | public createDirectory(item: ViewItem) { 229 | return new Promise((resolve, reject) => { 230 | const aPath = item.value; 231 | let newPath: string = null; 232 | let newName: string = null; 233 | 234 | vscode.window.showInputBox({ 235 | prompt: "Enter directory name", 236 | placeHolder: "Type directory name", 237 | }).then((val) => { 238 | if (val == null || val.trim() === "") { 239 | resolve(); 240 | return; 241 | } 242 | newName = val; 243 | newPath = path.join(aPath, val); 244 | return fs.pathExists(newPath); 245 | }).then((b) => { 246 | 247 | if (b === true) { 248 | vscode.window.showWarningMessage("Directory exists"); 249 | reject("exists"); 250 | return; 251 | } 252 | 253 | fs.ensureDir(newPath) 254 | .then((result) => { 255 | resolve(); 256 | }) 257 | .catch((e) => { 258 | vscode.window.showErrorMessage(`Error creating directory ${newName}`); 259 | reject(e); 260 | }); 261 | 262 | }); 263 | 264 | }); 265 | } 266 | public copy(clipboardItem: ViewItem, destination: ViewItem) { 267 | return new Promise((resolve, reject) => { 268 | const sPath = clipboardItem.value; 269 | const sBase = path.basename(clipboardItem.value); 270 | const dDir = destination.value; 271 | const dPath = path.join(dDir, sBase); 272 | 273 | Promise.all([ 274 | fs.pathExists(dPath), 275 | ]).then((results) => { 276 | 277 | const exists = results[0]; 278 | 279 | if (exists) { 280 | return vscode.window.showQuickPick( 281 | ["Yes", "No"], { 282 | placeHolder: `Destination exists! Overwrite? '${dPath}'`, 283 | }, 284 | ); 285 | } else { 286 | return Promise.resolve("Yes"); 287 | } 288 | 289 | }).then((shouldCopy: string) => { 290 | 291 | if (shouldCopy !== "Yes") { 292 | resolve(); 293 | return; 294 | } 295 | 296 | fs.copy(sPath, dPath, { 297 | overwrite: true, 298 | }).then((result) => { 299 | resolve(); 300 | }).catch((e) => { 301 | vscode.window.showErrorMessage(`Error copying ${sPath}`); 302 | reject(e); 303 | }); 304 | 305 | }); 306 | 307 | }); 308 | } 309 | public move(clipboardItem: ViewItem, destination: ViewItem) { 310 | 311 | return new Promise((resolve, reject) => { 312 | const isFav = clipboardItem.isFavorite; 313 | const sPath = clipboardItem.value; 314 | const sBase = path.basename(clipboardItem.value); 315 | const dDir = destination.value; 316 | const dPath = path.join(dDir, sBase); 317 | 318 | Promise.all([ 319 | fs.pathExists(dPath), 320 | ]).then((results) => { 321 | 322 | const exists = results[0]; 323 | 324 | if (exists) { 325 | return vscode.window.showQuickPick( 326 | ["Yes", "No"], { 327 | placeHolder: `Destination exists! Overwrite? '${dPath}'`, 328 | }, 329 | ); 330 | } else { 331 | return Promise.resolve("Yes"); 332 | } 333 | 334 | }).then((shouldMove: string) => { 335 | 336 | if (shouldMove !== "Yes") { 337 | resolve(); 338 | return; 339 | } 340 | 341 | fs.move(sPath, dPath, { 342 | overwrite: true, 343 | }).then((result) => { 344 | return isFav ? this.favorites.removeResource(clipboardItem.id) : Promise.resolve(); 345 | }).then((result) => { 346 | resolve(); 347 | }).catch((e) => { 348 | vscode.window.showErrorMessage(`Error moving ${sPath}`); 349 | reject(e); 350 | }); 351 | 352 | }); 353 | 354 | }); 355 | 356 | } 357 | 358 | } 359 | -------------------------------------------------------------------------------- /src/class/fs-watcher.ts: -------------------------------------------------------------------------------- 1 | import { fromEventPattern, merge, Subject } from "rxjs"; 2 | import { catchError, debounceTime, filter, map, takeUntil } from "rxjs/operators"; 3 | import * as vscode from "vscode"; 4 | import { Global } from "./global"; 5 | import { FavoriteStorage } from "./storage"; 6 | 7 | export interface FileSystemEvent { 8 | type: "change" | "create" | "delete"; 9 | fsPath: string; 10 | } 11 | 12 | export class FsWatcher { 13 | public eventFs = new Subject(); 14 | private watcher: vscode.FileSystemWatcher = null; 15 | constructor(private storage: FavoriteStorage) { 16 | 17 | this.watcher = vscode.workspace.createFileSystemWatcher("**/*.*", false, false, false); 18 | merge( 19 | fromEventPattern((f) => { 20 | return this.watcher.onDidCreate(f as any); 21 | }, (f: any, d: vscode.Disposable) => { 22 | d.dispose(); 23 | }).pipe(map((e) => { 24 | return { 25 | fsPath: e.fsPath, 26 | type: "create", 27 | } as FileSystemEvent; 28 | })), 29 | fromEventPattern((f) => { 30 | return this.watcher.onDidDelete(f as any); 31 | }, (f: any, d: vscode.Disposable) => { 32 | d.dispose(); 33 | }).pipe(map((e) => { 34 | return { 35 | fsPath: e.fsPath, 36 | type: "delete", 37 | } as FileSystemEvent; 38 | })), 39 | fromEventPattern((f) => { 40 | return this.watcher.onDidChange(f as any); 41 | }, (f: any, d: vscode.Disposable) => { 42 | d.dispose(); 43 | }).pipe(map((e) => { 44 | return { 45 | fsPath: e.fsPath, 46 | type: "change", 47 | } as FileSystemEvent; 48 | })), 49 | ).pipe( 50 | takeUntil(Global.eventDeactivate), 51 | filter((e) => e.fsPath !== this.storage.storageFilePath), 52 | debounceTime(1000), 53 | catchError((e, o) => o), 54 | ).subscribe((e) => { 55 | this.eventFs.next(); 56 | }); 57 | 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/class/global.ts: -------------------------------------------------------------------------------- 1 | import { Subject } from "rxjs"; 2 | import * as vscode from "vscode"; 3 | 4 | export class Global { 5 | public static eventDeactivate = new Subject(); 6 | public static statusRegistry: vscode.StatusBarItem = null; 7 | } 8 | -------------------------------------------------------------------------------- /src/class/group-color.ts: -------------------------------------------------------------------------------- 1 | import * as Color from "color"; 2 | import * as fs from "fs-extra"; 3 | import * as colors from "html-colors"; 4 | import * as md5 from "md5"; 5 | import * as path from "path"; 6 | import { ExtensionContext } from "vscode"; 7 | import { HtmlColor } from "../types"; 8 | import { Favorites } from "./favorites"; 9 | 10 | const STORAGE_DIRECTORY: string = "svg-generated"; 11 | 12 | export class GroupColor { 13 | public colorList: HtmlColor = colors.all(); 14 | public storagePath: string = null; 15 | constructor(private favorites: Favorites, private context: ExtensionContext) { 16 | this.storagePath = path.join(this.context.extensionPath, STORAGE_DIRECTORY); 17 | } 18 | public getIconPath(normalizedColor: string) { 19 | const ip = this.iconPath(normalizedColor); 20 | const exists = fs.pathExistsSync(ip); 21 | 22 | if (exists) { 23 | return ip; 24 | } 25 | const svg = this.build(normalizedColor); 26 | const dir = path.dirname(ip); 27 | fs.mkdirpSync(dir); 28 | fs.writeFileSync(ip, svg); 29 | return ip; 30 | 31 | } 32 | public iconPath(normalizedColor: string): string { 33 | const h = md5(normalizedColor); 34 | const p = path.join(this.storagePath, `icon_${h}.svg`); 35 | return p; 36 | } 37 | public setColor(id: string, colorDef: string) { 38 | return new Promise((resolve, reject) => { 39 | 40 | const isColor = this.isColor(colorDef); 41 | if (!isColor) { 42 | reject("invalid_color"); 43 | return; 44 | } 45 | 46 | const normColor = this.normalizeColor(colorDef); 47 | const iconPath = this.iconPath(normColor); 48 | const iconData = this.build(normColor); 49 | 50 | fs.outputFile(iconPath, iconData) 51 | .then(() => { 52 | return this.groupIconSet(id, normColor); 53 | }).then(() => { 54 | resolve(); 55 | }).catch((e) => { 56 | reject(e); 57 | }); 58 | }); 59 | } 60 | public isColor(colorDef: string): boolean { 61 | try { 62 | const c = Color(colorDef); 63 | const rgb = c.rgb().string(4); 64 | return true; 65 | } catch (e) { 66 | 67 | return false; 68 | } 69 | 70 | // colorDef.match(new RegExp(/#[0-9a-f]{6}/, "i")); 71 | } 72 | public normalizeColor(color: string): string { 73 | try { 74 | const c = Color(color); 75 | const rgb = c.rgb().string(4); 76 | return rgb; 77 | } catch (e) { 78 | return null; 79 | } 80 | } 81 | public removeColor(id: string) { 82 | return this.groupIconRemove(id); 83 | } 84 | private groupIconRemove(id: string) { 85 | return new Promise((resolve, reject) => { 86 | 87 | Promise.all([ 88 | this.favorites.get(), 89 | ]).then((results) => { 90 | const all = results[0]; 91 | const i = all.findIndex((d) => d.id === id); 92 | 93 | if (i < 0) { 94 | reject("no_item"); 95 | return; 96 | } 97 | 98 | delete all[i].iconColor; 99 | 100 | return this.favorites.save(all); 101 | 102 | }).then(() => { 103 | resolve(); 104 | }).catch((e) => { 105 | reject(e); 106 | }); 107 | 108 | }); 109 | } 110 | private groupIconSet(id: string, normalizedColor: string) { 111 | return new Promise((resolve, reject) => { 112 | 113 | Promise.all([ 114 | this.favorites.get(), 115 | ]).then((results) => { 116 | const all = results[0]; 117 | const i = all.findIndex((d) => d.id === id); 118 | 119 | if (i < 0) { 120 | reject("no_item"); 121 | return; 122 | } 123 | 124 | all[i].iconColor = normalizedColor; 125 | 126 | return this.favorites.save(all); 127 | 128 | }).then(() => { 129 | resolve(); 130 | }).catch((e) => { 131 | reject(e); 132 | }); 133 | 134 | }); 135 | } 136 | private build(fillColor: string) { 137 | const o = ` 138 | 139 | 142 | 143 | 147 | 151 | 152 | 153 | 154 | `; 155 | return o; 156 | } 157 | 158 | } 159 | -------------------------------------------------------------------------------- /src/class/storage.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs-extra"; 2 | import * as path from "path"; 3 | import * as vscode from "vscode"; 4 | 5 | import { from, Subject } from "rxjs"; 6 | import { concatMap, tap } from "rxjs/operators"; 7 | import { ResourceType, StoredResource } from "../types"; 8 | import workspace from "./workspace"; 9 | 10 | export class FavoriteStorage { 11 | public defaultRelativePath = ".favorites.json"; 12 | public storageFilePath: string = null; 13 | public eventChange = new Subject(); 14 | private eventDestroy = new Subject(); 15 | 16 | constructor(context: vscode.ExtensionContext) { 17 | 18 | this.reloadStoragePath(); 19 | 20 | } 21 | public reloadStoragePath() { 22 | const configRelativePath = workspace.get("storageFilePath"); 23 | const execRelativePath = configRelativePath || this.defaultRelativePath; 24 | this.storageFilePath = path.join(workspace.getConfigurationRoot(), execRelativePath); 25 | } 26 | public get(): Promise { 27 | return new Promise((resolve, reject) => { 28 | 29 | if (!fs.existsSync(this.storageFilePath)) { 30 | resolve([]); 31 | return; 32 | } 33 | 34 | from(fs.readJson(this.storageFilePath)).pipe( 35 | concatMap((result) => this.___fixItems(result)), 36 | ).subscribe((result) => { 37 | resolve(result); 38 | }, (e) => { 39 | console.log(e); 40 | resolve([] as StoredResource[]); 41 | }); 42 | 43 | }); 44 | } 45 | public storageName(fromPath?: string) { 46 | const sp = fromPath == null ? this.storageFilePath : fromPath; 47 | const e = path.extname(sp); 48 | const b = path.basename(sp); 49 | const rx = new RegExp(`${e}$`, "i"); 50 | const out = b.replace(rx, ""); 51 | 52 | return out; 53 | } 54 | public save(list: StoredResource[], triggerChange: boolean = true): Promise { 55 | return new Promise((resolve, reject) => { 56 | 57 | const data = !list ? [] : list; 58 | 59 | const dir = path.dirname(this.storageFilePath); 60 | 61 | from(fs.mkdirp(dir)).pipe( 62 | concatMap(() => fs.writeJson(this.storageFilePath, data, { 63 | spaces: 4, 64 | })), 65 | tap(() => { 66 | if (triggerChange) { 67 | this.eventChange.next(); 68 | } 69 | }), 70 | ).subscribe(() => { 71 | resolve(); 72 | }, (e) => { 73 | console.log(e); 74 | reject(e); 75 | }); 76 | 77 | }); 78 | } 79 | public destroy() { 80 | this.eventDestroy.next(); 81 | } 82 | private convertLegacy(list: StoredResource[]) { 83 | fs.writeJsonSync(this.storageFilePath, list, { 84 | spaces: 4, 85 | }); 86 | } 87 | private ___fixItems(list: StoredResource[]): Promise { 88 | return new Promise((resolve, reject) => { 89 | 90 | const result = list.map((item) => { 91 | 92 | const wRoot = workspace.workspaceRoot(item.name); 93 | const wPath = workspace.workspacePath(item.name); 94 | 95 | if (item.fsPath || item.type === ResourceType.Group) { 96 | item.workspaceRoot = null; 97 | item.workspacePath = null; 98 | return item; 99 | } 100 | if (wRoot && item.workspaceRoot == null && item.workspacePath == null && item.fsPath == null) { 101 | item.workspaceRoot = wRoot; 102 | item.workspacePath = wPath; 103 | return item; 104 | } 105 | if (!wRoot && item.workspaceRoot == null && item.workspacePath == null && item.fsPath == null) { 106 | item.fsPath = item.name; 107 | item.workspacePath = null; 108 | item.workspaceRoot = null; 109 | return item; 110 | } 111 | 112 | if (item.workspaceRoot == null && item.fsPath == null) { 113 | const itemFsPath = workspace.pathAbsolute(item.workspacePath || item.name); 114 | item.workspaceRoot = workspace.workspaceRoot(itemFsPath); 115 | item.workspacePath = workspace.workspacePath(itemFsPath); 116 | return item; 117 | 118 | } 119 | 120 | return item; 121 | 122 | }); 123 | 124 | this.save(result, false) 125 | .then(() => { 126 | resolve(result); 127 | }).catch((e) => { 128 | reject(e); 129 | }); 130 | 131 | }); 132 | } 133 | 134 | } 135 | -------------------------------------------------------------------------------- /src/class/tree.ts: -------------------------------------------------------------------------------- 1 | import { fromEventPattern, merge, Subject, zip } from "rxjs"; 2 | import { filter, takeUntil } from "rxjs/operators"; 3 | import * as vscode from "vscode"; 4 | import { ResourceType } from "../types/index"; 5 | import { DataProvider } from "./dataProvider"; 6 | import { Favorites } from "./favorites"; 7 | import { Global } from "./global"; 8 | import { ViewItem } from "./view-item"; 9 | import workspace from "./workspace"; 10 | 11 | export class TreeViewManager { 12 | public visible: boolean = false; 13 | public activeEditor: vscode.TextEditor = null; 14 | private eventVisibility = new Subject(); 15 | private eventActiveEditor = new Subject(); 16 | 17 | constructor( 18 | private treeView: vscode.TreeView, 19 | private context: vscode.ExtensionContext, 20 | private favorites: Favorites, 21 | private treeProvider: DataProvider, 22 | ) { 23 | 24 | this.visible = this.treeView.visible; 25 | this.activeEditor = vscode.window.activeTextEditor; 26 | 27 | fromEventPattern((f: (e: any) => any) => { 28 | return this.treeView.onDidChangeVisibility(f, null, context.subscriptions); 29 | }, (f: any, d: vscode.Disposable) => { 30 | d.dispose(); 31 | }).pipe( 32 | takeUntil(Global.eventDeactivate), 33 | ).subscribe((m) => { 34 | this.visible = m.visible; 35 | this.eventVisibility.next(m.visible); 36 | }); 37 | 38 | fromEventPattern((f: (e: any) => any) => { 39 | return vscode.window.onDidChangeActiveTextEditor(f, null, context.subscriptions); 40 | }, (f: any, d: vscode.Disposable) => { 41 | d.dispose(); 42 | }).pipe( 43 | takeUntil(Global.eventDeactivate), 44 | ).subscribe((m) => { 45 | this.activeEditor = m; 46 | this.eventActiveEditor.next(m); 47 | }); 48 | 49 | merge( 50 | this.eventActiveEditor.pipe(), 51 | this.eventVisibility.pipe(), 52 | ).pipe( 53 | takeUntil(Global.eventDeactivate), 54 | filter(() => { 55 | return ( 56 | this.activeEditor != null && 57 | this.activeEditor.document != null && 58 | this.activeEditor.document.uri.fsPath != null 59 | ); 60 | }), 61 | filter(() => this.visible === true), 62 | ).subscribe((editor) => { 63 | this.reveal(this.activeEditor.document.uri.fsPath); 64 | }); 65 | 66 | this.eventActiveEditor.next(this.activeEditor); 67 | } 68 | public reveal(fsPath: string) { 69 | 70 | if (workspace.excludeCheck.isExcluded(fsPath)) { 71 | return; 72 | } 73 | 74 | zip( 75 | this.favorites.identify(fsPath), 76 | this.favorites.stateList, 77 | this.favorites.isPartOfFavorites(fsPath), 78 | ).subscribe((result) => { 79 | const type = result[0]; 80 | const list = result[1]; 81 | const isPart = result[2]; 82 | 83 | // try to locate in favorite items! directly added 84 | const fileItem = list.find((f) => f.name === fsPath && f.type === ResourceType.File); 85 | 86 | // if file and in root! 87 | if (fileItem && type === ResourceType.File) { 88 | // it is file in root! 89 | const viewItem = this.favorites.asViewItem(fileItem); 90 | this.treeView.reveal(viewItem, { select: true, focus: false }); 91 | return; 92 | } 93 | 94 | if (isPart) { 95 | this.favorites.viewItemForPath(fsPath) 96 | .then((item) => { 97 | this.treeView.reveal(item, { select: true, focus: false }); 98 | }).catch((e) => { 99 | console.log(e); 100 | }); 101 | 102 | } 103 | 104 | }, (e) => { 105 | console.log(e); 106 | }); 107 | 108 | } 109 | 110 | } 111 | -------------------------------------------------------------------------------- /src/class/view-item.ts: -------------------------------------------------------------------------------- 1 | 2 | import * as path from "path"; 3 | import { take } from "rxjs/operators"; 4 | import * as vscode from "vscode"; 5 | import { ResourceType } from "../types/index"; 6 | import { Favorites } from "./favorites"; 7 | 8 | export type ViewItemContextValue = "FAVORITE_GROUP" | "FAVORITE_DIRECTORY" | "FAVORITE_FILE" | "FS_DIRECTORY" | "FS_FILE"; 9 | 10 | export class ViewItem extends vscode.TreeItem { 11 | public static favorites: Favorites; 12 | public static context: vscode.ExtensionContext; 13 | 14 | public resourceUri: vscode.Uri; 15 | public groupName: string; 16 | public parentViewItem: ViewItem = null; 17 | 18 | constructor( 19 | public label: string, 20 | public collapsibleState: vscode.TreeItemCollapsibleState, 21 | public value: string, 22 | public contextValue: ViewItemContextValue, 23 | public resourceName: string, 24 | public resourceType: ResourceType, 25 | public icon?: string | vscode.Uri | { light: string | vscode.Uri; dark: string | vscode.Uri }, 26 | public command?: vscode.Command, 27 | public id?: string, 28 | public parentId?: string, 29 | public tooltipText?: string, 30 | ) { 31 | super(label, collapsibleState); 32 | 33 | this.resourceUri = vscode.Uri.file(value); 34 | this.tooltip = value; 35 | this.iconPath = icon; 36 | this.tooltip = tooltipText; 37 | } 38 | public get isFavorite() { 39 | return this.contextValue === "FAVORITE_DIRECTORY" || this.contextValue === "FAVORITE_FILE"; 40 | } 41 | public getParent(): Promise { 42 | 43 | if ( 44 | this.contextValue === "FAVORITE_DIRECTORY" || 45 | this.contextValue === "FAVORITE_FILE" || 46 | this.contextValue === "FAVORITE_GROUP" 47 | ) { 48 | 49 | if (this.parentId == null) { 50 | return null; 51 | } 52 | 53 | return this.getParentForFavorite(this.parentId); 54 | 55 | } 56 | 57 | return new Promise((resolve, reject) => { 58 | 59 | ViewItem.favorites.get() 60 | .then((stored) => { 61 | 62 | this.getParentForFs(this.resourceUri.fsPath) 63 | .then((result) => { 64 | 65 | return Promise.all([ 66 | ViewItem.favorites.isPartOfFavorites(result.resourceUri.fsPath), 67 | result, 68 | ]); 69 | 70 | }).then((res) => { 71 | 72 | const check = res[0]; 73 | const item = res[1]; 74 | 75 | if (check) { 76 | resolve(item); 77 | } else { 78 | resolve(null); 79 | } 80 | 81 | }).catch((e) => { 82 | reject(e); 83 | }); 84 | 85 | }).catch((e) => { 86 | reject(e); 87 | }); 88 | 89 | }); 90 | } 91 | private getParentForFs(fsPath: string): Promise { 92 | return new Promise((resolve, reject) => { 93 | ViewItem.favorites.stateList.pipe( 94 | take(1), 95 | ).subscribe((list) => { 96 | 97 | const dir = path.dirname(fsPath); 98 | const sr = list.find((i) => i.name === dir && i.type === ResourceType.Directory); 99 | 100 | if (sr != null) { 101 | const vi = ViewItem.favorites.asViewItem(sr); 102 | resolve(vi); 103 | return; 104 | } 105 | 106 | const pvi = ViewItem.favorites.viewItemForPath(dir); 107 | resolve(pvi); 108 | 109 | }, (e) => { 110 | reject(e); 111 | }); 112 | }); 113 | } 114 | private getParentForFavorite(parentId: string): Promise { 115 | return new Promise((resolve, reject) => { 116 | ViewItem.favorites.stateList.pipe( 117 | take(1), 118 | ).subscribe((list) => { 119 | 120 | const p = list.find((item) => item.id === parentId); 121 | const vi = ViewItem.favorites.asViewItem(p); 122 | resolve(vi); 123 | }, (e) => { 124 | reject(e); 125 | }); 126 | }); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/class/workspace.ts: -------------------------------------------------------------------------------- 1 | import * as os from "os"; 2 | import * as path from "path"; 3 | import { fromEventPattern, Subject } from "rxjs"; 4 | import { takeUntil } from "rxjs/operators"; 5 | import * as vscode from "vscode"; 6 | import { WorkspaceConfiguration } from "../types/index"; 7 | import { ExcludeCheck } from "./exclude-check"; 8 | import { Global } from "./global"; 9 | 10 | export class Workspace { 11 | 12 | public eventConfigurationChange = new Subject(); 13 | public excludeCheck = new ExcludeCheck(this); 14 | 15 | public constructor() { 16 | 17 | fromEventPattern((f) => { 18 | return vscode.workspace.onDidChangeConfiguration(f as any); 19 | }, (f: any, d: vscode.Disposable) => { 20 | d.dispose(); 21 | }).pipe( 22 | takeUntil(Global.eventDeactivate), 23 | ).subscribe((e) => { 24 | this.excludeCheck = new ExcludeCheck(this); 25 | this.eventConfigurationChange.next(); 26 | }); 27 | } 28 | 29 | public get(key: T): WorkspaceConfiguration[T] { 30 | const config = vscode.workspace.getConfiguration("favorites"); 31 | return config.get(key) as T; 32 | } 33 | public getGlobal(key: string): any { 34 | const config = vscode.workspace.getConfiguration(); 35 | return config.get(key); 36 | } 37 | 38 | public save(key: T, value: WorkspaceConfiguration[T]): Promise { 39 | const config = vscode.workspace.getConfiguration("favorites"); 40 | 41 | config.update(key, value, false); 42 | return Promise.resolve(); 43 | 44 | } 45 | public getConfigurationRoot() { 46 | const workspaceIndex = this.get("useWorkspace"); 47 | const rootPath = vscode.workspace.workspaceFolders[workspaceIndex] != null 48 | ? vscode.workspace.workspaceFolders[workspaceIndex].uri.fsPath 49 | : vscode.workspace.workspaceFolders[0].uri.fsPath; 50 | 51 | return rootPath; 52 | } 53 | public getSingleRootPath(): string { 54 | return vscode.workspace.workspaceFolders[0].uri.fsPath; 55 | } 56 | 57 | public isMultiRootWorkspace(): boolean { 58 | return vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length > 1; 59 | } 60 | 61 | public pathResolve(filePath: string) { 62 | if (this.isMultiRootWorkspace()) { 63 | return filePath; 64 | } 65 | return path.resolve(vscode.workspace.workspaceFolders[0].uri.fsPath, filePath); 66 | } 67 | public pathAsUri(fsPath: string): vscode.Uri { 68 | let uri = vscode.Uri.parse(`file://${this.pathResolve(fsPath)}`); 69 | if (os.platform().startsWith("win")) { 70 | uri = vscode.Uri.parse(`file:///${this.pathResolve(fsPath)}`.replace(/\\/g, "/")); 71 | } 72 | 73 | return uri; 74 | } 75 | 76 | public pathForWorkspace(fsPath: string) { 77 | const isMultiRoot = this.isMultiRootWorkspace(); 78 | const wp = isMultiRoot ? fsPath : this.pathResolve(fsPath).substr(this.getSingleRootPath().length + 1); 79 | return wp; 80 | } 81 | public pathAbsolute(workspacePath: string) { 82 | if (this.isMultiRootWorkspace()) { 83 | return workspacePath; 84 | } 85 | return path.resolve(vscode.workspace.workspaceFolders[0].uri.fsPath, workspacePath); 86 | } 87 | // ----------------------------------------------------------------------- 88 | public workspaceRoot(fsPath): string { 89 | const roots = vscode.workspace.workspaceFolders.map((i) => i.uri.fsPath); 90 | for (let i = 0; i < roots.length; i++) { 91 | const p = roots[i]; 92 | const r = fsPath.search(p); 93 | if (r === 0) { 94 | return p; 95 | } 96 | } 97 | 98 | return null; 99 | 100 | } 101 | public workspacePath(fsPath): string { 102 | const w = this.workspaceRoot(fsPath); 103 | 104 | if (w) { 105 | return path.relative(w, fsPath); 106 | } 107 | 108 | return null; 109 | } 110 | 111 | } 112 | 113 | export default new Workspace(); 114 | -------------------------------------------------------------------------------- /src/command/index.ts: -------------------------------------------------------------------------------- 1 | import * as clipboardy from "clipboardy"; 2 | import * as path from "path"; 3 | import { of } from "rxjs"; 4 | import { concatMap } from "rxjs/operators"; 5 | import * as vscode from "vscode"; 6 | import { QuickPickItem } from "vscode"; 7 | import { Clipboard } from "../class/clipboard"; 8 | import { Favorites } from "../class/favorites"; 9 | import { FavoritesNavigator, FavoritesNavigatorQuickPickItem } from "../class/favorites-navigator"; 10 | import { FilesystemUtils } from "../class/filesystem"; 11 | import { GroupColor } from "../class/group-color"; 12 | import { FavoriteStorage } from "../class/storage"; 13 | import { ViewItem } from "../class/view-item"; 14 | import workspace from "../class/workspace"; 15 | import { RegistryQuickPickItem, ResourceType, TreeProviders, WorkspaceQuickPickItem } from "../types/index"; 16 | 17 | export class Commands { 18 | private clipboard = new Clipboard(); 19 | private filesystem: FilesystemUtils = null; 20 | private groupColor: GroupColor = null; 21 | 22 | constructor( 23 | private context: vscode.ExtensionContext, 24 | public providers: TreeProviders, 25 | private favorites: Favorites, 26 | private storage: FavoriteStorage, 27 | ) { 28 | 29 | this.filesystem = new FilesystemUtils(favorites); 30 | this.groupColor = new GroupColor(favorites, context); 31 | 32 | context.subscriptions.push(this.selectRegistryItem()); 33 | context.subscriptions.push(this.favoritesRefresh()); 34 | context.subscriptions.push(this.selectWorkspace()); 35 | context.subscriptions.push(this.addExternal()); 36 | context.subscriptions.push(this.addToFavorites()); 37 | context.subscriptions.push(this.addToFavoritesGroup()); 38 | context.subscriptions.push(this.addFileToFavorites()); 39 | context.subscriptions.push(this.addFileToFavoritesGroup()); 40 | context.subscriptions.push(this.deleteFavorite()); 41 | context.subscriptions.push(this.setSortAsc()); 42 | context.subscriptions.push(this.setSortDesc()); 43 | context.subscriptions.push(this.collapse()); 44 | context.subscriptions.push(this.collapseActivityView()); 45 | context.subscriptions.push(this.createGroup()); 46 | context.subscriptions.push(this.deleteGroup()); 47 | context.subscriptions.push(this.deleteAllFavorites()); 48 | context.subscriptions.push(this.groupRename()); 49 | context.subscriptions.push(this.aliasModify()); 50 | context.subscriptions.push(this.aliasRemove()); 51 | 52 | context.subscriptions.push(this.copyPath()); 53 | context.subscriptions.push(this.copyName()); 54 | context.subscriptions.push(this.fsCopy()); 55 | context.subscriptions.push(this.fsCut()); 56 | context.subscriptions.push(this.fsPaste()); 57 | context.subscriptions.push(this.fsCreateDirectory()); 58 | context.subscriptions.push(this.fsCreateFile()); 59 | context.subscriptions.push(this.fsDuplicate()); 60 | context.subscriptions.push(this.fsDelete()); 61 | context.subscriptions.push(this.fsRename()); 62 | context.subscriptions.push(this.groupColorSet()); 63 | context.subscriptions.push(this.favoritesBrowse()); 64 | } 65 | public favoritesBrowse = () => { 66 | return vscode.commands.registerCommand("favorites.browse", 67 | () => { 68 | 69 | Promise.all([]) 70 | .then(async () => { 71 | 72 | const navigator = new FavoritesNavigator(this.favorites); 73 | const editor = vscode.window.activeTextEditor; 74 | const editorFsPath = editor ? path.dirname(editor.document.uri.fsPath) : null; 75 | const isPart = await this.favorites.isPartOfFavorites(editorFsPath); 76 | const item = isPart ? await this.favorites.viewItemForPath(editorFsPath) : null; 77 | const viewColumn = editor ? editor.viewColumn : null; 78 | 79 | const display = (startItem: ViewItem) => { 80 | 81 | of(true).pipe( 82 | concatMap(() => navigator.build(startItem)), 83 | ).subscribe((list) => { 84 | vscode.window.showQuickPick(list, { 85 | canPickMany: false, 86 | placeHolder: "Type to filter", 87 | }).then((result) => { 88 | if (!result) { 89 | return; 90 | } 91 | 92 | if (result.upLevel) { 93 | display(result.parent); 94 | return; 95 | } 96 | 97 | const selItem = result.viewItem; 98 | switch (selItem.resourceType) { 99 | case ResourceType.Group: 100 | display(selItem); 101 | break; 102 | case ResourceType.Directory: 103 | display(selItem); 104 | break; 105 | case ResourceType.File: 106 | vscode.window.showTextDocument(selItem.resourceUri, { 107 | viewColumn, 108 | }).then(() => { 109 | 110 | }, (e) => { 111 | console.log(e); 112 | }); 113 | break; 114 | } 115 | 116 | }); 117 | }); 118 | 119 | }; 120 | 121 | display(item); 122 | 123 | }).catch((e) => { 124 | console.log(e); 125 | }); 126 | 127 | }); 128 | } 129 | public favoritesRefresh = () => { 130 | return vscode.commands.registerCommand("favorites.refresh", 131 | () => { 132 | this.providers.refresh(); 133 | }); 134 | } 135 | public selectRegistryItem = () => { 136 | return vscode.commands.registerCommand("favorites.selectFromRegistry", 137 | () => { 138 | 139 | const list: RegistryQuickPickItem[] = workspace.get("storageRegistry").map((p, i) => { 140 | const out: RegistryQuickPickItem = { 141 | index: i, 142 | label: `${this.storage.storageName(p)}`, 143 | description: `${p}`, 144 | relativePath: p, 145 | 146 | }; 147 | return out; 148 | }); 149 | 150 | vscode.window.showQuickPick(list, { 151 | matchOnDescription: true, 152 | matchOnDetail: true, 153 | placeHolder: "Select alternative storage from registry", 154 | }).then((result) => { 155 | if (!result) { 156 | return; 157 | } 158 | 159 | workspace.save("storageFilePath", result.relativePath); 160 | return this.providers.refresh(); 161 | }).then(() => { 162 | console.log("Active storageFilePath changed"); 163 | }); 164 | 165 | }); 166 | } 167 | public selectWorkspace = () => { 168 | return vscode.commands.registerCommand("favorites.selectWorkspace", 169 | () => { 170 | 171 | const list: WorkspaceQuickPickItem[] = vscode.workspace.workspaceFolders.map((wf, i) => { 172 | const out: WorkspaceQuickPickItem = { 173 | index: i, 174 | label: `${wf.uri.fsPath}`, 175 | 176 | }; 177 | return out; 178 | }); 179 | 180 | vscode.window.showQuickPick(list, { 181 | matchOnDescription: true, 182 | matchOnDetail: true, 183 | placeHolder: "Select workspace folder for Favorites", 184 | }).then((result) => { 185 | if (!result) { 186 | return; 187 | } 188 | 189 | return workspace.save("useWorkspace", result.index); 190 | }).then(() => { 191 | console.log("Active folder changed"); 192 | }); 193 | 194 | }); 195 | } 196 | 197 | public copyPath = () => { 198 | return vscode.commands.registerCommand("favorites.copy.path", 199 | (value: ViewItem) => { 200 | if (value == null) { 201 | return; 202 | } 203 | const fsPath = value.resourceUri.fsPath; 204 | console.log(path); 205 | try { 206 | clipboardy.writeSync(fsPath); 207 | } catch (e) { 208 | console.log(e); 209 | } 210 | 211 | }); 212 | } 213 | public copyName = () => { 214 | return vscode.commands.registerCommand("favorites.copy.name", 215 | (value: ViewItem) => { 216 | if (value == null) { 217 | return; 218 | } 219 | const name = path.basename(value.resourceUri.fsPath); 220 | console.log(path); 221 | clipboardy.writeSync(name); 222 | 223 | }); 224 | } 225 | public addExternal = () => { 226 | // 227 | return vscode.commands.registerCommand("favorites.addExternal", 228 | (value: ViewItem) => { 229 | console.log("favorites.addExternal"); 230 | console.log(value); 231 | const shouldExit = (value == null || value.contextValue === "FAVORITE_GROUP") ? false : true; 232 | 233 | if (shouldExit) { 234 | return; 235 | } 236 | 237 | const parentId = value == null ? null : value.id; 238 | 239 | Promise.all([vscode.window.showInputBox({ 240 | prompt: "New external resource", 241 | placeHolder: "Enter file or directory path", 242 | })]).then((args) => { 243 | 244 | const pathToAdd = args[0]; 245 | 246 | if (pathToAdd == null || pathToAdd.trim() === "") { 247 | // vscode.window.showWarningMessage("Invalid path"); 248 | return; 249 | } 250 | 251 | return this.favorites.addExternalPathToGroup(parentId, pathToAdd); 252 | 253 | }).catch((e) => { 254 | vscode.window.showErrorMessage(e); 255 | }); 256 | 257 | }); 258 | } 259 | 260 | public groupColorSet = () => { 261 | return vscode.commands.registerCommand("favorites.group.color.set", 262 | (value: ViewItem) => { 263 | 264 | const list: QuickPickItem[] = [ 265 | { 266 | label: "None", 267 | description: "Remove color from group", 268 | }, 269 | { 270 | label: "Custom", 271 | description: "Use custom color (hex or rgb/rgba)", 272 | }, 273 | ]; 274 | 275 | const colors: QuickPickItem[] = Object.keys(this.groupColor.colorList).map((k) => { 276 | const o: QuickPickItem = { 277 | label: k, 278 | description: this.groupColor.colorList[k], 279 | }; 280 | return o; 281 | }); 282 | const all = list.concat(colors); 283 | 284 | vscode.window.showQuickPick(all, { 285 | matchOnDescription: true, 286 | matchOnDetail: true, 287 | }).then((result) => { 288 | 289 | if (result == null) { 290 | return Promise.resolve(null); 291 | } 292 | if (result.label === "None") { 293 | return this.groupColor.removeColor(value.id); 294 | } 295 | if (result.label === "Custom") { 296 | return vscode.window.showInputBox({ 297 | prompt: "New group color", 298 | placeHolder: "Enter hex or rgb/rgba value", 299 | }); 300 | } 301 | // then desc = #hex 302 | return Promise.resolve(result.description); 303 | 304 | }).then((input: string) => { 305 | if (input == null) { 306 | // no action 307 | return Promise.resolve(); 308 | } 309 | 310 | return this.groupColor.setColor(value.id, input); 311 | }).then(() => { 312 | console.log("all ok"); 313 | }); 314 | }); 315 | } 316 | public fsCreateFile = () => { 317 | return vscode.commands.registerCommand("filesystem.create.file", 318 | (value: ViewItem) => { 319 | if (value == null) { 320 | return; 321 | } 322 | this.filesystem.createFile(value) 323 | .then((result) => { 324 | this.providers.refresh(); 325 | }) 326 | .catch((e) => { 327 | this.providers.refresh(); 328 | console.log(e); 329 | }); 330 | }); 331 | } 332 | public fsCreateDirectory = () => { 333 | return vscode.commands.registerCommand("filesystem.create.directory", 334 | (value: ViewItem) => { 335 | this.filesystem.createDirectory(value) 336 | .then((result) => { 337 | this.providers.refresh(); 338 | }) 339 | .catch((e) => { 340 | this.providers.refresh(); 341 | console.log(e); 342 | }); 343 | }); 344 | } 345 | public fsCopy = () => { 346 | return vscode.commands.registerCommand("filesystem.copy", 347 | // tslint:disable-next-line:no-empty 348 | (value: ViewItem) => { 349 | this.clipboard.copy(value); 350 | }); 351 | } 352 | public fsCut = () => { 353 | return vscode.commands.registerCommand("filesystem.cut", 354 | // tslint:disable-next-line:no-empty 355 | (value: ViewItem) => { 356 | this.clipboard.cut(value); 357 | }); 358 | } 359 | public fsPaste = () => { 360 | return vscode.commands.registerCommand("filesystem.paste", 361 | // tslint:disable-next-line:no-empty 362 | (value: ViewItem) => { 363 | const state = this.clipboard.get(); 364 | if (state == null) { 365 | return; 366 | } 367 | switch (state.operation) { 368 | case "copy": 369 | this.filesystem.copy(state.item, value) 370 | .then((result) => { 371 | this.providers.refresh(); 372 | // this.clipboard.reset(); 373 | }) 374 | .catch((e) => { 375 | console.log(e); 376 | }); 377 | break; 378 | case "cut": 379 | this.filesystem.move(state.item, value) 380 | .then((result) => { 381 | this.providers.refresh(); 382 | this.clipboard.reset(); 383 | }) 384 | .catch((e) => { 385 | console.log(e); 386 | }); 387 | break; 388 | } 389 | 390 | }); 391 | } 392 | public fsDelete = () => { 393 | return vscode.commands.registerCommand("filesystem.delete", 394 | // tslint:disable-next-line:no-empty 395 | (value: ViewItem) => { 396 | this.filesystem.delete(value) 397 | .then((result) => { 398 | this.providers.refresh(); 399 | }) 400 | .catch((e) => { 401 | this.providers.refresh(); 402 | console.log(e); 403 | }); 404 | }); 405 | } 406 | public fsRename = () => { 407 | return vscode.commands.registerCommand("filesystem.rename", 408 | // tslint:disable-next-line:no-empty 409 | (value: ViewItem) => { 410 | this.filesystem.rename(value) 411 | .then((result) => { 412 | this.providers.refresh(); 413 | }) 414 | .catch((e) => { 415 | this.providers.refresh(); 416 | console.log(e); 417 | }); 418 | }); 419 | } 420 | public fsDuplicate = () => { 421 | return vscode.commands.registerCommand("filesystem.duplicate", 422 | // tslint:disable-next-line:no-empty 423 | (value: ViewItem) => { 424 | this.filesystem.duplicate(value) 425 | .then((result) => { 426 | this.providers.refresh(); 427 | }) 428 | .catch((e) => { 429 | this.providers.refresh(); 430 | console.log(e); 431 | }); 432 | }); 433 | } 434 | public aliasRemove = () => { 435 | return vscode.commands.registerCommand("favorites.alias.remove", 436 | (value: ViewItem) => { 437 | 438 | this.favorites.labelModify(value.id, null) 439 | .then((result) => { 440 | this.providers.refresh(); 441 | }) 442 | .catch((e) => { 443 | console.log(e); 444 | }); 445 | 446 | }); 447 | 448 | } 449 | public aliasModify = () => { 450 | return vscode.commands.registerCommand("favorites.alias.modify", 451 | (value: ViewItem) => { 452 | 453 | this.favorites.get() 454 | .then((result) => { 455 | const item = result.find((r) => r.id === value.id); 456 | const oldVal = (item != null && item.label != null) ? item.label : ""; 457 | vscode.window.showInputBox({ prompt: "Enter alias", value: oldVal }) 458 | .then((name) => { 459 | if (!name || name.trim().length === 0) { 460 | return; 461 | } 462 | const tname = name.trim(); 463 | this.favorites.labelModify(value.id, tname); 464 | this.providers.refresh(); 465 | }); 466 | 467 | }) 468 | .catch((e) => { 469 | console.log(e); 470 | }); 471 | 472 | }); 473 | 474 | } 475 | public groupRename = () => { 476 | return vscode.commands.registerCommand("favorites.group.rename", 477 | (value: ViewItem) => { 478 | vscode.window.showInputBox({ prompt: "Enter new group name" }) 479 | .then((name) => { 480 | if (!name || name.trim().length === 0) { 481 | return; 482 | } 483 | const tname = name.trim(); 484 | this.favorites.groupRename(value.id, tname); 485 | this.providers.refresh(); 486 | }); 487 | 488 | }); 489 | 490 | } 491 | addToFavorites = () => { 492 | return vscode.commands.registerCommand("favorites.addToFavorites", (fileUri: vscode.Uri, list: any[]) => { 493 | 494 | const isList = Array.isArray(list); 495 | const isFile = (fileUri && fileUri.fsPath) != null ? true : false; 496 | const isActiveEditor = vscode.window.activeTextEditor != null ? true : false; 497 | 498 | if (!isList && isFile) { 499 | list = [fileUri]; 500 | } 501 | if (!isList && !isFile && isActiveEditor) { 502 | list = [vscode.window.activeTextEditor.document.uri]; 503 | } 504 | 505 | const run = async () => { 506 | 507 | for (const uri of list) { 508 | const itemPath = uri.fsPath; 509 | const workspacePath = workspace.workspaceRoot(itemPath); 510 | if (workspacePath) { 511 | await this.favorites.addPathToGroup(null, itemPath); 512 | } else { 513 | await this.favorites.addExternalPathToGroup(null, itemPath); 514 | } 515 | } 516 | }; 517 | 518 | run(); 519 | 520 | }); 521 | } 522 | addToFavoritesGroup = () => { 523 | return vscode.commands.registerCommand("favorites.addToFavoritesGroup", (fileUri: vscode.Uri, list: any[]) => { 524 | 525 | const isList = Array.isArray(list); 526 | const isFile = (fileUri && fileUri.fsPath) != null ? true : false; 527 | const isActiveEditor = vscode.window.activeTextEditor != null ? true : false; 528 | 529 | if (!isList && isFile) { 530 | list = [fileUri]; 531 | } 532 | if (!isList && !isFile && isActiveEditor) { 533 | list = [vscode.window.activeTextEditor.document.uri]; 534 | } 535 | 536 | const run = async (group_id: string) => { 537 | 538 | for (const uri of list) { 539 | const itemPath = uri.fsPath; 540 | const workspacePath = workspace.workspaceRoot(itemPath); 541 | 542 | if (workspacePath) { 543 | await this.favorites.addPathToGroup(group_id, itemPath); 544 | } else { 545 | await this.favorites.addExternalPathToGroup(group_id, itemPath); 546 | } 547 | } 548 | }; 549 | 550 | this.favorites.generateGroupQuickPickList() 551 | .then((result) => { 552 | 553 | if (result.length === 0) { 554 | vscode.window.showWarningMessage("No group definition found. Create group first."); 555 | return; 556 | } 557 | vscode.window.showQuickPick(result) 558 | .then((pickedItem) => { 559 | if (pickedItem == null) { 560 | // canceled 561 | return; 562 | } 563 | run(pickedItem.id); 564 | }); 565 | 566 | }) 567 | .catch((e) => { 568 | console.log(e); 569 | }); 570 | 571 | }); 572 | } 573 | 574 | addFileToFavorites = () => { 575 | // Note implementation is duplicated verbatim from addToFavorites. TODO: figure out how to call same code to prevent needless code duplication. 576 | return vscode.commands.registerCommand("favorites.addFileToFavorites", (fileUri: vscode.Uri, list: any[]) => { 577 | 578 | const isList = Array.isArray(list); 579 | const isFile = (fileUri && fileUri.fsPath) != null ? true : false; 580 | const isActiveEditor = vscode.window.activeTextEditor != null ? true : false; 581 | 582 | if (!isList && isFile) { 583 | list = [fileUri]; 584 | } 585 | if (!isList && !isFile && isActiveEditor) { 586 | list = [vscode.window.activeTextEditor.document.uri]; 587 | } 588 | 589 | const run = async () => { 590 | 591 | for (const uri of list) { 592 | const itemPath = uri.fsPath; 593 | const workspacePath = workspace.workspaceRoot(itemPath); 594 | if (workspacePath) { 595 | await this.favorites.addPathToGroup(null, itemPath); 596 | } else { 597 | await this.favorites.addExternalPathToGroup(null, itemPath); 598 | } 599 | } 600 | }; 601 | 602 | run(); 603 | 604 | }); 605 | } 606 | addFileToFavoritesGroup = () => { 607 | // Note implementation is duplicated verbatim from addToFavoritesGroup. TODO: figure out how to call same code to prevent needless code duplication. 608 | return vscode.commands.registerCommand("favorites.addFileToFavoritesGroup", (fileUri: vscode.Uri, list: any[]) => { 609 | 610 | const isList = Array.isArray(list); 611 | const isFile = (fileUri && fileUri.fsPath) != null ? true : false; 612 | const isActiveEditor = vscode.window.activeTextEditor != null ? true : false; 613 | 614 | if (!isList && isFile) { 615 | list = [fileUri]; 616 | } 617 | if (!isList && !isFile && isActiveEditor) { 618 | list = [vscode.window.activeTextEditor.document.uri]; 619 | } 620 | 621 | const run = async (group_id: string) => { 622 | 623 | for (const uri of list) { 624 | const itemPath = uri.fsPath; 625 | const workspacePath = workspace.workspaceRoot(itemPath); 626 | 627 | if (workspacePath) { 628 | await this.favorites.addPathToGroup(group_id, itemPath); 629 | } else { 630 | await this.favorites.addExternalPathToGroup(group_id, itemPath); 631 | } 632 | } 633 | }; 634 | 635 | this.favorites.generateGroupQuickPickList() 636 | .then((result) => { 637 | 638 | if (result.length === 0) { 639 | vscode.window.showWarningMessage("No group definition found. Create group first."); 640 | return; 641 | } 642 | vscode.window.showQuickPick(result) 643 | .then((pickedItem) => { 644 | if (pickedItem == null) { 645 | // canceled 646 | return; 647 | } 648 | run(pickedItem.id); 649 | }); 650 | 651 | }) 652 | .catch((e) => { 653 | console.log(e); 654 | }); 655 | 656 | }); 657 | } 658 | collapse = () => { 659 | return vscode.commands.registerCommand("favorites.collapse", (v) => { 660 | 661 | this.providers.explorer.returnEmpty = true; 662 | this.providers.refresh(); 663 | 664 | setTimeout(() => { 665 | this.providers.explorer.returnEmpty = false; 666 | this.providers.refresh(); 667 | 668 | }, 400); 669 | 670 | console.log(v); 671 | 672 | }); 673 | } 674 | collapseActivityView = () => { 675 | return vscode.commands.registerCommand("favorites.collapse.activity", (v) => { 676 | 677 | this.providers.activity.returnEmpty = true; 678 | this.providers.activity.refresh(); 679 | 680 | setTimeout(() => { 681 | this.providers.activity.returnEmpty = false; 682 | this.providers.activity.refresh(); 683 | 684 | }, 400); 685 | 686 | console.log(v); 687 | 688 | }); 689 | } 690 | deleteFavorite = () => { 691 | return vscode.commands.registerCommand("favorites.deleteFavorite", 692 | (value: ViewItem) => { 693 | 694 | this.favorites.removeResource(value.id); 695 | this.providers.refresh(); 696 | }); 697 | } 698 | setSortAsc = () => { 699 | return vscode.commands.registerCommand("favorites.nav.sort.az", (value: ViewItem) => { 700 | const config = vscode.workspace.getConfiguration("favorites"); 701 | 702 | vscode.commands.executeCommand("setContext", "sort", "ASC"); 703 | return config.update("sortDirection", "ASC", false); 704 | 705 | }); 706 | } 707 | setSortDesc = () => { 708 | return vscode.commands.registerCommand("favorites.nav.sort.za", (value: ViewItem) => { 709 | const config = vscode.workspace.getConfiguration("favorites"); 710 | 711 | vscode.commands.executeCommand("setContext", "sort", "DESC"); 712 | config.update("sortDirection", "DESC", false); 713 | 714 | }); 715 | } 716 | createGroup = () => { 717 | return vscode.commands.registerCommand("favorites.group.create", (value: ViewItem) => { 718 | 719 | vscode.window.showInputBox({ prompt: "Enter group name" }).then((name) => { 720 | console.log(name); 721 | if (!name || name.trim().length === 0) { 722 | return; 723 | } 724 | const tname = name.trim(); 725 | const parentId = value == null ? null : value.id; 726 | this.favorites.addGroup(parentId, tname); 727 | 728 | }); 729 | }); 730 | } 731 | deleteGroup = () => { 732 | return vscode.commands.registerCommand("favorites.group.delete", 733 | (value: ViewItem) => { 734 | this.favorites.removeResource(value.id); 735 | }); 736 | } 737 | deleteAllFavorites = () => { 738 | return vscode.commands.registerCommand("favorites.delete.all", (value: any) => { 739 | 740 | vscode.window.showInputBox({ 741 | prompt: "Do you want to remove ALL favorites (including groups)?", 742 | placeHolder: "type 'yes' to remove everything", 743 | }) 744 | .then((val) => { 745 | if (val === "yes") { 746 | this.storage.save([]) 747 | .then(() => { 748 | vscode.window.showInformationMessage(`All favorites are removed`); 749 | 750 | }).catch((e) => { 751 | console.log(e); 752 | }); 753 | 754 | } 755 | }); 756 | // 757 | 758 | }); 759 | } 760 | } 761 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { merge } from "rxjs"; 2 | import { takeUntil, tap } from "rxjs/operators"; 3 | import * as vscode from "vscode"; 4 | import { DataProvider } from "./class/dataProvider"; 5 | import { Favorites } from "./class/favorites"; 6 | import { FsWatcher } from "./class/fs-watcher"; 7 | import { Global } from "./class/global"; 8 | import { FavoriteStorage } from "./class/storage"; 9 | import { TreeViewManager } from "./class/tree"; 10 | import { ViewItem } from "./class/view-item"; 11 | import workspace from "./class/workspace"; 12 | import { Commands } from "./command"; 13 | import { TreeProviders } from "./types"; 14 | 15 | export function activate(context: vscode.ExtensionContext) { 16 | 17 | if (vscode.workspace.workspaceFolders == null || vscode.workspace.workspaceFolders.length === 0) { 18 | return; 19 | } 20 | 21 | const configSort = workspace.get("sortDirection"); 22 | let registryList = workspace.get("storageRegistry"); 23 | const sort = (configSort === "DESC" || configSort === "ASC") ? configSort : "ASC"; 24 | 25 | vscode.commands.executeCommand("setContext", "sort", sort); 26 | 27 | const storage = new FavoriteStorage(context); 28 | const favorites = new Favorites(context, storage); 29 | 30 | ViewItem.favorites = favorites; 31 | 32 | const provider = new DataProvider(favorites); 33 | const providerActivity = new DataProvider(favorites); 34 | 35 | const providers: TreeProviders = { 36 | explorer: provider, 37 | activity: providerActivity, 38 | refresh: () => { 39 | provider.refresh(); 40 | providerActivity.refresh(); 41 | }, 42 | }; 43 | 44 | const treeExplorer = vscode.window.createTreeView("favorites", { treeDataProvider: providers.explorer }); 45 | const treeActivity = vscode.window.createTreeView("favoritesActivity", { treeDataProvider: providers.activity }); 46 | 47 | const managerExplorer = new TreeViewManager(treeExplorer, context, favorites, providers.explorer); 48 | const managerActivity = new TreeViewManager(treeActivity, context, favorites, providers.activity); 49 | const fsWatcher = new FsWatcher(storage); 50 | 51 | const commands = new Commands(context, providers, favorites, storage); 52 | 53 | if (registryList.length > 0) { 54 | statusRegistryCreate(); 55 | refreshStatus(storage); 56 | } 57 | 58 | workspace.eventConfigurationChange.pipe( 59 | takeUntil(Global.eventDeactivate), 60 | tap(() => { 61 | registryList = workspace.get("storageRegistry"); 62 | if (registryList.length > 0) { 63 | statusRegistryCreate(); 64 | } else { 65 | statusRegistryDestroy(); 66 | } 67 | }), 68 | ).subscribe(() => { 69 | providers.refresh(); 70 | storage.reloadStoragePath(); 71 | refreshStatus(storage); 72 | }); 73 | 74 | merge(storage.eventChange, fsWatcher.eventFs).pipe( 75 | takeUntil(Global.eventDeactivate), 76 | ).subscribe(() => { 77 | providers.refresh(); 78 | }); 79 | 80 | } 81 | 82 | function statusRegistryCreate() { 83 | 84 | const status = !Global.statusRegistry ? vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 1) : Global.statusRegistry; 85 | Global.statusRegistry = status; 86 | 87 | status.command = "favorites.selectFromRegistry"; 88 | status.show(); 89 | } 90 | function statusRegistryDestroy() { 91 | 92 | const status = Global.statusRegistry; 93 | 94 | if (!status) { 95 | return; 96 | } 97 | 98 | status.hide(); 99 | status.dispose(); 100 | Global.statusRegistry = null; 101 | } 102 | 103 | function refreshStatus(storage: FavoriteStorage) { 104 | const status = Global.statusRegistry; 105 | 106 | if (!status) { 107 | return; 108 | } 109 | 110 | const name = storage.storageName(); 111 | status.text = `$(heart) ${name}`; 112 | status.tooltip = `${storage.storageFilePath}`; 113 | } 114 | 115 | export function deactivate() { 116 | Global.eventDeactivate.next(); 117 | } 118 | -------------------------------------------------------------------------------- /src/types/index.ts: -------------------------------------------------------------------------------- 1 | import { QuickPickItem } from "vscode"; 2 | import { DataProvider } from "../class/dataProvider"; 3 | import { ViewItem } from "../class/view-item"; 4 | 5 | export enum ResourceType { 6 | File = "File", 7 | Group = "Group", 8 | Directory = "Directory", 9 | } 10 | 11 | export interface WorkspaceQuickPickItem extends QuickPickItem { 12 | index: number; 13 | } 14 | export interface RegistryQuickPickItem extends QuickPickItem { 15 | index: number; 16 | relativePath: string; 17 | } 18 | 19 | export interface WorkspaceConfiguration { 20 | useTrash: boolean; 21 | useWorkspace: number; 22 | useFilesExclude: boolean; 23 | storageFilePath: string; 24 | storageRegistry: string[]; 25 | groupsFirst: boolean; 26 | sortDirection: "ASC" | "DESC"; 27 | includeInDocumentBodyContextMenu: boolean; 28 | includeInEditorTabContextMenu: boolean; 29 | } 30 | 31 | export interface StoredResource { 32 | id?: string; 33 | name: string; 34 | type: ResourceType; 35 | parent_id?: string; 36 | label?: string; 37 | workspaceRoot: string; 38 | workspacePath: string; 39 | fsPath?: string; 40 | iconColor?: string; 41 | } 42 | 43 | export interface FilesystemResource { 44 | path: string; 45 | type: ResourceType; 46 | } 47 | 48 | export interface GroupQuickPick extends QuickPickItem { 49 | id: string; 50 | } 51 | 52 | export interface TreeProviders { 53 | explorer: DataProvider; 54 | activity: DataProvider; 55 | refresh: () => void; 56 | } 57 | 58 | export interface ClipboardBuffer { 59 | item: ViewItem; 60 | operation: "copy" | "cut"; 61 | } 62 | 63 | export interface HtmlColor { 64 | [key: string]: string; 65 | } 66 | -------------------------------------------------------------------------------- /src/types/trash.d.ts: -------------------------------------------------------------------------------- 1 | 2 | // declare module "renderjson" { 3 | 4 | // export default function (data: any); 5 | // export function set_icons(collapse: string, uncollapse: string); 6 | // // set_max_string_length: function() 7 | // // set_property_list: function() 8 | // // set_replacer: function() 9 | // // set_show_by_default: function() 10 | // // set_show_to_level: function() 11 | // // set_sort_objects: function() 12 | 13 | // } 14 | 15 | declare module "trash" { 16 | interface TrashOptions { 17 | glob: boolean; 18 | } 19 | function trash (iterable: Iterable, opts?: TrashOptions): Promise; 20 | export = trash; 21 | } 22 | 23 | 24 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "outDir": "./out", 5 | "moduleResolution": "node", 6 | "module": "commonjs", 7 | "emitDecoratorMetadata": true, 8 | "experimentalDecorators": true, 9 | "allowSyntheticDefaultImports": true, 10 | "allowJs": true, 11 | "lib": [ 12 | "es2017" 13 | ], 14 | "sourceMap": true, 15 | "rootDir": "src", 16 | "typeRoots": [ 17 | "./node_modules/@types" 18 | ] 19 | }, 20 | "exclude": [ 21 | "node_modules", 22 | "out", 23 | "packed", 24 | "webpack.config.js" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint:recommended", 3 | "rules": { 4 | "directive-selector": [ 5 | false, 6 | "attribute", 7 | "foo", 8 | "camelCase" 9 | ], 10 | "component-selector": [ 11 | false, 12 | "element", 13 | "foo", 14 | "kebab-case" 15 | ], 16 | "quotemark": [ 17 | true, 18 | "double" 19 | ], 20 | "no-console": [ 21 | false, 22 | "debug", 23 | "info", 24 | "time", 25 | "timeEnd", 26 | "trace" 27 | ], 28 | "no-inferrable-types": [ 29 | false, 30 | "ignore-params" 31 | ], 32 | "no-input-rename": false, 33 | "import-spacing": false, 34 | "no-output-on-prefix": false, 35 | "no-output-rename": false, 36 | "object-literal-sort-keys": false, 37 | "max-line-length": [ 38 | true, 39 | 140 40 | ], 41 | "interface-name": false, 42 | "angular-whitespace": [ 43 | false 44 | ], 45 | "no-consecutive-blank-lines": true, 46 | "member-access": false, 47 | "variable-name": false, 48 | "semicolon": [ 49 | true, 50 | "always" 51 | ], 52 | "no-empty":false, 53 | "prefer-for-of":false 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | 2 | /*--------------------------------------------------------------------------------------------- 3 | * Copyright (c) Microsoft Corporation. All rights reserved. 4 | * Licensed under the MIT License. See License.txt in the project root for license information. 5 | *--------------------------------------------------------------------------------------------*/ 6 | 7 | //@ts-check 8 | 9 | 'use strict'; 10 | 11 | const path = require('path'); 12 | 13 | /**@type {import('webpack').Configuration}*/ 14 | const config = { 15 | target: 'node', // vscode extensions run in a Node.js-context 📖 -> https://webpack.js.org/configuration/node/ 16 | 17 | entry: './src/index.ts', // the entry point of this extension, 📖 -> https://webpack.js.org/configuration/entry-context/ 18 | output: { // the bundle is stored in the 'dist' folder (check package.json), 📖 -> https://webpack.js.org/configuration/output/ 19 | path: path.resolve(__dirname, 'packed'), 20 | filename: 'index.js', 21 | libraryTarget: "commonjs2", 22 | devtoolModuleFilenameTemplate: "../[resource-path]", 23 | }, 24 | devtool: 'source-map', 25 | externals: { 26 | vscode: "commonjs vscode", // the vscode-module is created on-the-fly and must be excluded. Add other modules that cannot be webpack'ed, 📖 -> https://webpack.js.org/configuration/externals/ 27 | clipboardy: "clipboardy" 28 | }, 29 | resolve: { // support reading TypeScript and JavaScript files, 📖 -> https://github.com/TypeStrong/ts-loader 30 | extensions: ['.ts', '.js'] 31 | }, 32 | module: { 33 | rules: [{ 34 | test: /\.ts$/, 35 | exclude: /node_modules/, 36 | use: [{ 37 | loader: 'ts-loader', 38 | }] 39 | }] 40 | }, 41 | } 42 | 43 | module.exports = config; 44 | --------------------------------------------------------------------------------