├── .github └── workflows │ └── CI.yml ├── .gitignore ├── .prettierrc ├── .vscode ├── launch.json └── tasks.json ├── .vscodeignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── assets ├── flutter.gif └── icon.png ├── package-lock.json ├── package.json ├── src ├── angular-cli.ts ├── commands.ts ├── config-ext.ts ├── config │ └── cli-config.ts ├── configuration-manager-ext.ts ├── configuration-manager.ts ├── deep-merge.ts ├── editor.ts ├── enums │ ├── command-type.ts │ └── resource-type.ts ├── extension.ts ├── file-contents.ts ├── formatting.ts ├── ioutil.ts ├── models │ ├── command.ts │ ├── config.ts │ ├── file.ts │ ├── option-item.ts │ ├── path.ts │ ├── resource-file.ts │ └── resource.ts ├── promisify.ts └── resources.ts ├── templates ├── ff.config.json ├── ff_bloc │ ├── bloc.tmpl │ ├── event.tmpl │ ├── index.tmpl │ ├── model.tmpl │ ├── page.tmpl │ ├── provider.tmpl │ ├── screen.tmpl │ └── state.tmpl ├── mutable │ ├── bloc.tmpl │ ├── event.tmpl │ ├── index.tmpl │ ├── model.tmpl │ ├── page.tmpl │ ├── provider.tmpl │ ├── repository.tmpl │ ├── screen.tmpl │ └── state.tmpl ├── navigate │ └── navigate.tmpl └── simple │ ├── bloc.tmpl │ ├── event.tmpl │ ├── index.tmpl │ ├── model.tmpl │ ├── page.tmpl │ ├── provider.tmpl │ ├── repository.tmpl │ ├── screen.tmpl │ └── state.tmpl ├── tsconfig.json ├── tslint.json ├── vsc-extension-quickstart.md └── webpack.config.js /.github/workflows/CI.yml: -------------------------------------------------------------------------------- 1 | 2 | name: CI 3 | 4 | on: 5 | push: 6 | branches: [ master ] 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | 12 | strategy: 13 | matrix: 14 | node-version: [12.x] 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | 19 | - name: Use Node.js ${{ matrix.node-version }} 20 | uses: actions/setup-node@v1 21 | with: 22 | node-version: ${{ matrix.node-version }} 23 | 24 | - name: Cache Node 25 | id: cache-nodes 26 | uses: actions/cache@v1 27 | with: 28 | path: nodes-cache 29 | key: ${{ runner.os }}-nodes 30 | 31 | - name: Install Npm 32 | if: steps.cache-nodes.outputs.cache-hit != 'true' 33 | run: npm install 34 | 35 | - name: Install vsce 36 | if: steps.cache-nodes.outputs.cache-hit != 'true' 37 | run: npm install -g vsce 38 | 39 | - name: Generate Package 40 | run: vsce package 41 | 42 | - name: Auto TAG from Version 43 | id: tagger 44 | uses: azu/action-package-version-to-git-tag@v1 45 | with: 46 | github_token: ${{ secrets.GITHUB_TOKEN }} 47 | github_repo: ${{ github.repository }} 48 | git_commit_sha: ${{ github.sha }} 49 | git_tag_prefix: "v" 50 | 51 | - name: Gen var 52 | id: previoustag 53 | run: | 54 | echo "::set-output name=num::$(git ls-remote --tags --refs -q --sort -version:refname https://github.com/gorniv/vscode-flutter-files.git v\* | head -n 1 | awk -F refs/tags/v {'print $2'})" 55 | echo "::set-output name=version::$(git ls-remote --tags --refs -q --sort -version:refname https://github.com/gorniv/vscode-flutter-files.git v\* | head -n 1 | awk -F refs/tags/ {'print $2'})" 56 | echo "::set-env name=num::$(git ls-remote --tags --refs -q --sort -version:refname https://github.com/gorniv/vscode-flutter-files.git v\* | head -n 1 | awk -F refs/tags/v {'print $2'})" 57 | echo "::set-env name=version::$(git ls-remote --tags --refs -q --sort -version:refname https://github.com/gorniv/vscode-flutter-files.git v\* | head -n 1 | awk -F refs/tags/ {'print $2'})" 58 | 59 | - name: Get the num of the version 60 | id: num 61 | run: | 62 | echo ${{ steps.previoustag.outputs.num }} 63 | echo ${{ steps.previoustag.outputs.version }} 64 | echo ${{ env.num }} 65 | echo ${{ env.version }} 66 | 67 | - name: Create Release 68 | id: create_release 69 | uses: actions/create-release@v1 70 | env: 71 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 72 | with: 73 | tag_name: ${{ env.version }} 74 | release_name: Release ${{ env.version }} 75 | draft: true 76 | prerelease: false 77 | 78 | - name: Upload Release Asset 79 | id: upload-release-asset 80 | uses: actions/upload-release-asset@v1 81 | env: 82 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 83 | with: 84 | upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps 85 | asset_path: ./vscode-flutter-files-${{ env.num }}.vsix 86 | asset_name: vscode-flutter-files-${{ env.num }}.vsix 87 | asset_content_type: application/vsix 88 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://www.dartlang.org/guides/libraries/private-files 2 | 3 | # Files and directories created by pub 4 | .dart_tool/ 5 | .packages 6 | .pub/ 7 | build/ 8 | # If you're building an application, you may want to check-in your pubspec.lock 9 | pubspec.lock 10 | 11 | # Directory created by dartdoc 12 | # If you don't generate documentation locally you can remove this line. 13 | doc/api/ 14 | 15 | out 16 | node_modules 17 | .vscode-test/** 18 | *.vsix 19 | npm-debug.log 20 | 21 | .history 22 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "singleQuote": true, 4 | "tabWidth": 2, 5 | "trailingComma": "all", 6 | "arrowParens": "always", 7 | "overrides": [ 8 | { 9 | "files": "*.ts", 10 | "options": { 11 | "parser": "typescript" 12 | } 13 | }, 14 | { 15 | "files": "*.html", 16 | "options": { 17 | "parser": "angular" 18 | } 19 | }, 20 | { 21 | "files": "*.json", 22 | "options": { 23 | "parser": "json", 24 | "trailingComma": "none" 25 | } 26 | }, 27 | { 28 | "files": "*.scss", 29 | "options": { 30 | "parser": "scss", 31 | "trailingComma": "none" 32 | } 33 | }, 34 | { 35 | "files": "*.css", 36 | "options": { 37 | "parser": "css", 38 | "trailingComma": "none" 39 | } 40 | } 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "type": "extensionHost", 10 | "request": "launch", 11 | "name": "Launch Extension", 12 | "runtimeExecutable": "${execPath}", 13 | "args": ["--extensionDevelopmentPath=${workspaceFolder}"], 14 | "outFiles": ["${workspaceFolder}/out/**/*.js"], 15 | "preLaunchTask": "npm: watch" 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "watch", 9 | "problemMatcher": [ 10 | "$tsc-watch" 11 | ], 12 | "isBackground": true 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | out/test/** 4 | src/** 5 | .gitignore 6 | vsc-extension-quickstart.md 7 | **/tsconfig.json 8 | **/tslint.json 9 | **/*.map 10 | **/*.ts -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ### [4.9.1](https://github.com/Gorniv/vscode-flutter-files/compare/v4.9.0...v4.9.1) (2024-06-24) 6 | 7 | ## [4.9.0](https://github.com/Gorniv/vscode-flutter-files/compare/v4.8.1...v4.9.0) (2024-06-24) 8 | 9 | 10 | ### Features 11 | 12 | * improve create index(filter .g and .freezed, use quota from settings project) ([48d9818](https://github.com/Gorniv/vscode-flutter-files/commit/48d9818153398142cedd26539cc538d2f29bd6bc)) 13 | 14 | ### [4.8.1](https://github.com/Gorniv/vscode-flutter-files/compare/v4.8.0...v4.8.1) (2023-12-26) 15 | 16 | ## [4.8.0](https://github.com/Gorniv/vscode-flutter-files/compare/v4.7.1...v4.8.0) (2023-12-26) 17 | 18 | 19 | ### Features 20 | 21 | * share my project for ios developers - aso.dev ([4d07ae6](https://github.com/Gorniv/vscode-flutter-files/commit/4d07ae6795deec5cff2c2590c1ffa18540f8e0ad)) 22 | 23 | ### [4.7.1](https://github.com/Gorniv/vscode-flutter-files/compare/v4.7.0...v4.7.1) (2023-06-21) 24 | 25 | 26 | ### Bug Fixes 27 | 28 | * badge ([de9cb68](https://github.com/Gorniv/vscode-flutter-files/commit/de9cb68040f580ad4a4b010022a64bc9a1092433)) 29 | 30 | ## [4.7.0](https://github.com/Gorniv/vscode-flutter-files/compare/v4.6.0...v4.7.0) (2023-06-21) 31 | 32 | 33 | ### Features 34 | 35 | * upgrade templates, readme ([7e2c899](https://github.com/Gorniv/vscode-flutter-files/commit/7e2c89959c983a727228111453ef667cb2ad54be)) 36 | 37 | ## [4.6.0](https://github.com/Gorniv/vscode-flutter-files/compare/v4.5.0...v4.6.0) (2022-11-24) 38 | 39 | 40 | ### Features 41 | 42 | * use ff_bloc ([1f5eed0](https://github.com/Gorniv/vscode-flutter-files/commit/1f5eed09c782bfa6d50f81abef86adf360243d1d)) 43 | 44 | ## [4.5.0](https://github.com/Gorniv/vscode-flutter-files/compare/v4.4.2...v4.5.0) (2022-11-23) 45 | 46 | 47 | ### Features 48 | 49 | * improve error template ([bd51e6a](https://github.com/Gorniv/vscode-flutter-files/commit/bd51e6a7fa30d5588f9c29079872cb2dc7af8369)) 50 | 51 | ### [4.4.2](https://github.com/Gorniv/vscode-flutter-files/compare/v4.4.1...v4.4.2) (2022-10-20) 52 | 53 | 54 | ### Bug Fixes 55 | 56 | * 4.4.1 can not gen new big pack in vscode [#40](https://github.com/Gorniv/vscode-flutter-files/issues/40) ([3947f8b](https://github.com/Gorniv/vscode-flutter-files/commit/3947f8b3db7d2ca35f045505dd23b85f9725aae9)) 57 | 58 | ### [4.4.1](https://github.com/Gorniv/vscode-flutter-files/compare/v4.4.0...v4.4.1) (2022-10-18) 59 | 60 | ## [4.4.0](https://github.com/Gorniv/vscode-flutter-files/compare/v4.3.1...v4.4.0) (2022-10-01) 61 | 62 | 63 | ### Features 64 | 65 | * support multi folder in templates.(base) ([484c58d](https://github.com/Gorniv/vscode-flutter-files/commit/484c58d6202731cb8ce2847471168d6d46954d94)) 66 | 67 | ### [4.3.1](https://github.com/Gorniv/vscode-flutter-files/compare/v4.3.0...v4.3.1) (2022-07-01) 68 | 69 | ## [4.3.0](https://github.com/Gorniv/vscode-flutter-files/compare/v3.1.2...v4.3.0) (2022-07-01) 70 | 71 | 72 | ### Features 73 | 74 | * new template for v.8 type ([7715833](https://github.com/Gorniv/vscode-flutter-files/commit/77158332b85a17780b67936278fa815b55c94cf7)) 75 | * support null safety ([162582d](https://github.com/Gorniv/vscode-flutter-files/commit/162582d9d5ed4910339cef1c8833dc49e215b903)) 76 | 77 | ### [4.2.1](https://github.com/Gorniv/vscode-flutter-files/compare/v4.2.0...v4.2.1) (2021-11-16) 78 | 79 | ## [4.2.0](https://github.com/Gorniv/vscode-flutter-files/compare/v4.1.1...v4.2.0) (2021-11-16) 80 | 81 | 82 | ### Features 83 | 84 | * new template for v.8 type ([7715833](https://github.com/Gorniv/vscode-flutter-files/commit/77158332b85a17780b67936278fa815b55c94cf7)) 85 | 86 | ### [4.1.1](https://github.com/Gorniv/vscode-flutter-files/compare/v4.1.0...v4.1.1) (2021-07-07) 87 | 88 | ## [4.1.0](https://github.com/Gorniv/vscode-flutter-files/compare/v3.1.2...v4.1.0) (2021-07-07) 89 | 90 | 91 | ### Features 92 | 93 | * support null safety ([162582d](https://github.com/Gorniv/vscode-flutter-files/commit/162582d9d5ed4910339cef1c8833dc49e215b903)) 94 | 95 | ### [3.1.2](https://github.com/Gorniv/vscode-flutter-files/compare/v3.1.1...v3.1.2) (2021-02-04) 96 | 97 | 98 | ### Bug Fixes 99 | 100 | * ff.config.json ([7a05444](https://github.com/Gorniv/vscode-flutter-files/commit/7a05444de7f86406c488a8b9b6e94e218616be2e)) 101 | 102 | ### [3.1.1](https://github.com/Gorniv/vscode-flutter-files/compare/v3.1.0...v3.1.1) (2021-02-03) 103 | 104 | ## [3.1.0](https://github.com/Gorniv/vscode-flutter-files/compare/v2.2.0...v3.1.0) (2021-02-03) 105 | 106 | 107 | ### Features 108 | 109 | * dynamic commands and config ([7142dee](https://github.com/Gorniv/vscode-flutter-files/commit/7142dee64d3fac7ab2e4377d8b394952e20265a4)) 110 | 111 | ## [2.2.0](https://github.com/Gorniv/vscode-flutter-files/compare/v2.1.0...v2.2.0) (2020-08-15) 112 | 113 | 114 | ### Features 115 | 116 | * support v.6 bloc ([01d4ce2](https://github.com/Gorniv/vscode-flutter-files/commit/01d4ce2dff1e8746e8348661415ddf5f93caf287)) 117 | * support v5 ([965a07c](https://github.com/Gorniv/vscode-flutter-files/commit/965a07cdef5a3393bdc398518c397b7a80b6a33f)) 118 | 119 | ## [2.1.0](https://github.com/Gorniv/vscode-flutter-files/compare/v1.9.0...v2.1.0) (2020-05-01) 120 | 121 | 122 | ### Features 123 | 124 | * add new command - copy templates ([096fc10](https://github.com/Gorniv/vscode-flutter-files/commit/096fc1085381dce44c1a8a62ed7dc4ee37a3f479)) 125 | * support multi templates ([f1732de](https://github.com/Gorniv/vscode-flutter-files/commit/f1732defb9b3b5969e2dfee6673db39de8fce3c0)) 126 | 127 | 128 | ### Bug Fixes 129 | 130 | * change linter ([97cb622](https://github.com/Gorniv/vscode-flutter-files/commit/97cb6224cbfcd4aa180d278510046f787163ebb8)) 131 | 132 | ## [1.9.0](https://github.com/Gorniv/vscode-flutter-files/compare/v1.8.0...v1.9.0) (2020-03-09) 133 | 134 | 135 | ### Features 136 | 137 | * use Stream for event ([8c17a78](https://github.com/Gorniv/vscode-flutter-files/commit/8c17a784a850f58ae72653eaeb7cf6ff8be20fa5)) 138 | 139 | ## [1.8.0](https://github.com/Gorniv/vscode-flutter-files/compare/v1.7.0...v1.8.0) (2020-03-06) 140 | 141 | 142 | ### Features 143 | 144 | * update templates ([891ba4c](https://github.com/Gorniv/vscode-flutter-files/commit/891ba4c47cf9eec7e6b4dd5044e29a421ef31f70)) 145 | 146 | ## [1.7.0](https://github.com/Gorniv/vscode-flutter-files/compare/v1.6.0...v1.7.0) (2019-12-30) 147 | 148 | 149 | ### Features 150 | 151 | * upgrade templates ([fdb4e01](https://github.com/Gorniv/vscode-flutter-files/commit/fdb4e017ae92a8e3c3acd67cb26f091dd649727b)) 152 | 153 | ## 1.6.0 (2019-11-27) 154 | 155 | 156 | ### Features 157 | 158 | * equatable v0.6.0 ([7668837](https://github.com/Gorniv/vscode-flutter-files/commit/7668837bf11dcec55e517a7894713e09de9806e7)) 159 | * flutter_bloc: 2.0.0 [#14](https://github.com/Gorniv/vscode-flutter-files/issues/14) ([e92e913](https://github.com/Gorniv/vscode-flutter-files/commit/e92e913644dfed233efd172e4b7a2e2b2f26060c)) 160 | * support flutter_bloc 0.22.1 ([7e19570](https://github.com/Gorniv/vscode-flutter-files/commit/7e19570aa02b42dd253a85e394548b30266a2a5a)) 161 | * update readme ([2c2495f](https://github.com/Gorniv/vscode-flutter-files/commit/2c2495ff0ac4b04b2a2ee839d5e8b22b1ee1e5f2)) 162 | * update templates ([76601aa](https://github.com/Gorniv/vscode-flutter-files/commit/76601aaf1cf0433853719ab40b743470fb74221a)) 163 | * update templates ([c7b236c](https://github.com/Gorniv/vscode-flutter-files/commit/c7b236c2c4f7ecb4bcec2e5aa77b5ab6cfbb32f1)) 164 | * Update templates - add StackTrace ([a591756](https://github.com/Gorniv/vscode-flutter-files/commit/a591756e024e428df05d6aea911511ee0f4184fc)) 165 | * Update templates: model, repository, state ([1e6bc12](https://github.com/Gorniv/vscode-flutter-files/commit/1e6bc128dcce4c87f48230e1327c15e86d07da4c)) 166 | * use custom template [#4](https://github.com/Gorniv/vscode-flutter-files/issues/4) ([288c596](https://github.com/Gorniv/vscode-flutter-files/commit/288c596979c1a837c9bd2f0970b0d8a3710ab727)) 167 | 168 | 169 | ### Bug Fixes 170 | 171 | * [#10](https://github.com/Gorniv/vscode-flutter-files/issues/10) and update bloc ([d4dcc78](https://github.com/Gorniv/vscode-flutter-files/commit/d4dcc78f8e5d136c5fda85f38252ef91dc47efc2)) 172 | * custom template for pack ([a4c9798](https://github.com/Gorniv/vscode-flutter-files/commit/a4c97983845714f29d41f0a2cf12b53d2caac447)) 173 | * File(s) could not be created. TypeError: Cannot read property 'name' of undefined [#7](https://github.com/Gorniv/vscode-flutter-files/issues/7) ([6eababc](https://github.com/Gorniv/vscode-flutter-files/commit/6eababc607c93e3ca866cdbe6acf0b435e69d766)) 174 | * final ([b4486be](https://github.com/Gorniv/vscode-flutter-files/commit/b4486beb04c5ce6aecf3b48770a84f5f90a55387)) 175 | * New Big Pack Bloc Stack Overflow issue [#11](https://github.com/Gorniv/vscode-flutter-files/issues/11) ([e6039ad](https://github.com/Gorniv/vscode-flutter-files/commit/e6039ade1d74fa5b058d4552c9229499dfc710a8)) 176 | * stack overflow ([c09ecd1](https://github.com/Gorniv/vscode-flutter-files/commit/c09ecd161a4bab58c37536bf0adf9b9e63910c1f)) 177 | * the wrong path(windows) [#6](https://github.com/Gorniv/vscode-flutter-files/issues/6) ([757a2ed](https://github.com/Gorniv/vscode-flutter-files/commit/757a2ed09fb6eeeb9d70cb789fc47e9139244609)) 178 | * update print stackTrace ([3239677](https://github.com/Gorniv/vscode-flutter-files/commit/3239677345961595b38bf0ae30b18c89bbae5b8c)) 179 | * use [@immutable](https://github.com/immutable), new version BLoC, webpack ([c0e4f71](https://github.com/Gorniv/vscode-flutter-files/commit/c0e4f71fa9314f9881550c8d69416fc286d3c875)) 180 | * vscode engines [#2](https://github.com/Gorniv/vscode-flutter-files/issues/2) ([e7aa5b6](https://github.com/Gorniv/vscode-flutter-files/commit/e7aa5b618c923f333f4b026c946bdd33735db9ce)) 181 | 182 | ## [1.5.5] 21.11.2019 183 | 184 | - support flutter_bloc ^2.0.0 185 | 186 | ## [1.5.4] 18.10.2019 187 | 188 | - support flutter_bloc 0.22.1 189 | 190 | ## [1.5.3] 18.10.2019 191 | 192 | - add workspace support 193 | - update templates 194 | 195 | ## [1.5.1] 28.09.2019 196 | 197 | - fix Stack Overflow 198 | 199 | ## [1.5.0] 28.09.2019 200 | 201 | - support equatable v0.6.0 202 | 203 | ## [1.4.0] 24.07.2019 204 | 205 | - fix: #10 and update bloc 206 | 207 | ## [1.3.4] 14.05.2019 208 | 209 | - Update templates: fix StackTrace 210 | 211 | ## [1.3.3] 13.05.2019 212 | 213 | - Update templates: add StackTrace 214 | 215 | ## [1.3.2] 01.05.2019 216 | 217 | - File(s) could not be created. TypeError: Cannot read property 'name' of undefined #7 218 | 219 | ## [1.3.1] 29.04.2019 220 | 221 | - Fix custom templates for pack 222 | 223 | ## [1.3.0] 23.04.2019 224 | 225 | - Support custom templates 226 | 227 | ## [1.2.4] 17.04.2019 228 | 229 | - Support windows path. Update templates 230 | 231 | ## [1.2.3] 05.04.2019 232 | 233 | - Update templates, use @immutable, new version BLoC 234 | 235 | ## [1.2.2] 19.03.2019 236 | 237 | - Update templates 238 | 239 | ## [1.2.1] 09.03.2019 240 | 241 | - fix: vscode engines 242 | 243 | ## [1.2.0] 24.02.2019 244 | 245 | - Update templates: model, repository, state 246 | 247 | ## [1.0.0] 24.02.2019 248 | 249 | - Initial release 250 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Kravchenko Igor 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 | # VS Code Flutter Files 2 | 3 | [![Awesome Flutter](https://img.shields.io/badge/Awesome-Flutter-blue.svg?longCache=true&style=flat-square)](https://github.com/Solido/awesome-flutter#vscode) 4 | [![Installs](https://img.shields.io/visual-studio-marketplace/i/gornivv.vscode-flutter-files)](https://marketplace.visualstudio.com/items?itemName=gornivv.vscode-flutter-files) 5 | 6 | This extension allows **quickly scaffold flutter BLoC templates** in VS Code project. 7 | 8 | This extension use: 9 | - for BLoC (base) 10 | - for (my way from real code) 11 | 12 | How it works(Russian lang) - https://vas3k.club/post/10567/ 13 | 14 | ## Please, support me: 15 | - Add me on [linkedin](https://www.linkedin.com/in/gorniv/) 16 | - install my 'Tool for iOS developers: [aso.dev](https://aso.dev?utm_source=ext&utm_medium=f_f)' 17 | - install my 'Music player for Apple Music: [meows.app](https://meows.app?utm_source=ext&utm_medium=f_f)' 18 | 19 | ![demo](https://github.com/Gorniv/vscode-flutter-files/raw/master/assets/flutter.gif) 20 | 21 | ## Custom(dynamic) templates 22 | 23 | Copy [templates](./templates) directory to your project (by command "[FF] Copy templates to project") and change any of content files (bloc,event,model,page,provider,repository,scree,state) 24 | 25 | ## Changelog 26 | 27 | See [CHANGELOG.md](CHANGELOG.md) 28 | 29 | ## Features 30 | 31 | Right click on a file or a folder in your current project. 32 | You can find multiple options been added to the context menu: 33 | 34 | | Menu Options | 35 | | ------------------- | 36 | | New Big Pack Bloc | 37 | | New Small Pack Bloc | 38 | | New with dynamic config | 39 | 40 | | Menu Options | 41 | | -------------- | 42 | | New Bloc | 43 | | New Event | 44 | | New Model | 45 | | New Page | 46 | | New Provider | 47 | | New Repository | 48 | | New Screen | 49 | | New State | 50 | 51 | | Menu Options | 52 | | ------------ | 53 | | New Index | 54 | 55 | ## Disclaimer 56 | 57 | **Important:** This extension due to the nature of it's purpose will create 58 | files on your hard drive and if necessary create the respective folder structure. 59 | While it should not override any files during this process, I'm not giving any guarantees 60 | or take any responsibility in case of lost data. 61 | 62 | Fork https://github.com/ivalexa/vscode-angular2-files 63 | 64 | ## License 65 | 66 | MIT 67 | -------------------------------------------------------------------------------- /assets/flutter.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gorniv/vscode-flutter-files/135c8920bb8c40b8d008d44cd3f6b2216bf01775/assets/flutter.gif -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gorniv/vscode-flutter-files/135c8920bb8c40b8d008d44cd3f6b2216bf01775/assets/icon.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vscode-flutter-files", 3 | "displayName": "[FF] Flutter Files", 4 | "description": "Quickly scaffold flutter bloc file templates", 5 | "version": "4.9.1", 6 | "icon": "assets/icon.png", 7 | "publisher": "gornivv", 8 | "author": { 9 | "name": "Kravchenko Igor" 10 | }, 11 | "engines": { 12 | "vscode": "^1.31.0" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/Gorniv/vscode-flutter-files" 17 | }, 18 | "bugs": { 19 | "url": "https://github.com/Gorniv/vscode-flutter-files/issues" 20 | }, 21 | "keywords": [ 22 | "Flutter", 23 | "angular-cli", 24 | "Dart", 25 | "BLoC", 26 | "Templates" 27 | ], 28 | "categories": [ 29 | "Other", 30 | "Programming Languages", 31 | "Snippets" 32 | ], 33 | "activationEvents": [ 34 | "*" 35 | ], 36 | "main": "./out/extension.js", 37 | "contributes": { 38 | "configuration": { 39 | "type": "object", 40 | "title": "Flutter Files menu option configuration", 41 | "properties": { 42 | "flutter-files.menu.asodev": { 43 | "type": "boolean", 44 | "default": true, 45 | "description": "Shows or hides the menu item." 46 | }, 47 | "flutter-files.menu.dynamic": { 48 | "type": "boolean", 49 | "default": true, 50 | "description": "Shows or hides the menu item." 51 | }, 52 | "flutter-files.menu.bigpack": { 53 | "type": "boolean", 54 | "default": true, 55 | "description": "Shows or hides the menu item." 56 | }, 57 | "flutter-files.menu.smallpack": { 58 | "type": "boolean", 59 | "default": true, 60 | "description": "Shows or hides the menu item." 61 | }, 62 | "flutter-files.menu.bloc": { 63 | "type": "boolean", 64 | "default": true, 65 | "description": "Shows or hides the menu item." 66 | }, 67 | "flutter-files.menu.event": { 68 | "type": "boolean", 69 | "default": true, 70 | "description": "Shows or hides the menu item." 71 | }, 72 | "flutter-files.menu.model": { 73 | "type": "boolean", 74 | "default": false, 75 | "description": "Shows or hides the menu item." 76 | }, 77 | "flutter-files.menu.page": { 78 | "type": "boolean", 79 | "default": true, 80 | "description": "Shows or hides the menu item." 81 | }, 82 | "flutter-files.menu.provider": { 83 | "type": "boolean", 84 | "default": false, 85 | "description": "Shows or hides the menu item." 86 | }, 87 | "flutter-files.menu.repository": { 88 | "type": "boolean", 89 | "default": false, 90 | "description": "Shows or hides the menu item." 91 | }, 92 | "flutter-files.menu.screen": { 93 | "type": "boolean", 94 | "default": true, 95 | "description": "Shows or hides the menu item." 96 | }, 97 | "flutter-files.menu.state": { 98 | "type": "boolean", 99 | "default": true, 100 | "description": "Shows or hides the menu item." 101 | }, 102 | "flutter-files.menu.templates": { 103 | "type": "boolean", 104 | "default": true, 105 | "description": "Shows or hides the menu item." 106 | }, 107 | "flutter-files.menu.index": { 108 | "type": "boolean", 109 | "default": true, 110 | "description": "Shows or hides the menu item." 111 | } 112 | } 113 | }, 114 | "commands": [ 115 | { 116 | "command": "extension.addFlutterAsoDev", 117 | "title": "Copilot for App Store Connect" 118 | }, 119 | { 120 | "command": "extension.addFlutter2_Dynamic", 121 | "title": "[FF] New with dynamic config" 122 | }, 123 | { 124 | "command": "extension.addFlutter2BigPack", 125 | "title": "[FF] New Big Pack Bloc" 126 | }, 127 | { 128 | "command": "extension.addFlutter2SmallPack", 129 | "title": "[FF] New Small Pack Bloc" 130 | }, 131 | { 132 | "command": "extension.addFlutter2Bloc", 133 | "title": "[FF] New Bloc" 134 | }, 135 | { 136 | "command": "extension.addFlutter2Event", 137 | "title": "[FF] New Event" 138 | }, 139 | { 140 | "command": "extension.addFlutter2Model", 141 | "title": "[FF] New Model" 142 | }, 143 | { 144 | "command": "extension.addFlutter2Page", 145 | "title": "[FF] New Page" 146 | }, 147 | { 148 | "command": "extension.addFlutter2Provider", 149 | "title": "[FF] New Provider" 150 | }, 151 | { 152 | "command": "extension.addFlutter2Repository", 153 | "title": "[FF] New Repository" 154 | }, 155 | { 156 | "command": "extension.addFlutter2Screen", 157 | "title": "[FF] New Screen" 158 | }, 159 | { 160 | "command": "extension.addFlutter2State", 161 | "title": "[FF] New State" 162 | }, 163 | { 164 | "command": "extension.addFlutter2zTemplates", 165 | "title": "[FF] Copy templates to project" 166 | }, 167 | { 168 | "command": "extension.addFlutter2zIndex", 169 | "title": "[FF] New Index" 170 | } 171 | ], 172 | "menus": { 173 | "explorer/context": [ 174 | { 175 | "when": "config.flutter-files.menu.asodev", 176 | "command": "extension.addFlutterAsoDev", 177 | "group": "_Flutter_Pack_Support" 178 | }, 179 | { 180 | "when": "config.flutter-files.menu.dynamic", 181 | "command": "extension.addFlutter2_Dynamic", 182 | "group": "0Flutter Pack" 183 | }, 184 | { 185 | "when": "config.flutter-files.menu.bigpack", 186 | "command": "extension.addFlutter2BigPack", 187 | "group": "0Flutter Pack" 188 | }, 189 | { 190 | "when": "config.flutter-files.menu.smallpack", 191 | "command": "extension.addFlutter2SmallPack", 192 | "group": "0Flutter Pack" 193 | }, 194 | { 195 | "when": "config.flutter-files.menu.bloc", 196 | "command": "extension.addFlutter2Bloc", 197 | "group": "1Flutter" 198 | }, 199 | { 200 | "when": "config.flutter-files.menu.event", 201 | "command": "extension.addFlutter2Event", 202 | "group": "1Flutter" 203 | }, 204 | { 205 | "when": "config.flutter-files.menu.model", 206 | "command": "extension.addFlutter2Model", 207 | "group": "1Flutter" 208 | }, 209 | { 210 | "when": "config.flutter-files.menu.page", 211 | "command": "extension.addFlutter2Page", 212 | "group": "1Flutter" 213 | }, 214 | { 215 | "when": "config.flutter-files.menu.provider", 216 | "command": "extension.addFlutter2Provider", 217 | "group": "1Flutter" 218 | }, 219 | { 220 | "when": "config.flutter-files.menu.repository", 221 | "command": "extension.addFlutter2Repository", 222 | "group": "1Flutter" 223 | }, 224 | { 225 | "when": "config.flutter-files.menu.screen", 226 | "command": "extension.addFlutter2Screen", 227 | "group": "1Flutter" 228 | }, 229 | { 230 | "when": "config.flutter-files.menu.state", 231 | "command": "extension.addFlutter2State", 232 | "group": "1Flutter" 233 | }, 234 | { 235 | "when": "config.flutter-files.menu.templates", 236 | "command": "extension.addFlutter2zTemplates", 237 | "group": "2Flutter" 238 | }, 239 | { 240 | "when": "config.flutter-files.menu.index", 241 | "command": "extension.addFlutter2zIndex", 242 | "group": "3Flutter Index" 243 | } 244 | ] 245 | } 246 | }, 247 | "scripts": { 248 | "vscode:prepublish": "npm run templates && webpack --mode production", 249 | "compile": "npm run templates && webpack --mode none", 250 | "watch": "npm run templates && webpack --mode none", 251 | "test-compile": "tsc -p ./", 252 | "templates": "copyfiles templates/**/*.tmpl templates/**/*.tmpl ./out/ && copyfiles templates/**/*.json templates/**/*.json ./out/", 253 | "postinstall": "node ./node_modules/vscode/bin/install", 254 | "test": "npm run compile && node ./node_modules/vscode/bin/test", 255 | "format:check": "prettier --write --config ./.prettierrc --list-different \"src/**/*{.ts,.json}\"", 256 | "vsce:package": "vsce package", 257 | "vsce:publish": "vsce publish", 258 | "dry": "standard-version --dry-run", 259 | "standard": "standard-version", 260 | "release": "npm run standard && npm run vsce:publish && npm publish" 261 | }, 262 | "devDependencies": { 263 | "@types/mocha": "9.0.0", 264 | "@types/node": "16.11.7", 265 | "copyfiles": "2.4.1", 266 | "prettier": "2.4.1", 267 | "standard-version": "9.3.2", 268 | "ts-loader": "9.2.6", 269 | "tslint": "6.1.3", 270 | "typescript": "4.4.4", 271 | "vscode": "1.1.37", 272 | "vscode-languageserver": "7.0.0", 273 | "vscode-languageserver-textdocument": "1.0.2", 274 | "webpack": "5.64.1", 275 | "webpack-cli": "4.9.1" 276 | }, 277 | "dependencies": { 278 | "express-es6-template-engine": "2.2.3", 279 | "js-yaml": "4.1.0" 280 | } 281 | } 282 | -------------------------------------------------------------------------------- /src/angular-cli.ts: -------------------------------------------------------------------------------- 1 | import { window } from 'vscode'; 2 | import * as fs from 'fs'; 3 | import * as path from 'path'; 4 | import { IConfig } from './models/config'; 5 | import { IPath } from './models/path'; 6 | import { FileContents } from './file-contents'; 7 | import { IFiles } from './models/file'; 8 | import { promisify } from './promisify'; 9 | import { toUpperCase } from './formatting'; 10 | import { createDirectory, createFiles, createFolder } from './ioutil'; 11 | import { ResourcesDynamic } from './resources'; 12 | import { ResourceType } from './enums/resource-type'; 13 | import { ConfigElement } from './config-ext'; 14 | import { ConfigurationManager } from './configuration-manager'; 15 | 16 | const fsWriteFile = promisify(fs.writeFile); 17 | const fsReaddir = promisify(fs.readdir); 18 | const fsStat = promisify(fs.stat); 19 | const fsReadFile = promisify(fs.readFile); 20 | 21 | export class AngularCli { 22 | constructor(private readonly fc = new FileContents()) {} 23 | 24 | async generateResources( 25 | name: ResourceType, 26 | loc: IPath, 27 | config: IConfig, 28 | configExt: ConfigElement, 29 | ) { 30 | const resource = !!loc.command 31 | ? ResourcesDynamic.resourcesCommand(loc.command) 32 | : ResourcesDynamic.resourcesDynamic(configExt).get(name); 33 | 34 | if (!resource) { 35 | window.showErrorMessage(`Error: can't found key = '${name}' in ff.config.json`); 36 | return; 37 | } 38 | 39 | loc.dirName = resource.hasOwnProperty('locDirName') 40 | ? resource.locDirName(loc, config) 41 | : loc.dirName; 42 | loc.dirPath = resource.hasOwnProperty('locDirPath') 43 | ? resource.locDirPath(loc, config) 44 | : loc.dirPath; 45 | 46 | // tslint:disable-next-line:ter-arrow-parens 47 | if (resource.hasOwnProperty('createFolder') && resource.createFolder(config)) { 48 | await createFolder(loc); 49 | } 50 | 51 | const filesASync: Promise[] = resource.files 52 | // tslint:disable-next-line:ter-arrow-parens 53 | .filter((file) => file !== 'index') 54 | .map(async (file) => { 55 | try { 56 | const splitDir = '\\'; 57 | var newDirs = file.split(splitDir); 58 | var tempDir = ''; 59 | var fileTemp: string = file; 60 | if (newDirs.length != 1) { 61 | for (let index = 0; index < newDirs.length - 1; index++) { 62 | const newDir = newDirs[index]; 63 | createDirectory(path.join(loc.dirPath, newDir)); 64 | tempDir = path.join(tempDir, newDir); 65 | fileTemp = fileTemp.replace(newDir + splitDir, ''); 66 | } 67 | } 68 | const fileName: string = `${fileTemp}.dart`; 69 | const newName: string = path.join( 70 | loc.dirPath, 71 | tempDir, 72 | fileName.startsWith('_') ? `${loc.fileName}${fileName}` : `${loc.fileName}_${fileName}`, 73 | ); 74 | const result: IFiles = { 75 | name: newName, 76 | content: await this.fc.getTemplateContent(file, config, loc.fileName, loc), 77 | }; 78 | return result; 79 | } catch (ex) { 80 | console.log(ex); 81 | window.showErrorMessage(`Error: ${ex}`); 82 | } 83 | }); 84 | let files = await Promise.all(filesASync); 85 | files = files.filter((c) => c.content != ''); 86 | await createFiles(loc, files); 87 | 88 | let indexFiles = await this.createIndexFile({ files: resource.files }, loc); 89 | indexFiles = indexFiles.filter((c) => c.content != ''); 90 | await createFiles(loc, indexFiles); 91 | } 92 | 93 | // Function to read directory recursively and gather .dart files 94 | async readDirectoryRecursive(dirPath: string, fileList: string[] = []): Promise { 95 | const files = await fs.promises.readdir(dirPath); 96 | if (fileList.length > 0 && files.includes('index.dart')) { 97 | fileList.push(path.join(dirPath, 'index.dart')); 98 | return fileList; 99 | } 100 | 101 | for (const file of files) { 102 | const filePath = path.join(dirPath, file); 103 | const stat = await fs.promises.stat(filePath); 104 | 105 | if (stat.isDirectory()) { 106 | await this.readDirectoryRecursive(filePath, fileList); // Recursively read subdirectory 107 | } else { 108 | if ( 109 | file.toLowerCase().endsWith('.dart') && 110 | !file.toLowerCase().includes('.g.dart') && 111 | !file.toLowerCase().includes('.freezed.dart') 112 | ) { 113 | fileList.push(filePath); 114 | } 115 | } 116 | } 117 | 118 | return fileList; 119 | } 120 | 121 | async createIndexFile( 122 | resource: { files: string[] }, 123 | loc: { dirPath: string }, 124 | ): Promise { 125 | const filesIndex: Promise[] = resource.files 126 | .filter((file) => file === 'index') 127 | .map(async (file) => { 128 | try { 129 | const fileName = `${file}.dart`; 130 | const files = await this.readDirectoryRecursive(loc.dirPath); 131 | let contentStr = ''; 132 | const quota = await ConfigurationManager.quote(); 133 | for (const filePath of files) { 134 | const relativeFilePath = path.relative(loc.dirPath, filePath).replace(/\\/g, '/'); 135 | if (relativeFilePath === 'index.dart') { 136 | continue; 137 | } 138 | contentStr += `export ${quota}${relativeFilePath}${quota};\r\n`; 139 | } 140 | 141 | const result: IFiles = { 142 | name: path.join(loc.dirPath, fileName), 143 | content: contentStr, 144 | }; 145 | 146 | return result; 147 | } catch (ex) { 148 | console.error(ex); 149 | window.showErrorMessage(`Error: ${ex}`); 150 | } 151 | }); 152 | 153 | return Promise.all(filesIndex); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/commands.ts: -------------------------------------------------------------------------------- 1 | import { ResourceType } from './enums/resource-type'; 2 | import { ICommand } from './models/command'; 3 | import { CommandType } from './enums/command-type'; 4 | 5 | export const commandsMap = new Map([ 6 | [CommandType.Dynamic, { fileName: 'you_awesome', resource: ResourceType.Dynamic }], 7 | [CommandType.BigPack, { fileName: 'you_awesome', resource: ResourceType.BigPack }], 8 | [CommandType.SmallPack, { fileName: 'you_awesome', resource: ResourceType.SmallPack }], 9 | [CommandType.Bloc, { fileName: 'you_awesome', resource: ResourceType.Bloc }], 10 | [CommandType.Event, { fileName: 'you_awesome', resource: ResourceType.Event }], 11 | [CommandType.State, { fileName: 'you_awesome', resource: ResourceType.State }], 12 | [CommandType.Model, { fileName: 'you_awesome', resource: ResourceType.Model }], 13 | [CommandType.Page, { fileName: 'you_awesome', resource: ResourceType.Page }], 14 | [CommandType.Provider, { fileName: 'you_awesome', resource: ResourceType.Provider }], 15 | [CommandType.Repository, { fileName: 'you_awesome', resource: ResourceType.Repository }], 16 | [CommandType.Screen, { fileName: 'you_awesome', resource: ResourceType.Screen }], 17 | [CommandType.State, { fileName: 'you_awesome', resource: ResourceType.State }], 18 | [CommandType.Index, { fileName: 'you_awesome', resource: ResourceType.Index }], 19 | [CommandType.Templates, { fileName: '', resource: ResourceType.Templates }], 20 | ]); 21 | -------------------------------------------------------------------------------- /src/config-ext.ts: -------------------------------------------------------------------------------- 1 | 2 | export interface ConfigExt { 3 | name: string; 4 | configs: ConfigElement[]; 5 | } 6 | 7 | export interface ConfigElement { 8 | name: string; 9 | commands: Command[]; 10 | } 11 | 12 | export interface Command { 13 | name: string; 14 | key?: string; 15 | templates: string[]; 16 | files: string[]; 17 | } 18 | -------------------------------------------------------------------------------- /src/config/cli-config.ts: -------------------------------------------------------------------------------- 1 | import { IConfig } from './../models/config'; 2 | 3 | export const config: IConfig = { 4 | appName: 'flutter_files', 5 | defaults: { 6 | bigpack: { flat: false }, 7 | smallpack: { flat: false }, 8 | }, 9 | appPath: '', 10 | }; 11 | -------------------------------------------------------------------------------- /src/configuration-manager-ext.ts: -------------------------------------------------------------------------------- 1 | import { workspace, window, Uri } from 'vscode'; 2 | import * as fs from 'fs'; 3 | import { promisify } from './promisify'; 4 | import deepMerge from './deep-merge'; 5 | import { ConfigExt } from './config-ext'; 6 | import * as path from 'path'; 7 | import { FileContents } from './file-contents'; 8 | 9 | const readFileAsync = promisify(fs.readFile); 10 | 11 | export class ConfigurationManagerExt { 12 | private getFiles(): string[] { 13 | const from = path.join(__dirname, FileContents.TEMPLATES_FOLDER, 'ff.config.json'); 14 | const files = [path.join(workspace.rootPath, 'templates', 'ff.config.json'), from]; 15 | return files; 16 | } 17 | private async readConfigFile(): Promise> { 18 | const files = this.getFiles(); 19 | const configMap = new Map(); 20 | while (files.length > 0) { 21 | const filePath: string = files.splice(0, 1)?.toString(); 22 | const fsExists = fs.existsSync(filePath); 23 | if (fsExists != true) { 24 | continue; 25 | } 26 | const data = await readFileAsync(filePath, 'utf8'); 27 | const config: ConfigExt = JSON.parse(data); 28 | 29 | // prevent parsing issues 30 | configMap.set(filePath, config); 31 | } 32 | 33 | return configMap; 34 | } 35 | 36 | private parseConfig(config): ConfigExt { 37 | return deepMerge({}, config); 38 | } 39 | 40 | public async getConfig(): Promise> { 41 | const configFile = await this.readConfigFile(); 42 | return new Map([...configFile].map(([key, value]) => [key, this.parseConfig(value)])); 43 | } 44 | 45 | public watchConfigFiles(callback) { 46 | const files = this.getFiles(); 47 | files.forEach((file) => { 48 | const fsExists = fs.existsSync(file); 49 | if (fsExists == true) { 50 | fs.watch(file, (eventType, filename) => { 51 | callback(); 52 | }); 53 | } 54 | }); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/configuration-manager.ts: -------------------------------------------------------------------------------- 1 | import { IConfig } from './models/config'; 2 | import { workspace, window, Uri } from 'vscode'; 3 | import * as fs from 'fs'; 4 | import { config as defaultConfig } from './config/cli-config'; 5 | import { promisify } from './promisify'; 6 | import deepMerge from './deep-merge'; 7 | import jsYaml = require('js-yaml'); 8 | 9 | const readFileAsync = promisify(fs.readFile); 10 | 11 | export class ConfigurationManager { 12 | private readonly CONFIG_FILES = ['pubspec.yaml']; 13 | private static readonly analysis_options_file = 'analysis_options.yaml'; 14 | 15 | private async readConfigFile(): Promise> { 16 | const files = await workspace.findFiles('{pubspec.yaml}', ''); 17 | const configMap = new Map(); 18 | while (files.length > 0) { 19 | const [{ fsPath: filePath }] = files.splice(0, 1); 20 | 21 | const data = await readFileAsync(filePath, 'utf8'); 22 | 23 | const config: any = {}; 24 | 25 | // prevent parsing issues 26 | try { 27 | const pubspec = jsYaml.load(data); 28 | config.appName = pubspec.name; 29 | } catch (ex) { 30 | window.showErrorMessage( 31 | `Invalid schema detected in pubspec.yaml, please correct and try again! error: ${ex}`, 32 | ); 33 | throw Error('Invalid schema'); 34 | } 35 | configMap.set(workspace.getWorkspaceFolder(Uri.file(filePath)).name, config); 36 | } 37 | 38 | return configMap; 39 | } 40 | 41 | private parseConfig(config): IConfig { 42 | return deepMerge({}, defaultConfig, config); 43 | } 44 | static _singleQuote = undefined; 45 | public static async quote(): Promise { 46 | return (await ConfigurationManager._isSingleQuote()) ? "'" : '"'; 47 | } 48 | 49 | public static async _isSingleQuote(): Promise { 50 | if (ConfigurationManager._singleQuote !== undefined) { 51 | return ConfigurationManager._singleQuote; 52 | } 53 | try { 54 | const files = await workspace.findFiles(`{${this.analysis_options_file}}`, ''); 55 | if (files.length === 0) { 56 | console.error('analysis_options.yaml not found'); 57 | return true; 58 | } 59 | const [{ fsPath: filePath }] = files.splice(0, 1); 60 | const fileData = fs.readFileSync(filePath, 'utf8'); 61 | const result = 62 | fileData.includes('prefer_double_quotes: false') || 63 | fileData.includes('prefer_single_quotes: true') || 64 | (fileData.includes('prefer_single_quotes') && !fileData.includes('prefer_double_quotes')); 65 | ConfigurationManager._singleQuote = result; 66 | return result; 67 | } catch (ex) { 68 | console.error(ex); 69 | return true; 70 | } 71 | } 72 | 73 | public async getConfig(): Promise> { 74 | const configFile = await this.readConfigFile(); 75 | return new Map([...configFile].map(([key, value]) => [key, this.parseConfig(value)])); 76 | } 77 | 78 | public watchConfigFiles(callback) { 79 | if (workspace.rootPath) { 80 | fs.watch(workspace.rootPath, (eventType, filename) => { 81 | if (this.CONFIG_FILES.includes(filename)) { 82 | callback(); 83 | } 84 | }); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/deep-merge.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Simple object check. 3 | * @param item 4 | * @returns {boolean} 5 | */ 6 | const isObject = (item) => item && typeof item === 'object' && !Array.isArray(item); 7 | 8 | /** 9 | * Deep merge two objects. 10 | * @param target 11 | * @param ...sources 12 | */ 13 | const mergeDeep = (target, ...sources) => { 14 | if (!sources.length) return target; 15 | const source = sources.shift(); 16 | 17 | if (isObject(target) && isObject(source)) { 18 | for (const key in source) { 19 | if (isObject(source[key])) { 20 | if (!target[key]) Object.assign(target, { [key]: {} }); 21 | mergeDeep(target[key], source[key]); 22 | } else { 23 | Object.assign(target, { [key]: source[key] }); 24 | } 25 | } 26 | } 27 | 28 | return mergeDeep(target, ...sources); 29 | }; 30 | 31 | export default mergeDeep; 32 | -------------------------------------------------------------------------------- /src/editor.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import * as fs from 'fs'; 3 | import * as path from 'path'; 4 | import { IPath } from './models/path'; 5 | import { ResourceType } from './enums/resource-type'; 6 | import { FileContents } from './file-contents'; 7 | import { copyRecursiveSync } from './ioutil'; 8 | import { Command, ConfigElement, ConfigExt } from './config-ext'; 9 | 10 | export const displayStatusMessage = (type: string, name: string, timeout = 2000) => 11 | vscode.window.setStatusBarMessage(`${type} ${name} was successfully generated`, timeout); 12 | 13 | // Show input prompt for folder name 14 | export const showFileNameDialog = async ( 15 | args: any, 16 | type: ResourceType, 17 | defaultTypeName, 18 | configExt: Map, 19 | ): Promise => { 20 | let clickedFolderPath: string; 21 | if (args) { 22 | clickedFolderPath = args.fsPath; 23 | } else { 24 | if (!vscode.window.activeTextEditor) { 25 | throw new Error( 26 | 'Please open a file first.. or just right-click on a file/folder and use the context menu!', 27 | ); 28 | } else { 29 | clickedFolderPath = path.dirname(vscode.window.activeTextEditor.document.fileName); 30 | } 31 | } 32 | 33 | const rootPath = fs.lstatSync(clickedFolderPath).isDirectory() 34 | ? clickedFolderPath 35 | : path.dirname(clickedFolderPath); 36 | 37 | if (vscode.workspace.rootPath === undefined) { 38 | throw new Error('Please open a project first. Thanks! :-)'); 39 | } else { 40 | let fileName = `${type}`; 41 | if (type === ResourceType.Templates) { 42 | const from = path.join(__dirname, FileContents.TEMPLATES_FOLDER); 43 | const to = path.join(vscode.workspace.rootPath, FileContents.TEMPLATES_FOLDER); 44 | await copyRecursiveSync(from, to); 45 | return; 46 | } 47 | if (type !== ResourceType.Index) { 48 | fileName = await vscode.window.showInputBox({ 49 | prompt: `Type the name of the new ${type}`, 50 | value: `${defaultTypeName}`, 51 | }); 52 | } 53 | 54 | if (!fileName) { 55 | throw new Error("That's not a valid name! (no whitespaces or special characters)"); 56 | } else { 57 | let dirName = ''; 58 | const fc = new FileContents(); 59 | const dirsRoot = await fc.loadDirTemplates(vscode.workspace.rootPath); 60 | // if (dirsRoot && dirsRoot.length === 0) { 61 | // dirsRoot.push(''); 62 | // } 63 | const dirs = await fc.loadDirTemplates(__dirname); 64 | let i = 0; 65 | let allDir = [ 66 | ...(dirsRoot?.map((c) => { 67 | i += 1; 68 | return { 69 | title: `${i}. ` + c.concat('(project)'), 70 | project: true, 71 | value: c, 72 | }; 73 | }) ?? []), 74 | ...dirs.map((c) => { 75 | i += 1; 76 | return { title: `${i}. ` + c, project: false, value: c }; 77 | }), 78 | ]; 79 | 80 | let index = 0; 81 | let choosedCommand = null; 82 | if (type === ResourceType.Dynamic) { 83 | let commands = []; 84 | for (const kv of configExt) { 85 | const element: ConfigExt = kv[1]; 86 | for (const item of element.configs) { 87 | item.commands.forEach((c) => 88 | commands.push({ 89 | title: `${c.name} (${element.name})`, 90 | kv: kv, 91 | item: item, 92 | command: c, 93 | }), 94 | ); 95 | } 96 | } 97 | let commandsShow = commands.map((c) => { 98 | index += 1; 99 | return `${index}. ${c.title}`; 100 | }); 101 | const command = await vscode.window.showQuickPick(commandsShow, { 102 | canPickMany: false, 103 | placeHolder: 'Select type command', 104 | }); 105 | let indexOfCommand = commandsShow.indexOf(command); 106 | choosedCommand = commands[indexOfCommand]; 107 | console.log(command); 108 | } 109 | let directory; 110 | let dirsShow: string[]; 111 | if (choosedCommand != null) { 112 | let choosedCommandValue: Command = choosedCommand.command as Command; 113 | dirsShow = allDir 114 | .filter((c) => 115 | choosedCommandValue.templates.some((tem) => c.value.includes(tem) || tem == '*'), 116 | ) 117 | .map((c) => { 118 | return `${c.title}`; 119 | }); 120 | } else { 121 | dirsShow = allDir.map((c) => { 122 | return `${c.title}`; 123 | }); 124 | } 125 | 126 | if (type !== ResourceType.Index) { 127 | directory = await vscode.window.showQuickPick(dirsShow, { 128 | canPickMany: false, 129 | placeHolder: 'Select type templates', 130 | }); 131 | } else { 132 | directory = dirsShow[0]; 133 | } 134 | const indexStr = directory.split('.')[0]; 135 | const indexDirectory = parseInt(indexStr, 0); 136 | let templateDirectory = ''; 137 | if (dirsRoot && indexDirectory <= dirsRoot.length) { 138 | templateDirectory = path.join( 139 | vscode.workspace.rootPath, 140 | FileContents.TEMPLATES_FOLDER, 141 | dirsRoot[indexDirectory - 1], 142 | ); 143 | } else { 144 | templateDirectory = path.join( 145 | __dirname, 146 | FileContents.TEMPLATES_FOLDER, 147 | dirs[indexDirectory - (dirsRoot?.length ?? 0) - 1], 148 | ); 149 | } 150 | 151 | [fileName] = fileName.split(' '); 152 | const fullPath = path.join(rootPath, fileName); 153 | 154 | if (fileName.indexOf('\\') !== -1) { 155 | [dirName, fileName] = fileName.split('\\'); 156 | } 157 | const dirPath = path.join(rootPath, dirName); 158 | const result: IPath = { 159 | fullPath, 160 | fileName, 161 | dirName, 162 | dirPath, 163 | rootPath, 164 | templateDirectory, 165 | command: choosedCommand?.command, 166 | }; 167 | return result; 168 | } 169 | } 170 | }; 171 | 172 | export const showWarning = async (): Promise => { 173 | vscode.window.showInformationMessage('Please install latest version of vscode', 'Got It'); 174 | }; 175 | -------------------------------------------------------------------------------- /src/enums/command-type.ts: -------------------------------------------------------------------------------- 1 | export enum CommandType { 2 | Dynamic = 'extension.addFlutter2_Dynamic', 3 | AsoDev = 'extension.addFlutterAsoDev', 4 | BigPack = 'extension.addFlutter2BigPack', 5 | SmallPack = 'extension.addFlutter2SmallPack', 6 | Bloc = 'extension.addFlutter2Bloc', 7 | State = 'extension.addFlutter2State', 8 | Event = 'extension.addFlutter2Event', 9 | Model = 'extension.addFlutter2Model', 10 | Page = 'extension.addFlutter2Page', 11 | Provider = 'extension.addFlutter2Provider', 12 | Repository = 'extension.addFlutter2Repository', 13 | Screen = 'extension.addFlutter2Screen', 14 | Templates = 'extension.addFlutter2zTemplates', 15 | Index = 'extension.addFlutter2zIndex', 16 | } 17 | -------------------------------------------------------------------------------- /src/enums/resource-type.ts: -------------------------------------------------------------------------------- 1 | export enum ResourceType { 2 | Dynamic = 'dynamic', 3 | BigPack = 'bigpack', 4 | SmallPack = 'smallpack', 5 | Bloc = 'bloc', 6 | Event = 'event', 7 | Model = 'model', 8 | Page = 'page', 9 | Provider = 'provider', 10 | Repository = 'repository', 11 | Screen = 'screen', 12 | State = 'state', 13 | Index = 'index', 14 | Templates = 'Templates', 15 | } 16 | -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | import { ExtensionContext, commands, workspace, Uri } from 'vscode'; 2 | import { ConfigurationManager } from './configuration-manager'; 3 | import { showFileNameDialog, displayStatusMessage } from './editor'; 4 | import { commandsMap } from './commands'; 5 | import { toTileCase } from './formatting'; 6 | import { AngularCli } from './angular-cli'; 7 | import { ResourceType } from './enums/resource-type'; 8 | import { IConfig } from './models/config'; 9 | import { config as defaultConfig } from './config/cli-config'; 10 | import { ConfigExt } from './config-ext'; 11 | import { ConfigurationManagerExt } from './configuration-manager-ext'; 12 | import { CommandType } from './enums/command-type'; 13 | import * as vscode from 'vscode'; 14 | 15 | export async function activate(context: ExtensionContext) { 16 | const angularCli = new AngularCli(); 17 | const cm = new ConfigurationManager(); 18 | const cmExt = new ConfigurationManagerExt(); 19 | let configMap: Map; 20 | let configExtMap: Map; 21 | 22 | setImmediate(async () => (configMap = await cm.getConfig())); 23 | setImmediate(async () => (configExtMap = await cmExt.getConfig())); 24 | 25 | // watch and update on config file changes 26 | cm.watchConfigFiles(async () => (configMap = await cm.getConfig())); 27 | cmExt.watchConfigFiles(async () => (configExtMap = await cmExt.getConfig())); 28 | 29 | const showDynamicDialog = async (args: any, fileName: string, resource: ResourceType) => { 30 | const loc = await showFileNameDialog(args, resource, fileName, configExtMap); 31 | 32 | const workspaceFolder = workspace.getWorkspaceFolder(Uri.file(loc.fullPath)); 33 | let config = 34 | (workspaceFolder && 35 | configMap.has(workspaceFolder.name) && 36 | configMap.get(workspaceFolder.name)) || 37 | defaultConfig; 38 | let resourceConfig: IConfig = { 39 | ...config, 40 | appPath: (workspaceFolder && workspaceFolder.uri.fsPath) || workspace.rootPath, 41 | }; 42 | 43 | let arr = [...configExtMap]; 44 | let lastIndex = configExtMap.size - 1; 45 | let confLast = arr[lastIndex][1]; 46 | let defaultConfigExt = confLast.configs[0]; 47 | await angularCli.generateResources( 48 | resource, 49 | loc, 50 | resourceConfig, 51 | defaultConfigExt, 52 | ); 53 | displayStatusMessage(toTileCase(resource), loc.fileName); 54 | }; 55 | const command = commands.registerCommand(CommandType.AsoDev, (args) => { 56 | return vscode.env.openExternal(vscode.Uri.parse('https://aso.dev')); 57 | }); 58 | context.subscriptions.push(command); 59 | for (let [key, value] of commandsMap) { 60 | const command = commands.registerCommand(key, (args) => { 61 | return showDynamicDialog(args, value.fileName, value.resource); 62 | }); 63 | context.subscriptions.push(command); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/file-contents.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import * as path from 'path'; 3 | import * as es6Renderer from 'express-es6-template-engine'; 4 | import { IConfig } from './models/config'; 5 | import { toUpperCase, toPrivateCase } from './formatting'; 6 | import { promisify } from './promisify'; 7 | import { IPath } from './models/path'; 8 | import { resolve } from 'path'; 9 | 10 | const fsReaddir = promisify(fs.readdir); 11 | const fsReadFile = promisify(fs.readFile); 12 | const fsExists = promisify(fs.exists); 13 | 14 | export class FileContents { 15 | static TEMPLATES_FOLDER = 'templates'; 16 | static TEMPLATE_ARGUMENTS = 'inputName, upperName, privateName, appName, relative, params'; 17 | 18 | private templatesMap: Map; 19 | private localTemplatesMap: Map; 20 | 21 | constructor() { 22 | this.templatesMap = new Map(); 23 | } 24 | 25 | async loadTemplates(pathDirectory: string, templatesMap: Map) { 26 | if (!fs.existsSync(pathDirectory)) { 27 | return; 28 | } 29 | 30 | let tempMap: Map = await this.getTemplates(pathDirectory); 31 | for (const [key, value] of tempMap.entries()) { 32 | try { 33 | let compiled = es6Renderer(value, FileContents.TEMPLATE_ARGUMENTS); 34 | templatesMap.set(key, compiled); 35 | } catch (e) { 36 | let compiled = es6Renderer(e, FileContents.TEMPLATE_ARGUMENTS); 37 | templatesMap.set(key, compiled); 38 | console.log(e); 39 | } 40 | } 41 | return templatesMap; 42 | } 43 | 44 | private async getTemplates(templatesPath: string): Promise> { 45 | const templatesFiles: string[] = (await getFiles(templatesPath)) 46 | .map((f) => f.replace(templatesPath, '')) 47 | .map((f: string) => f.substring(1)); 48 | 49 | // tslint:disable-next-line:ter-arrow-parens 50 | const templatesFilesPromises = templatesFiles.map((t) => { 51 | try { 52 | // tslint:disable-next-line:ter-arrow-parens 53 | return fsReadFile(path.join(templatesPath, t), 'utf8').then((data) => [t, data]); 54 | } catch (e) { 55 | console.log(e); 56 | } 57 | }); 58 | const templates = await Promise.all(templatesFilesPromises); 59 | 60 | // tslint:disable-next-line:ter-arrow-parens 61 | return new Map(templates.map((x) => x as [string, string])); 62 | } 63 | 64 | async loadDirTemplates(pathDirectory: string) { 65 | const templatesPath = path.join(pathDirectory, FileContents.TEMPLATES_FOLDER); 66 | if (!fs.existsSync(templatesPath)) { 67 | return undefined; 68 | } 69 | 70 | const directories: string[] = await this.getDirTemplates(templatesPath); 71 | return directories.filter((c) => { 72 | return !c.endsWith('.tmpl') && !c.endsWith('.json'); 73 | }); 74 | } 75 | 76 | private async getDirTemplates(templatesPath: string): Promise { 77 | const templatesFiles: string[] = await fsReaddir(templatesPath, 'utf-8'); 78 | return templatesFiles; 79 | } 80 | 81 | public async getTemplateContent( 82 | template: string, 83 | config: IConfig, 84 | inputName: string, 85 | loc: IPath, 86 | ) { 87 | const paths = loc.dirPath.split(path.sep); 88 | let relative: string; 89 | let find = false; 90 | for (const item of paths) { 91 | if (find) { 92 | relative = `${relative}/${item}`; 93 | } 94 | if (item === 'lib') { 95 | find = true; 96 | relative = ''; 97 | } 98 | } 99 | const templateName: string = `${template}.tmpl`; 100 | const upperName = toUpperCase(inputName); 101 | const args = [inputName, upperName, toPrivateCase(upperName), config.appName, relative]; 102 | // load dynamic templates 103 | this.localTemplatesMap = new Map(); 104 | this.localTemplatesMap = await this.loadTemplates( 105 | loc.templateDirectory, 106 | this.localTemplatesMap, 107 | ); 108 | var key = templateName.replace('\\', path.sep); 109 | let resultTemplate = 110 | this.localTemplatesMap && this.localTemplatesMap.has(key) 111 | ? this.localTemplatesMap.get(key)(...args) 112 | : undefined; 113 | if (!resultTemplate) { 114 | /// use template from extension 115 | resultTemplate = this.templatesMap.has(key) ? this.templatesMap.get(key)(...args) : ''; 116 | } 117 | return resultTemplate; 118 | } 119 | } 120 | 121 | function getFiles(dir: string): any[] { 122 | var results = []; 123 | var list = fs.readdirSync(dir); 124 | list.forEach(function (file) { 125 | file = dir + '/' + file; 126 | var stat = fs.statSync(file); 127 | if (stat && stat.isDirectory()) { 128 | /* Recurse into a subdirectory */ 129 | var recFiles = getFiles(file); 130 | results = results.concat(recFiles); 131 | } else { 132 | /* Is a file */ 133 | results.push(file); 134 | } 135 | }); 136 | return results; 137 | } 138 | -------------------------------------------------------------------------------- /src/formatting.ts: -------------------------------------------------------------------------------- 1 | export const toCamelCase = (input: string) => 2 | input.replace(/_([a-z])/gi, (all, letter) => letter.toUpperCase()); 3 | 4 | export const toTileCase = (str: string) => 5 | str.replace(/\w\S*/g, (txt) => { 6 | return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); 7 | }); 8 | 9 | export const toUpperCase = (input: string) => 10 | toCamelCase(input.charAt(0).toUpperCase() + input.slice(1)); 11 | 12 | export const toPrivateCase = (input: string) => input.charAt(0).toLowerCase() + input.slice(1); 13 | -------------------------------------------------------------------------------- /src/ioutil.ts: -------------------------------------------------------------------------------- 1 | import { window, workspace, TextEditor, commands, Uri } from 'vscode'; 2 | import * as fs from 'fs'; 3 | import { IPath } from './models/path'; 4 | import { IFiles } from './models/file'; 5 | import { promisify } from './promisify'; 6 | import path = require('path'); 7 | 8 | const fsWriteFile = promisify(fs.writeFile); 9 | const fsExists = promisify(fs.exists); 10 | const fsMkdir = promisify(fs.mkdir); 11 | const fsCopyFile = promisify(fs.copyFile); 12 | const fsReaddir = promisify(fs.readdir); 13 | 14 | // Get file contents and create the new files in the folder 15 | export const createFiles = async (loc: IPath, files: IFiles[]) => { 16 | try { 17 | await writeFiles(files); 18 | } catch (ex) { 19 | await window.showErrorMessage(`File(s) could not be created. ${ex}`); 20 | } 21 | 22 | return loc.dirPath; 23 | }; 24 | 25 | const writeFiles = async (files: IFiles[]) => { 26 | // tslint:disable-next-line:ter-arrow-parens 27 | const filesPromises: Promise[] = files.map((file) => { 28 | try { 29 | return fsWriteFile(file.name, file.content.toString()); 30 | } catch (_) { 31 | console.log(_); 32 | } 33 | }); 34 | 35 | await Promise.all(filesPromises); 36 | }; 37 | 38 | // Create the new folder 39 | export const createFolder = async (loc: IPath) => { 40 | try { 41 | if (loc.dirName) { 42 | const exists = await fsExists(loc.dirPath); 43 | if (exists) { 44 | throw new Error('Folder already exists'); 45 | } 46 | await fsMkdir(loc.dirPath); 47 | } 48 | } catch (_) { 49 | console.log(_); 50 | } 51 | return loc; 52 | }; 53 | 54 | export const createDirectory = async (pathDir: String) => { 55 | if (pathDir) { 56 | try { 57 | const exists = await fsExists(pathDir); 58 | if (exists) { 59 | return; 60 | } 61 | 62 | await fsMkdir(pathDir); 63 | } catch (_) { 64 | console.log(_); 65 | } 66 | } 67 | 68 | return; 69 | }; 70 | 71 | /** 72 | * Look ma, it's cp -R. 73 | * @param {string} src The path to the thing to copy. 74 | * @param {string} dest The path to the new copy. 75 | */ 76 | export const copyRecursiveSync = async (src: string, dest: string) => { 77 | const exists = fs.existsSync(src); 78 | const stats = exists && fs.statSync(src); 79 | const isDirectory = exists && stats.isDirectory(); 80 | console.log('isDirectory', isDirectory); 81 | if (isDirectory) { 82 | console.log('dest', dest); 83 | await createDirectory(dest); 84 | console.log('finish dest'); 85 | console.log('fs.readdirSync(src)', await fsReaddir(src)); 86 | const items = await fsReaddir(src); 87 | items.forEach((childItemName) => { 88 | console.log('childItemName', childItemName); 89 | copyRecursiveSync(path.join(src, childItemName), path.join(dest, childItemName)); 90 | }); 91 | } else { 92 | console.log('fsCopyFile'); 93 | await fsCopyFile(src, dest); // UPDATE FROM: fs.linkSync(src, dest); 94 | } 95 | }; 96 | -------------------------------------------------------------------------------- /src/models/command.ts: -------------------------------------------------------------------------------- 1 | import { ResourceType } from '../enums/resource-type'; 2 | 3 | export interface ICommand { 4 | fileName: string; 5 | resource: ResourceType; 6 | } 7 | -------------------------------------------------------------------------------- /src/models/config.ts: -------------------------------------------------------------------------------- 1 | export interface IProject { 2 | version: string; 3 | name: string; 4 | } 5 | 6 | export interface IEnvironments { 7 | source: string; 8 | dev: string; 9 | prod: string; 10 | } 11 | 12 | export interface IApp { 13 | outDir?: string; 14 | assets?: string[]; 15 | index?: string; 16 | main?: string; 17 | test?: string; 18 | tsconfig?: string; 19 | environments?: IEnvironments; 20 | } 21 | 22 | export interface IProperties { 23 | flat?: boolean; 24 | [k: string]: any; 25 | } 26 | 27 | export interface IDefaults { 28 | dynamic?: IProperties; 29 | bigpack?: IProperties; 30 | smallpack?: IProperties; 31 | bloc?: IProperties; 32 | event?: IProperties; 33 | model?: IProperties; 34 | page?: IProperties; 35 | provider?: IProperties; 36 | repository?: IProperties; 37 | screen?: IProperties; 38 | state?: IProperties; 39 | index?: IProperties; 40 | } 41 | 42 | export interface IConfig { 43 | defaults: IDefaults; 44 | appName: string; 45 | appPath: string; 46 | } 47 | -------------------------------------------------------------------------------- /src/models/file.ts: -------------------------------------------------------------------------------- 1 | export interface IFiles { 2 | name: string; 3 | content: string; 4 | } 5 | -------------------------------------------------------------------------------- /src/models/option-item.ts: -------------------------------------------------------------------------------- 1 | export class OptionItem { 2 | commands?: string[]; 3 | configPath?: string; 4 | description?: string; 5 | type?: string; 6 | } 7 | -------------------------------------------------------------------------------- /src/models/path.ts: -------------------------------------------------------------------------------- 1 | import { Command, ConfigElement } from '../config-ext'; 2 | 3 | export interface IPath { 4 | fileName: string; 5 | dirName: string; 6 | dirPath: string; 7 | fullPath: string; 8 | rootPath: string; 9 | templateDirectory: string; 10 | command: Command; 11 | } 12 | -------------------------------------------------------------------------------- /src/models/resource-file.ts: -------------------------------------------------------------------------------- 1 | export interface IResourceFile { 2 | name: Function; 3 | condition?: Function; 4 | } 5 | -------------------------------------------------------------------------------- /src/models/resource.ts: -------------------------------------------------------------------------------- 1 | import { IResourceFile } from './resource-file'; 2 | 3 | export interface IResource { 4 | locDirName?: Function; 5 | locDirPath?: Function; 6 | files: string[]; 7 | createFolder?: Function; 8 | declaration?: string; 9 | } 10 | -------------------------------------------------------------------------------- /src/promisify.ts: -------------------------------------------------------------------------------- 1 | export const promisify = (f) => { 2 | return (...params) => 3 | new Promise((resolve, reject) => { 4 | f.apply(this, [ 5 | ...params, 6 | (err, data) => { 7 | if (err) { 8 | reject(err); 9 | } 10 | resolve(data); 11 | }, 12 | ]); 13 | }); 14 | }; 15 | -------------------------------------------------------------------------------- /src/resources.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import { IResource } from './models/resource'; 3 | import { IConfig } from './models/config'; 4 | import { Command, ConfigElement } from './config-ext'; 5 | export class ResourcesDynamic { 6 | static resourcesDynamic(config: ConfigElement): Map { 7 | let result = new Map(); 8 | config.commands.forEach((e) => { 9 | let temp = ResourcesDynamic.resourcesCommand(e); 10 | let newKey = e.key ?? e.files.join(''); 11 | result.set(newKey, temp); 12 | }); 13 | return result; 14 | } 15 | 16 | static resourcesCommand(command: Command): IResource { 17 | let temp = { 18 | locDirName: (loc) => (command.files.length > 1 ? loc.fileName : loc.dirName), 19 | locDirPath: (loc) => path.join(loc.dirPath, loc.dirName), 20 | 21 | files: command.files, 22 | createFolder: () => command.files.length > 1, 23 | }; 24 | 25 | return temp; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /templates/ff.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "base", 3 | "configs": [ 4 | { 5 | "name": "bloc", 6 | "commands": [ 7 | { 8 | "name": "[FF] New Big Pack Bloc", 9 | "templates": ["*"], 10 | "key": "bigpack", 11 | "files": [ 12 | "bloc", 13 | "event", 14 | "index", 15 | "model", 16 | "page", 17 | "provider", 18 | "repository", 19 | "screen", 20 | "state" 21 | ] 22 | }, 23 | { 24 | "name": "[FF] New Small Pack Bloc", 25 | "templates": ["*"], 26 | "key": "smallpack", 27 | "files": ["bloc", "event", "index", "page", "screen", "state"] 28 | }, 29 | { 30 | "name": "[FF] New Bloc", 31 | "templates": ["*"], 32 | "files": ["bloc"] 33 | }, 34 | { 35 | "name": "[FF] New Event", 36 | "templates": ["*"], 37 | "files": ["event"] 38 | }, 39 | { 40 | "name": "[FF] New Model", 41 | "templates": ["*"], 42 | "files": ["model"] 43 | }, 44 | { 45 | "name": "[FF] New Page", 46 | "templates": ["*"], 47 | "files": ["page"] 48 | }, 49 | { 50 | "name": "[FF] New Provider", 51 | "templates": ["*"], 52 | "files": ["provider"] 53 | }, 54 | { 55 | "name": "[FF] New Repository", 56 | "templates": ["*"], 57 | "files": ["repository"] 58 | }, 59 | { 60 | "name": "[FF] New Screen", 61 | "templates": ["*"], 62 | "files": ["screen"] 63 | }, 64 | { 65 | "name": "[FF] New State", 66 | "templates": ["*"], 67 | "files": ["state"] 68 | }, 69 | { 70 | "name": "[FF] New Index", 71 | "templates": ["*"], 72 | "files": ["index"] 73 | }, 74 | { 75 | "name": "[FF] New Navigate(Navme)", 76 | "templates": ["navigate"], 77 | "files": ["navigate"] 78 | } 79 | ] 80 | } 81 | ] 82 | } 83 | -------------------------------------------------------------------------------- /templates/ff_bloc/bloc.tmpl: -------------------------------------------------------------------------------- 1 | import 'package:ff_bloc/ff_bloc.dart'; 2 | 3 | import 'package:${appName}${relative}/index.dart'; 4 | 5 | class ${upperName}Bloc extends FFBloc<${upperName}Event, ${upperName}State> { 6 | ${upperName}Bloc({ 7 | required this.provider, 8 | super.initialState = const ${upperName}State(), 9 | }); 10 | /// Use this for all requests to backend - you can mock it in tests 11 | final ${upperName}Provider provider; 12 | 13 | @override 14 | ${upperName}State onErrorState(Object error) => state.copy(error: error, isLoading: false); 15 | 16 | } 17 | -------------------------------------------------------------------------------- /templates/ff_bloc/event.tmpl: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/widgets.dart'; 4 | import 'package:ff_bloc/ff_bloc.dart'; 5 | 6 | import 'package:${appName}${relative}/index.dart'; 7 | 8 | @immutable 9 | abstract class ${upperName}Event implements FFBlocEvent<${upperName}State, ${upperName}Bloc> {} 10 | 11 | /// Initial Event with load data 12 | class Load${upperName}Event extends ${upperName}Event { 13 | Load${upperName}Event({required this.id}); 14 | final String? id; 15 | 16 | static const String _name = 'Load${upperName}Event'; 17 | 18 | @override 19 | String toString() => _name; 20 | 21 | @override 22 | Stream<${upperName}State> applyAsync({required ${upperName}Bloc bloc}) async* { 23 | // set loading true for show loading 24 | yield bloc.state.copyWithoutError(isLoading: true); 25 | // fetch data 26 | final result = await bloc.provider.fetchAsync(id); 27 | // set data to state 28 | yield bloc.state.copyWithoutError( 29 | isLoading: false, 30 | data: ${upperName}ViewModel(items: result), 31 | ); 32 | } 33 | } 34 | 35 | 36 | class Add${upperName}Event extends ${upperName}Event { 37 | static const String _name = 'Add${upperName}Event'; 38 | 39 | @override 40 | String toString() => _name; 41 | 42 | @override 43 | Stream<${upperName}State> applyAsync({required ${upperName}Bloc bloc}) async* { 44 | yield bloc.state.copyWithoutError(isLoading: true); 45 | final result = await bloc.provider.addMore(bloc.state.data?.items); 46 | yield bloc.state.copyWithoutError( 47 | isLoading: false, 48 | data: ${upperName}ViewModel(items: result), 49 | ); 50 | } 51 | } 52 | 53 | class ErrorYouAwesomeEvent extends YouAwesomeEvent { 54 | static const String _name = 'ErrorYouAwesomeEvent'; 55 | 56 | @override 57 | String toString() => _name; 58 | 59 | @override 60 | Stream applyAsync({required YouAwesomeBloc bloc}) async* { 61 | throw Exception('Test error'); 62 | } 63 | } 64 | 65 | class Clear${upperName}Event extends ${upperName}Event { 66 | static const String _name = 'Clear${upperName}Event'; 67 | 68 | @override 69 | String toString() => _name; 70 | 71 | @override 72 | Stream<${upperName}State> applyAsync({required ${upperName}Bloc bloc}) async* { 73 | yield bloc.state.copyWithoutError(isLoading: true); 74 | yield bloc.state.copyWithoutData( 75 | isLoading: false, 76 | ); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /templates/ff_bloc/index.tmpl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gorniv/vscode-flutter-files/135c8920bb8c40b8d008d44cd3f6b2216bf01775/templates/ff_bloc/index.tmpl -------------------------------------------------------------------------------- /templates/ff_bloc/model.tmpl: -------------------------------------------------------------------------------- 1 | // ignore: depend_on_referenced_packages 2 | import 'package:equatable/equatable.dart'; 3 | 4 | class ${upperName}Model extends Equatable { 5 | const ${upperName}Model({ 6 | required this.name, 7 | }); 8 | final String name; 9 | 10 | @override 11 | List get props => [ name]; 12 | 13 | Map toMap() { 14 | return { 15 | 'name': name, 16 | }; 17 | } 18 | 19 | static ${upperName}Model? fromMap(Map? map) { 20 | if (map == null) { 21 | return null; 22 | } 23 | 24 | return ${upperName}Model( 25 | name: map['name']!.toString(), 26 | ); 27 | } 28 | 29 | } 30 | 31 | class ${upperName}ViewModel extends Equatable { 32 | const ${upperName}ViewModel({ 33 | // TODO(all): add all required constructor parameters 34 | required this.items, 35 | }); 36 | 37 | // TODO(all): declare your fields here 38 | final List<${upperName}Model>? items; 39 | 40 | @override 41 | List get props => [items /*TODO(all): List all fields here*/]; 42 | 43 | // TODO(all): implement copyWith 44 | ${upperName}ViewModel copyWith({ 45 | List<${upperName}Model>? items, 46 | }) { 47 | return ${upperName}ViewModel( 48 | items: items ?? this.items, 49 | ); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /templates/ff_bloc/page.tmpl: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:${appName}${relative}/index.dart'; 3 | 4 | 5 | class ${upperName}Page extends StatefulWidget { 6 | const ${upperName}Page({ 7 | required this.bloc, 8 | super.key 9 | }); 10 | static const String routeName = '/${privateName}'; 11 | 12 | final ${upperName}Bloc? bloc; 13 | 14 | @override 15 | State<${upperName}Page> createState() => _${upperName}PageState(); 16 | } 17 | 18 | class _${upperName}PageState extends State<${upperName}Page> { 19 | 20 | ${upperName}Bloc? _bloc; 21 | ${upperName}Bloc get bloc { 22 | // get it by DI in real code. 23 | _bloc ??= widget.bloc ?? ${upperName}Bloc(); 24 | return _bloc!; 25 | } 26 | 27 | @override 28 | Widget build(BuildContext context) { 29 | return Scaffold( 30 | appBar: AppBar( 31 | centerTitle: true, 32 | title: const Text('${upperName}'), 33 | actions: [ 34 | IconButton( 35 | icon: const Icon(Icons.error), 36 | onPressed: () { 37 | bloc.add(ErrorYouAwesomeEvent()); 38 | }, 39 | ), 40 | IconButton( 41 | icon: const Icon(Icons.add), 42 | onPressed: () { 43 | bloc.add(Add${upperName}Event()); 44 | }, 45 | ), 46 | IconButton( 47 | icon: const Icon(Icons.clear), 48 | onPressed: () { 49 | bloc.add(Clear${upperName}Event()); 50 | }, 51 | ), 52 | ], 53 | ), 54 | body: ${upperName}Screen(bloc: bloc), 55 | ); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /templates/ff_bloc/provider.tmpl: -------------------------------------------------------------------------------- 1 | 2 | import 'dart:async'; 3 | import 'package:${appName}${relative}/index.dart'; 4 | 5 | class ${upperName}Provider { 6 | 7 | Future?> fetchAsync(String? id) async { 8 | // write logic here to send request to server 9 | if (id == null) { 10 | return null; 11 | } 12 | return [${upperName}Model(name: id)]; 13 | } 14 | 15 | 16 | Future?> addMore(List<${upperName}Model>? now) async { 17 | // write logic here to send request to server 18 | final result = [ 19 | ...(now ?? <${upperName}Model>[]), 20 | ${upperName}Model(name: now?.length.toString() ?? '0') 21 | ]; 22 | return result; 23 | } 24 | 25 | } 26 | 27 | -------------------------------------------------------------------------------- /templates/ff_bloc/screen.tmpl: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import 'package:${appName}${relative}/index.dart'; 4 | 5 | 6 | class ${upperName}Screen extends StatefulWidget { 7 | const ${upperName}Screen({ 8 | required this.bloc, 9 | super.key, 10 | }) ; 11 | 12 | @protected 13 | final ${upperName}Bloc bloc; 14 | 15 | @override 16 | State<${upperName}Screen> createState() { 17 | return ${upperName}ScreenState(); 18 | } 19 | } 20 | 21 | class ${upperName}ScreenState extends State<${upperName}Screen> { 22 | 23 | @override 24 | void initState() { 25 | super.initState(); 26 | // load data on init widget if bloc has not data 27 | if (!widget.bloc.state.hasData) { 28 | _load(); 29 | } 30 | } 31 | 32 | @override 33 | void dispose() { 34 | // dispose bloc if you use subscriptions in bloc 35 | super.dispose(); 36 | } 37 | 38 | @override 39 | Widget build(BuildContext context) { 40 | return BlocBuilder<${upperName}Bloc, ${upperName}State>( 41 | bloc: widget.bloc, 42 | builder: ( 43 | BuildContext context, 44 | ${upperName}State currentState, 45 | ) { 46 | // declaration of bloc states 47 | return currentState.when( 48 | onLoading: ()=>const CircularProgressIndicator(), 49 | onEmpty: (data) => _Empty(), 50 | onData: (data) => _BodyList(data: data), 51 | onError: (e) => Center( 52 | child: Column( 53 | children: [ 54 | Text(e.toString()), 55 | TextButton( 56 | onPressed: _load, 57 | child: const Text('ReLoad'), 58 | ) 59 | ], 60 | ), 61 | ), 62 | ); 63 | }, 64 | ); 65 | } 66 | 67 | void _load() { 68 | widget.bloc.add(Load${upperName}Event(id:'1')); 69 | } 70 | 71 | } 72 | 73 | 74 | class _BodyList extends StatefulWidget { 75 | const _BodyList({required this.data}); 76 | 77 | final ${upperName}ViewModel data; 78 | 79 | @override 80 | State<_BodyList> createState() => _BodyListState(); 81 | } 82 | 83 | class _BodyListState extends State<_BodyList> { 84 | 85 | @override 86 | void initState() { 87 | super.initState(); 88 | } 89 | 90 | @override 91 | void dispose() { 92 | super.dispose(); 93 | } 94 | 95 | @override 96 | Widget build(BuildContext context) { 97 | 98 | return CustomScrollView( 99 | // primary: true, 100 | slivers: [ 101 | const SliverToBoxAdapter(child: Divider()), 102 | SliverList( 103 | delegate: SliverChildBuilderDelegate( 104 | (BuildContext context, int index) { 105 | final item = widget.data.items![index]; 106 | if (index == 0) { 107 | return Text('Header $index, id = '+item.name); 108 | } 109 | return Text('Index = $index, id = '+item.name); 110 | }, 111 | childCount: widget.data.items!.length, 112 | ))]); 113 | } 114 | } 115 | 116 | 117 | class _Empty extends StatelessWidget { 118 | @override 119 | Widget build(BuildContext context) { 120 | return Column( 121 | children: [ 122 | Text('Empty'), 123 | ], 124 | ); 125 | } 126 | } -------------------------------------------------------------------------------- /templates/ff_bloc/state.tmpl: -------------------------------------------------------------------------------- 1 | import 'package:ff_bloc/ff_bloc.dart'; 2 | 3 | import 'package:${appName}${relative}/index.dart'; 4 | 5 | class ${upperName}State extends FFState<${upperName}State, ${upperName}ViewModel> { 6 | const ${upperName}State({ 7 | super.version = 0, 8 | super.isLoading = false, 9 | super.data, 10 | super.error, 11 | }); 12 | 13 | @override 14 | StateCopyFactory<${upperName}State, ${upperName}ViewModel> getCopyFactory() => ${upperName}State.new; 15 | } 16 | -------------------------------------------------------------------------------- /templates/mutable/bloc.tmpl: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:developer' as developer; 3 | 4 | import 'package:bloc/bloc.dart'; 5 | import 'package:${appName}${relative}/index.dart'; 6 | 7 | class ${upperName}Bloc extends Bloc<${upperName}Event, ${upperName}State> { 8 | // todo: check singleton for logic in project 9 | // use GetIt for DI in projct 10 | static final ${upperName}Bloc _${privateName}BlocSingleton = ${upperName}Bloc._internal(); 11 | factory ${upperName}Bloc() { 12 | return _${privateName}BlocSingleton; 13 | } 14 | 15 | ${upperName}Bloc._internal(): super(Un${upperName}State(0)){ 16 | on<${upperName}Event>((event, emit) { 17 | return emit.forEach<${upperName}State>( 18 | event.applyAsync(currentState: state, bloc: this), 19 | onData: (state) => state, 20 | onError: (error, stackTrace) { 21 | developer.log('$error', name: '${upperName}Bloc', error: error, stackTrace: stackTrace); 22 | return Error${upperName}State(0, error.toString()); 23 | }, 24 | ); 25 | }); 26 | } 27 | 28 | @override 29 | Future close() async{ 30 | // dispose objects 31 | await super.close(); 32 | } 33 | 34 | @override 35 | ${upperName}State get initialState => Un${upperName}State(0); 36 | 37 | } 38 | -------------------------------------------------------------------------------- /templates/mutable/event.tmpl: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:developer' as developer; 3 | 4 | import 'package:${appName}${relative}/index.dart'; 5 | import 'package:meta/meta.dart'; 6 | 7 | @immutable 8 | abstract class ${upperName}Event { 9 | Stream<${upperName}State> applyAsync( 10 | {${upperName}State currentState, ${upperName}Bloc bloc}); 11 | final ${upperName}Repository _${privateName}Repository = ${upperName}Repository(); 12 | } 13 | 14 | class Un${upperName}Event extends ${upperName}Event { 15 | @override 16 | Stream<${upperName}State> applyAsync({${upperName}State? currentState, ${upperName}Bloc? bloc}) async* { 17 | yield Un${upperName}State(0); 18 | } 19 | } 20 | 21 | class Load${upperName}Event extends ${upperName}Event { 22 | 23 | final bool isError; 24 | @override 25 | String toString() => 'Load${upperName}Event'; 26 | 27 | Load${upperName}Event(this.isError); 28 | 29 | @override 30 | Stream<${upperName}State> applyAsync( 31 | {${upperName}State? currentState, ${upperName}Bloc? bloc}) async* { 32 | try { 33 | yield Un${upperName}State(0); 34 | await Future.delayed(const Duration(seconds: 1)); 35 | _${privateName}Repository.test(isError); 36 | yield In${upperName}State(0, 'Hello world'); 37 | } catch (_, stackTrace) { 38 | developer.log('$_', name: 'Load${upperName}Event', error: _, stackTrace: stackTrace); 39 | yield Error${upperName}State(0, _.toString()); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /templates/mutable/index.tmpl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gorniv/vscode-flutter-files/135c8920bb8c40b8d008d44cd3f6b2216bf01775/templates/mutable/index.tmpl -------------------------------------------------------------------------------- /templates/mutable/model.tmpl: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | /// generate by https://javiercbk.github.io/json_to_dart/ 4 | class Autogenerated${upperName} { 5 | final List<${upperName}Model> results; 6 | 7 | Autogenerated${upperName}({required this.results}); 8 | 9 | factory Autogenerated${upperName}.fromJson(Map json) { 10 | var temp = []; 11 | if (json['results'] != null) { 12 | temp = <${upperName}Model>[]; 13 | json['results'].forEach((v) { 14 | temp.add(${upperName}Model.fromJson(v as Map)); 15 | }); 16 | } 17 | return Autogenerated${upperName}(results: temp); 18 | } 19 | 20 | Map toJson() { 21 | final data = {}; 22 | data['results'] = results.map((v) => v.toJson()).toList(); 23 | return data; 24 | } 25 | } 26 | 27 | class ${upperName}Model extends Equatable { 28 | final int id; 29 | final String name; 30 | 31 | ${upperName}Model(this.id, this.name); 32 | 33 | @override 34 | List get props => [id, name]; 35 | 36 | factory ${upperName}Model.fromJson(Map json) { 37 | return ${upperName}Model(json['id'] as int, json['name'] as String); 38 | } 39 | 40 | Map toJson() { 41 | final data = {}; 42 | data['id'] = id; 43 | data['name'] = name; 44 | return data; 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /templates/mutable/page.tmpl: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:${appName}${relative}/index.dart'; 3 | 4 | class ${upperName}Page extends StatefulWidget { 5 | static const String routeName = '/${privateName}'; 6 | 7 | @override 8 | _${upperName}PageState createState() => _${upperName}PageState(); 9 | } 10 | 11 | class _${upperName}PageState extends State<${upperName}Page> { 12 | final _${privateName}Bloc = ${upperName}Bloc(); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return Scaffold( 17 | appBar: AppBar( 18 | title: Text('${upperName}'), 19 | ), 20 | body: ${upperName}Screen(${privateName}Bloc: _${privateName}Bloc), 21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /templates/mutable/provider.tmpl: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | class ${upperName}Provider { 4 | Future loadAsync(String token) async { 5 | /// write from keystore/keychain 6 | await Future.delayed(Duration(seconds: 2)); 7 | } 8 | 9 | Future saveAsync(String token) async { 10 | /// write from keystore/keychain 11 | await Future.delayed(Duration(seconds: 2)); 12 | } 13 | 14 | void test(bool isError) { 15 | if (isError == true){ 16 | throw Exception('manual error'); 17 | } 18 | } 19 | } 20 | 21 | -------------------------------------------------------------------------------- /templates/mutable/repository.tmpl: -------------------------------------------------------------------------------- 1 | import 'package:${appName}${relative}/index.dart'; 2 | 3 | class ${upperName}Repository { 4 | final ${upperName}Provider _${privateName}Provider = ${upperName}Provider(); 5 | 6 | ${upperName}Repository(); 7 | 8 | void test(bool isError) { 9 | _${privateName}Provider.test(isError); 10 | } 11 | } -------------------------------------------------------------------------------- /templates/mutable/screen.tmpl: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import 'package:${appName}${relative}/index.dart'; 4 | 5 | class ${upperName}Screen extends StatefulWidget { 6 | const ${upperName}Screen({ 7 | required ${upperName}Bloc ${privateName}Bloc, 8 | Key? key, 9 | }) : _${privateName}Bloc = ${privateName}Bloc, 10 | super(key: key); 11 | 12 | final ${upperName}Bloc _${privateName}Bloc; 13 | 14 | @override 15 | ${upperName}ScreenState createState() { 16 | return ${upperName}ScreenState(); 17 | } 18 | } 19 | 20 | class ${upperName}ScreenState extends State<${upperName}Screen> { 21 | ${upperName}ScreenState(); 22 | 23 | @override 24 | void initState() { 25 | super.initState(); 26 | _load(); 27 | } 28 | 29 | @override 30 | void dispose() { 31 | super.dispose(); 32 | } 33 | 34 | @override 35 | Widget build(BuildContext context) { 36 | return BlocBuilder<${upperName}Bloc, ${upperName}State>( 37 | bloc: widget._${privateName}Bloc, 38 | builder: ( 39 | BuildContext context, 40 | ${upperName}State currentState, 41 | ) { 42 | if (currentState is Un${upperName}State) { 43 | return Center( 44 | child: CircularProgressIndicator(), 45 | ); 46 | } 47 | if (currentState is Error${upperName}State) { 48 | return Center( 49 | child: Column( 50 | mainAxisAlignment: MainAxisAlignment.center, 51 | children: [ 52 | Text(currentState.errorMessage), 53 | Padding( 54 | padding: const EdgeInsets.only(top: 32.0), 55 | child: ElevatedButton( 56 | style: ElevatedButton.styleFrom( 57 | backgroundColor: Colors.blue, 58 | ), 59 | child: Text('reload'), 60 | onPressed: _load, 61 | ), 62 | ), 63 | ], 64 | )); 65 | } 66 | if (currentState is In${upperName}State) { 67 | return Center( 68 | child: Column( 69 | mainAxisAlignment: MainAxisAlignment.center, 70 | children: [ 71 | Text(currentState.hello), 72 | const Text('Flutter files: done'), 73 | Padding( 74 | padding: const EdgeInsets.only(top: 32.0), 75 | child: ElevatedButton( 76 | style: ElevatedButton.styleFrom( 77 | backgroundColor: Colors.red, 78 | ), 79 | child: Text('throw error'), 80 | onPressed: () => _load(true), 81 | ), 82 | ), 83 | ], 84 | ), 85 | ); 86 | } 87 | return Center( 88 | child: CircularProgressIndicator(), 89 | ); 90 | 91 | }); 92 | } 93 | 94 | void _load([bool isError = false]) { 95 | widget._${privateName}Bloc.add(Load${upperName}Event(isError)); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /templates/mutable/state.tmpl: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | abstract class ${upperName}State extends Equatable { 4 | ${upperName}State(this.version); 5 | 6 | /// notify change state without deep clone state 7 | final int version; 8 | 9 | /// Copy object for use in action 10 | /// if need use deep clone 11 | ${upperName}State getStateCopy(); 12 | 13 | ${upperName}State getNewVersion(); 14 | 15 | @override 16 | List get props => [version]; 17 | } 18 | 19 | /// UnInitialized 20 | class Un${upperName}State extends ${upperName}State { 21 | 22 | Un${upperName}State(int version) : super(version); 23 | 24 | @override 25 | String toString() => 'Un${upperName}State'; 26 | 27 | @override 28 | Un${upperName}State getStateCopy() { 29 | return Un${upperName}State(0); 30 | } 31 | 32 | @override 33 | Un${upperName}State getNewVersion() { 34 | return Un${upperName}State(version+1); 35 | } 36 | } 37 | 38 | /// Initialized 39 | class In${upperName}State extends ${upperName}State { 40 | 41 | In${upperName}State(int version, this.hello) : super(version); 42 | 43 | final String hello; 44 | 45 | @override 46 | String toString() => 'In${upperName}State $hello'; 47 | 48 | @override 49 | In${upperName}State getStateCopy() { 50 | return In${upperName}State(version, hello); 51 | } 52 | 53 | @override 54 | In${upperName}State getNewVersion() { 55 | return In${upperName}State(version+1, hello); 56 | } 57 | 58 | @override 59 | List get props => [version, hello]; 60 | } 61 | 62 | class Error${upperName}State extends ${upperName}State { 63 | Error${upperName}State(int version, this.errorMessage): super(version); 64 | 65 | final String errorMessage; 66 | 67 | @override 68 | String toString() => 'Error${upperName}State'; 69 | 70 | @override 71 | Error${upperName}State getStateCopy() { 72 | return Error${upperName}State(version, errorMessage); 73 | } 74 | 75 | @override 76 | Error${upperName}State getNewVersion() { 77 | return Error${upperName}State(version+1, 78 | errorMessage); 79 | } 80 | 81 | @override 82 | List get props => [version, errorMessage]; 83 | } 84 | -------------------------------------------------------------------------------- /templates/navigate/navigate.tmpl: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:navme/navme.dart'; 3 | import 'package:navme/helpers.dart'; 4 | 5 | import 'index.dart'; 6 | 7 | class ${upperName}Navigate { 8 | // base path 9 | static String path = '${privateName}'; 10 | 11 | // config for configurate Router 12 | static RouteConfig routeConfig = RouteConfig( 13 | state: (Uri? uri) => RouteState(uri: path.toUri()), 14 | // condition for using this page 15 | isThisPage: (RouteState state) { 16 | if (state?.firstPath == path) { 17 | return true; 18 | } 19 | return false; 20 | }, 21 | // settigs from url 22 | settings: (RouteState state) { 23 | return null; 24 | }, 25 | // get Page for Router 26 | page: ({RouteState? state}) { 27 | return MaterialPage( 28 | key: const ValueKey('${upperName}Page'), 29 | child: ${upperName}Page(), 30 | name: '${upperName}Page'); 31 | }, 32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /templates/simple/bloc.tmpl: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:developer' as developer; 3 | 4 | import 'package:bloc/bloc.dart'; 5 | import 'package:${appName}${relative}/index.dart'; 6 | 7 | class ${upperName}Bloc extends Bloc<${upperName}Event, ${upperName}State> { 8 | 9 | ${upperName}Bloc(${upperName}State initialState) : super(initialState){ 10 | on<${upperName}Event>((event, emit) { 11 | return emit.forEach<${upperName}State>( 12 | event.applyAsync(currentState: state, bloc: this), 13 | onData: (state) => state, 14 | onError: (error, stackTrace) { 15 | developer.log('$error', name: '${upperName}Bloc', error: error, stackTrace: stackTrace); 16 | return Error${upperName}State(error.toString()); 17 | }, 18 | ); 19 | }); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /templates/simple/event.tmpl: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:developer' as developer; 3 | 4 | import 'package:${appName}${relative}/index.dart'; 5 | import 'package:meta/meta.dart'; 6 | 7 | @immutable 8 | abstract class ${upperName}Event { 9 | Stream<${upperName}State> applyAsync( 10 | {${upperName}State currentState, ${upperName}Bloc bloc}); 11 | } 12 | 13 | class Un${upperName}Event extends ${upperName}Event { 14 | @override 15 | Stream<${upperName}State> applyAsync({${upperName}State? currentState, ${upperName}Bloc? bloc}) async* { 16 | yield Un${upperName}State(); 17 | } 18 | } 19 | 20 | class Load${upperName}Event extends ${upperName}Event { 21 | 22 | @override 23 | Stream<${upperName}State> applyAsync( 24 | {${upperName}State? currentState, ${upperName}Bloc? bloc}) async* { 25 | try { 26 | yield Un${upperName}State(); 27 | await Future.delayed(const Duration(seconds: 1)); 28 | yield In${upperName}State('Hello world'); 29 | } catch (_, stackTrace) { 30 | developer.log('$_', name: 'Load${upperName}Event', error: _, stackTrace: stackTrace); 31 | yield Error${upperName}State( _.toString()); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /templates/simple/index.tmpl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gorniv/vscode-flutter-files/135c8920bb8c40b8d008d44cd3f6b2216bf01775/templates/simple/index.tmpl -------------------------------------------------------------------------------- /templates/simple/model.tmpl: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | /// use https://marketplace.visualstudio.com/items?itemName=BendixMa.dart-data-class-generator 4 | class ${upperName}Model extends Equatable { 5 | final int id; 6 | final String name; 7 | 8 | ${upperName}Model(this.id, this.name); 9 | 10 | @override 11 | List get props => [id, name]; 12 | 13 | } 14 | -------------------------------------------------------------------------------- /templates/simple/page.tmpl: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:${appName}${relative}/index.dart'; 3 | 4 | class ${upperName}Page extends StatefulWidget { 5 | static const String routeName = '/${privateName}'; 6 | 7 | @override 8 | _${upperName}PageState createState() => _${upperName}PageState(); 9 | } 10 | 11 | class _${upperName}PageState extends State<${upperName}Page> { 12 | final _${privateName}Bloc = ${upperName}Bloc(Un${upperName}State()); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return Scaffold( 17 | appBar: AppBar( 18 | title: Text('${upperName}'), 19 | ), 20 | body: ${upperName}Screen(${privateName}Bloc: _${privateName}Bloc), 21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /templates/simple/provider.tmpl: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | class ${upperName}Provider { 4 | Future loadAsync(String token) async { 5 | /// write from keystore/keychain 6 | await Future.delayed(Duration(seconds: 2)); 7 | } 8 | 9 | Future saveAsync(String token) async { 10 | /// write from keystore/keychain 11 | await Future.delayed(Duration(seconds: 2)); 12 | } 13 | 14 | void test(bool isError) { 15 | if (isError == true){ 16 | throw Exception('manual error'); 17 | } 18 | } 19 | } 20 | 21 | -------------------------------------------------------------------------------- /templates/simple/repository.tmpl: -------------------------------------------------------------------------------- 1 | import 'package:${appName}${relative}/index.dart'; 2 | 3 | class ${upperName}Repository { 4 | final ${upperName}Provider _${privateName}Provider = ${upperName}Provider(); 5 | 6 | ${upperName}Repository(); 7 | 8 | void test(bool isError) { 9 | _${privateName}Provider.test(isError); 10 | } 11 | } -------------------------------------------------------------------------------- /templates/simple/screen.tmpl: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import 'package:${appName}${relative}/index.dart'; 4 | 5 | class ${upperName}Screen extends StatefulWidget { 6 | const ${upperName}Screen({ 7 | required ${upperName}Bloc ${privateName}Bloc, 8 | Key? key, 9 | }) : _${privateName}Bloc = ${privateName}Bloc, 10 | super(key: key); 11 | 12 | final ${upperName}Bloc _${privateName}Bloc; 13 | 14 | @override 15 | ${upperName}ScreenState createState() { 16 | return ${upperName}ScreenState(); 17 | } 18 | } 19 | 20 | class ${upperName}ScreenState extends State<${upperName}Screen> { 21 | ${upperName}ScreenState(); 22 | 23 | @override 24 | void initState() { 25 | super.initState(); 26 | _load(); 27 | } 28 | 29 | @override 30 | void dispose() { 31 | super.dispose(); 32 | } 33 | 34 | @override 35 | Widget build(BuildContext context) { 36 | return BlocBuilder<${upperName}Bloc, ${upperName}State>( 37 | bloc: widget._${privateName}Bloc, 38 | builder: ( 39 | BuildContext context, 40 | ${upperName}State currentState, 41 | ) { 42 | if (currentState is Un${upperName}State) { 43 | return Center( 44 | child: CircularProgressIndicator(), 45 | ); 46 | } 47 | if (currentState is Error${upperName}State) { 48 | return Center( 49 | child: Column( 50 | mainAxisAlignment: MainAxisAlignment.center, 51 | children: [ 52 | Text(currentState.errorMessage ), 53 | Padding( 54 | padding: const EdgeInsets.only(top: 32.0), 55 | child: RaisedButton( 56 | color: Colors.blue, 57 | child: Text('reload'), 58 | onPressed: _load, 59 | ), 60 | ), 61 | ], 62 | )); 63 | } 64 | if (currentState is In${upperName}State) { 65 | return Center( 66 | child: Column( 67 | mainAxisAlignment: MainAxisAlignment.center, 68 | children: [ 69 | Text(currentState.hello), 70 | ], 71 | ), 72 | ); 73 | } 74 | return Center( 75 | child: CircularProgressIndicator(), 76 | ); 77 | 78 | }); 79 | } 80 | 81 | void _load() { 82 | widget._${privateName}Bloc.add(Load${upperName}Event()); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /templates/simple/state.tmpl: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | 3 | abstract class ${upperName}State extends Equatable { 4 | ${upperName}State(); 5 | 6 | @override 7 | List get props => []; 8 | } 9 | 10 | /// UnInitialized 11 | class Un${upperName}State extends ${upperName}State { 12 | 13 | Un${upperName}State(); 14 | 15 | @override 16 | String toString() => 'Un${upperName}State'; 17 | } 18 | 19 | /// Initialized 20 | class In${upperName}State extends ${upperName}State { 21 | In${upperName}State(this.hello); 22 | 23 | final String hello; 24 | 25 | @override 26 | String toString() => 'In${upperName}State $hello'; 27 | 28 | @override 29 | List get props => [hello]; 30 | } 31 | 32 | class Error${upperName}State extends ${upperName}State { 33 | Error${upperName}State(this.errorMessage); 34 | 35 | final String errorMessage; 36 | 37 | @override 38 | String toString() => 'Error${upperName}State'; 39 | 40 | @override 41 | List get props => [errorMessage]; 42 | } 43 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "outDir": "out", 6 | "lib": [ 7 | "es6" 8 | ], 9 | "sourceMap": true, 10 | "rootDir": "src", 11 | // "strict": true /* enable all strict type-checking options */ 12 | /* Additional Checks */ 13 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 14 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 15 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 16 | }, 17 | "exclude": [ 18 | "node_modules", 19 | ".vscode-test" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint-config-airbnb", 3 | "rules": { 4 | "max-line-length": { 5 | "options": [ 6 | 500 7 | ] 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /vsc-extension-quickstart.md: -------------------------------------------------------------------------------- 1 | # Welcome to your VS Code Extension 2 | 3 | ## What's in the folder 4 | 5 | * This folder contains all of the files necessary for your extension. 6 | * `package.json` - this is the manifest file in which you declare your extension and command. 7 | * The sample plugin registers a command and defines its title and command name. With this information VS Code can show the command in the command palette. It doesn’t yet need to load the plugin. 8 | * `src/extension.ts` - this is the main file where you will provide the implementation of your command. 9 | * The file exports one function, `activate`, which is called the very first time your extension is activated (in this case by executing the command). Inside the `activate` function we call `registerCommand`. 10 | * We pass the function containing the implementation of the command as the second parameter to `registerCommand`. 11 | 12 | ## Get up and running straight away 13 | 14 | * Press `F5` to open a new window with your extension loaded. 15 | * Run your command from the command palette by pressing (`Ctrl+Shift+P` or `Cmd+Shift+P` on Mac) and typing `Hello World`. 16 | * Set breakpoints in your code inside `src/extension.ts` to debug your extension. 17 | * Find output from your extension in the debug console. 18 | 19 | ## Make changes 20 | 21 | * You can relaunch the extension from the debug toolbar after changing code in `src/extension.ts`. 22 | * You can also reload (`Ctrl+R` or `Cmd+R` on Mac) the VS Code window with your extension to load your changes. 23 | 24 | ## Explore the API 25 | 26 | * You can open the full set of our API when you open the file `node_modules/vscode/vscode.d.ts`. 27 | 28 | ## Run tests 29 | 30 | * Open the debug viewlet (`Ctrl+Shift+D` or `Cmd+Shift+D` on Mac) and from the launch configuration dropdown pick `Extension Tests`. 31 | * Press `F5` to run the tests in a new window with your extension loaded. 32 | * See the output of the test result in the debug console. 33 | * Make changes to `test/extension.test.ts` or create new test files inside the `test` folder. 34 | * By convention, the test runner will only consider files matching the name pattern `**.test.ts`. 35 | * You can create folders inside the `test` folder to structure your tests any way you want. 36 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | //@ts-check 2 | 3 | 'use strict'; 4 | 5 | const path = require('path'); 6 | 7 | /**@type {import('webpack').Configuration}*/ 8 | const config = { 9 | target: 'node', // vscode extensions run in a Node.js-context 📖 -> https://webpack.js.org/configuration/node/ 10 | 11 | entry: './src/extension.ts', // the entry point of this extension, 📖 -> https://webpack.js.org/configuration/entry-context/ 12 | output: { 13 | // the bundle is stored in the 'dist' folder (check package.json), 📖 -> https://webpack.js.org/configuration/output/ 14 | path: path.resolve(__dirname, 'out'), 15 | filename: 'extension.js', 16 | libraryTarget: 'commonjs2', 17 | devtoolModuleFilenameTemplate: '../[resource-path]', 18 | }, 19 | devtool: 'source-map', 20 | externals: { 21 | 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/ 22 | }, 23 | resolve: { 24 | // support reading TypeScript and JavaScript files, 📖 -> https://github.com/TypeStrong/ts-loader 25 | extensions: ['.ts', '.js'], 26 | }, 27 | module: { 28 | rules: [ 29 | { 30 | test: /\.ts$/, 31 | exclude: /node_modules/, 32 | use: [ 33 | { 34 | loader: 'ts-loader', 35 | }, 36 | ], 37 | }, 38 | ], 39 | }, 40 | node: { 41 | __dirname: false, 42 | }, 43 | }; 44 | module.exports = config; 45 | --------------------------------------------------------------------------------