├── .gitignore ├── .travis.yml ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── .vscodeignore ├── LICENSE ├── README.md ├── docs └── README.md ├── grammars ├── apex.json ├── log.json └── visualforce.json ├── icon.png ├── languages ├── apex-language-configuration.json └── visualforce-language-configuration.json ├── package-lock.json ├── package.json ├── src ├── extension.ts ├── mavensMateExtension.ts ├── mavensmate │ ├── commands │ │ ├── baseCommand.ts │ │ ├── cleanProject.ts │ │ ├── clientCommand.ts │ │ ├── compileAllTabs.ts │ │ ├── compileFile.ts │ │ ├── compileProject.ts │ │ ├── convertProject.ts │ │ ├── createResourceBundle.ts │ │ ├── deleteFile.ts │ │ ├── deploy.ts │ │ ├── deployResourceBundle.ts │ │ ├── editProject.ts │ │ ├── executeApex.ts │ │ ├── executeSoql.ts │ │ ├── flushLogs.ts │ │ ├── flushSoql.ts │ │ ├── forceCompileFile.ts │ │ ├── getCoverage.ts │ │ ├── getOrgWideCoverage.ts │ │ ├── hideCoverageCommand.ts │ │ ├── index.ts │ │ ├── indexMetadata.ts │ │ ├── newApexClass.ts │ │ ├── newApexScript.ts │ │ ├── newApexTrigger.ts │ │ ├── newLightningApp.ts │ │ ├── newLightningComponent.ts │ │ ├── newLightningEvent.ts │ │ ├── newLightningInterface.ts │ │ ├── newLightningTokens.ts │ │ ├── newProject.ts │ │ ├── newVisualforceComponent.ts │ │ ├── newVisualforcePage.ts │ │ ├── oAuthProject.ts │ │ ├── openGlobalSettings.ts │ │ ├── openMetadata.ts │ │ ├── openProject.ts │ │ ├── openSalesforce.ts │ │ ├── openSettings.ts │ │ ├── openUI.ts │ │ ├── pathsCommand.ts │ │ ├── refreshFile.ts │ │ ├── refreshFolder.ts │ │ ├── runApexScript.ts │ │ ├── runTests.ts │ │ ├── runTestsAsync.ts │ │ ├── runTestsMethods.ts │ │ ├── startLogging.ts │ │ ├── stopLogging.ts │ │ └── toggleOutput.ts │ ├── handlers │ │ ├── compileResponseHandler.ts │ │ └── parsers │ │ │ └── compileResultParser.ts │ ├── mavensMateAppConfig.ts │ ├── mavensMateClient.ts │ └── projectSettings.ts ├── vscode │ ├── commandRegistrar.ts │ ├── diagnosticFactory.ts │ ├── mavensMateChannel.ts │ ├── mavensMateCodeCoverage.ts │ ├── mavensMateConfiguration.ts │ ├── mavensMateDiagnostics.ts │ ├── mavensMateStatus.ts │ ├── projectQuickPick.ts │ ├── resourceBundleQuickPick.ts │ └── staticResourceQuickPick.ts └── workspace │ ├── componentPath.ts │ ├── jsonFile.ts │ ├── operatingSystem.ts │ ├── projectList.ts │ ├── readDirAsync.ts │ ├── resourceBundleList.ts │ └── staticResourceList.ts ├── test ├── index.ts ├── mavensmate │ ├── mavensMateAppConfig.test.ts │ ├── mavensMateClient.test.ts │ └── projectSettings.test.ts ├── testWorkspace │ ├── .vscode │ │ └── settings.json │ ├── sample.txt │ └── testApexClass.cls ├── vscode │ ├── commandRegistrar.test.ts │ ├── mavensMateStatus.test.ts │ ├── projectQuickPick.test.ts │ └── testExtensionContext.ts └── workspace │ ├── jsonFile.test.ts │ ├── operatingSystem.test.ts │ └── projectList.test.ts ├── tsconfig.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | node_modules 3 | .DS_Store 4 | *.vsix 5 | .vscode-test 6 | .sfdx/tools/apex.db 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - "node" 5 | 6 | cache: 7 | yarn: true 8 | directories: 9 | - node_modules 10 | 11 | os: 12 | - osx 13 | - linux 14 | 15 | addons: 16 | apt: 17 | sources: 18 | - ubuntu-toolchain-r-test 19 | packages: 20 | - g++-4.9 21 | 22 | before_install: 23 | - if [ $TRAVIS_OS_NAME == "linux" ]; then 24 | export CXX="g++-4.9" CC="gcc-4.9" DISPLAY=:99.0; 25 | sh -e /etc/init.d/xvfb start; 26 | sleep 3; 27 | fi 28 | 29 | install: 30 | - yarn install 31 | - yarn run vscode:prepublish 32 | 33 | script: 34 | - yarn test --silent 35 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | { 3 | "version": "0.1.0", 4 | "configurations": [ 5 | { 6 | "name": "Launch Extension", 7 | "type": "extensionHost", 8 | "request": "launch", 9 | "runtimeExecutable": "${execPath}", 10 | "args": ["--extensionDevelopmentPath=${workspaceRoot}" ], 11 | "stopOnEntry": false, 12 | "sourceMaps": true, 13 | "outFiles": [ "${workspaceRoot}/out/src/**/*.js" ], 14 | "preLaunchTask": "npm" 15 | }, 16 | { 17 | "name": "Launch Tests", 18 | "type": "extensionHost", 19 | "request": "launch", 20 | "runtimeExecutable": "${execPath}", 21 | "args": ["--extensionDevelopmentPath=${workspaceRoot}", "${workspaceRoot}/test/testWorkspace", "--extensionTestsPath=${workspaceRoot}/out/test" ], 22 | "stopOnEntry": false, 23 | "sourceMaps": true, 24 | "outFiles": [ "${workspaceRoot}/out/test/**/*.js" ], 25 | "preLaunchTask": "npm" 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.exclude": { 4 | "out": true // set this to true to hide the "out" folder with the compiled JS files 5 | }, 6 | "search.exclude": { 7 | "out": true // set this to false to include "out" folder in search results 8 | }, 9 | "typescript.tsdk": "./node_modules/typescript/lib" // we want to use the TS server from our node_modules folder to control its version 10 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // Available variables which can be used inside of strings. 2 | // ${workspaceRoot}: the root folder of the team 3 | // ${file}: the current opened file 4 | // ${fileBasename}: the current opened file's basename 5 | // ${fileDirname}: the current opened file's dirname 6 | // ${fileExtname}: the current opened file's extension 7 | // ${cwd}: the current working directory of the spawned process 8 | 9 | // A task runner that calls a custom npm script that compiles the extension. 10 | { 11 | "version": "0.1.0", 12 | 13 | // we want to run npm 14 | "command": "npm", 15 | 16 | // the command is a shell script 17 | "isShellCommand": true, 18 | 19 | // show the output window only if unrecognized errors occur. 20 | "showOutput": "silent", 21 | 22 | // we run the custom script "compile" as defined in package.json 23 | "args": ["run", "compile", "--loglevel", "silent"], 24 | 25 | // The tsc compiler is started in watching mode 26 | "isWatching": true, 27 | 28 | // use the standard tsc in watch mode problem matcher to find compile problems in the output. 29 | "problemMatcher": "$tsc-watch" 30 | } -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | typings/** 3 | out/test/** 4 | test/** 5 | src/** 6 | **/*.map 7 | .gitignore 8 | tsconfig.json 9 | vsc-extension-quickstart.md 10 | **/*.ts 11 | !file.ts 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Joe Ferraro 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 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MavensMate for VS Code 2 | [![Build Status](https://travis-ci.org/kidtsunami/MavensMate-VisualStudioCode.svg?branch=master)](https://travis-ci.org/kidtsunami/MavensMate-VisualStudioCode) 3 | 4 | [MavensMate](http://mavensmate.com/) plugin for the Visual Studio Code editor that aims to replicate the functionality of the Eclipse-based Salesforce IDE. Its goal is to allow developers to work inside Sublime Text for all their Salesforce-related tasks. 5 | 6 | * Create & Edit Salesforce.com projects with specific package metadata 7 | * Create & compile Apex Classes, Apex Trigger, Visualforce Pages, Visualforce Components 8 | * Create & Edit Lightning Components (v7 only) 9 | * Retrieve & compile other types of Salesforce.com metadata 10 | * Run Apex test methods and visualize test successes/failures & coverage 11 | * Deploy metadata to other Salesforce.com orgs 12 | * Apex Execute Anonymous 13 | * Stream Apex Logs to your local filesystem 14 | * Apex & Visualforce Code Assist 15 | 16 | ## Issues 17 | 18 | All issues are managed by the [central MavensMate project](https://github.com/joeferraro/MavensMate) 19 | 20 | ## Install 21 | 22 | ### Prerequisites 23 | 24 | - VS Code [https://code.visualstudio.com/](https://code.visualstudio.com/) 25 | - OSX: Add VS Code to Path (Run `Shell Command : Install code in PATH` from command palette) 26 | - MavensMate Desktop Beta.6 **(must be running in order for MavensMate for VS Code to function)** [https://github.com/joeferraro/MavensMate-Desktop/releases/tag/v0.0.11-beta.6](https://github.com/joeferraro/MavensMate-Desktop/releases/tag/v0.0.11-beta.6) 27 | 28 | ### Plugin Installation 29 | 30 | 1. Open VS Code 31 | 2. Run `Extensions: Install Extension` command 32 | - [Running commands from VS Code](https://code.visualstudio.com/docs/editor/codebasics#_command-palette) 33 | 3. Search for `MavensMate` 34 | 4. Hit `Enter` 35 | 36 | ## Setup 37 | 38 | ### Important Settings (Configured in MavensMate Desktop) 39 | 40 | #### Workspaces (mm_workspace) 41 | 42 | You may set `mm_workspace` to a single path on your local filesystem or an array of paths. 43 | 44 | ##### Examples 45 | 46 | ###### Array of workspaces 47 | 48 | ``` 49 | "mm_workspace" : [ 50 | "/Users/darylshaber/Desktop/my-cool-folder", 51 | "/Users/darylshaber/Workspaces/my-mavensmate-workspace" 52 | ], 53 | ``` 54 | 55 | ###### Single workspace 56 | 57 | ``` 58 | "mm_workspace" : "/Users/darylshaber/Desktop/my-cool-folder", 59 | ``` 60 | 61 | **Windows users:** You must use escaped backslashes to set your workspaces: 62 | 63 | ``` 64 | "mm_workspace" : [ 65 | "\\Users\\darylshaber\\Desktop\\my-cool-folder", 66 | "\\Users\\darylshaber\\Workspaces\\my-mavensmate-workspace" 67 | ], 68 | ``` 69 | 70 | ## Plugin Development 71 | 72 | 1. Open the extension project in VS Code 73 | 2. Open a Terminal with ```ctrl+` ``` 74 | 3. Run ```npm install``` 75 | 4. Develop! [VS Code Extensibility Reference](https://code.visualstudio.com/docs/extensionAPI/overview) 76 | 77 | ## Organization 78 | The point of entry for the code is src/extension.ts where the extension is registered. 79 | ### src 80 | Is the main directory of code for this project. 81 | #### mavensmate 82 | Code specifically relevant to the MavensMate app. Should avoid referencing vscode. 83 | #### vscode 84 | Provides an interface to vscode, encapsulating some concepts relevant to mavensmate. 85 | #### workspace 86 | Encapsulates code relevant to the workspace where projects are contained and the file system. 87 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | MavensMate for VS Code 2 | ------------ 3 | -------------------------------------------------------------------------------- /grammars/apex.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileTypes": [ 3 | "apex", 4 | "cls", 5 | "trigger", 6 | "tgr" 7 | ], 8 | "foldingStartMarker": "/\\*\\*|\\{\\s*$", 9 | "foldingStopMarker": "\\*\\*/|^\\s*\\}", 10 | "name": "Apex", 11 | "patterns": [ 12 | { 13 | "match": "\\b(?i:package|trigger|class|interface)\\b", 14 | "name": "storage.type.apex" 15 | }, 16 | { 17 | "match": "\\b(?i:public|private|global|webservice|abstract|virtual|protected|static)\\b", 18 | "name": "keyword.control.scope.apex" 19 | }, 20 | { 21 | "match": "\\b(?i:istest|future|deprecrated|readonly|remoteaction)\\b", 22 | "name": "keyword.control.annotation.apex" 23 | }, 24 | { 25 | "match": "\\b(?i:commit|rollback)\\b", 26 | "name": "keyword.control.transaction.apex" 27 | }, 28 | { 29 | "match": "\\b(?i:insert|update|delete|upsert)\\b", 30 | "name": "keyword.control.trigger.dml.apex" 31 | }, 32 | { 33 | "match": "\\b(?i:try|catch|finally|throw)\\b", 34 | "name": "keyword.control.catch-exception.apex" 35 | }, 36 | { 37 | "match": "\\b(?i:null|system|apex|label|apexpages|userinfo|schema)\\b", 38 | "name": "keyword.control.system.apex" 39 | }, 40 | { 41 | "match": "\\=|\\!\\=|\\>|\\<|\\&\\&|\\|\\|\\+", 42 | "name": "keyword.control.evaluation.apex" 43 | }, 44 | { 45 | "include": "#support-type-built-ins-apex" 46 | }, 47 | { 48 | "begin": "\\[", 49 | "end": "\\]", 50 | "name": "string.quoted.brackets.soql.apex" 51 | }, 52 | { 53 | "begin": "\\('", 54 | "end": "'\\)", 55 | "name": "string.quoted.parens.soql.apex" 56 | }, 57 | { 58 | "begin": "\\='", 59 | "end": "'\\;", 60 | "name": "string.quoted.var1.soql.apex" 61 | }, 62 | { 63 | "captures": { 64 | "1": { 65 | "name": "keyword.other.package.apex" 66 | }, 67 | "2": { 68 | "name": "storage.modifier.package.apex" 69 | }, 70 | "3": { 71 | "name": "punctuation.terminator.apex" 72 | } 73 | }, 74 | "match": "^\\s*(package)\\b(?:\\s*([^ ;$]+)\\s*(;)?)?", 75 | "name": "meta.package.apex" 76 | }, 77 | { 78 | "begin": "(import static)\\b\\s*", 79 | "beginCaptures": { 80 | "1": { 81 | "name": "keyword.other.import.static.apex" 82 | } 83 | }, 84 | "captures": { 85 | "1": { 86 | "name": "keyword.other.import.apex" 87 | }, 88 | "2": { 89 | "name": "storage.modifier.import.apex" 90 | }, 91 | "3": { 92 | "name": "punctuation.terminator.apex" 93 | } 94 | }, 95 | "contentName": "storage.modifier.import.apex", 96 | "end": "\\s*(?:$|(;))", 97 | "endCaptures": { 98 | "1": { 99 | "name": "punctuation.terminator.apex" 100 | } 101 | }, 102 | "name": "meta.import.apex", 103 | "patterns": [ 104 | { 105 | "match": "\\.", 106 | "name": "punctuation.separator.apex" 107 | }, 108 | { 109 | "match": "\\s", 110 | "name": "invalid.illegal.character_not_allowed_here.apex" 111 | } 112 | ] 113 | }, 114 | { 115 | "begin": "(import)\\b\\s*", 116 | "beginCaptures": { 117 | "1": { 118 | "name": "keyword.other.import.apex" 119 | } 120 | }, 121 | "captures": { 122 | "1": { 123 | "name": "keyword.other.import.apex" 124 | }, 125 | "2": { 126 | "name": "storage.modifier.import.apex" 127 | }, 128 | "3": { 129 | "name": "punctuation.terminator.apex" 130 | } 131 | }, 132 | "contentName": "storage.modifier.import.apex", 133 | "end": "\\s*(?:$|(;))", 134 | "endCaptures": { 135 | "1": { 136 | "name": "punctuation.terminator.apex" 137 | } 138 | }, 139 | "name": "meta.import.apex", 140 | "patterns": [ 141 | { 142 | "match": "\\.", 143 | "name": "punctuation.separator.apex" 144 | }, 145 | { 146 | "match": "\\s", 147 | "name": "invalid.illegal.character_not_allowed_here.apex" 148 | } 149 | ] 150 | }, 151 | { 152 | "include": "#code" 153 | }, 154 | { 155 | "begin": "\\= '", 156 | "end": "'\\;", 157 | "name": "string.quoted.var2.soql.apex" 158 | } 159 | ], 160 | "repository": { 161 | "support-type-built-ins-apex": { 162 | "match": "\\b(?i:integer|string|void|double|boolean|date|datetime|id)\\b", 163 | "name": "support.type.built-ins.apex" 164 | }, 165 | "all-types": { 166 | "patterns": [ 167 | { 168 | "include": "#primitive-arrays" 169 | }, 170 | { 171 | "include": "#primitive-types" 172 | }, 173 | { 174 | "include": "#object-types" 175 | } 176 | ] 177 | }, 178 | "annotations": { 179 | "patterns": [ 180 | { 181 | "begin": "(@[^ (]+)(\\()", 182 | "beginCaptures": { 183 | "1": { 184 | "name": "storage.type.annotation.apex" 185 | }, 186 | "2": { 187 | "name": "punctuation.definition.annotation-arguments.begin.apex" 188 | } 189 | }, 190 | "end": "(\\))", 191 | "endCaptures": { 192 | "1": { 193 | "name": "punctuation.definition.annotation-arguments.end.apex" 194 | } 195 | }, 196 | "name": "meta.declaration.annotation.apex", 197 | "patterns": [ 198 | { 199 | "captures": { 200 | "1": { 201 | "name": "constant.other.key.apex" 202 | }, 203 | "2": { 204 | "name": "keyword.operator.assignment.apex" 205 | } 206 | }, 207 | "match": "(\\w*)\\s*(=)" 208 | }, 209 | { 210 | "include": "#code" 211 | }, 212 | { 213 | "match": ",", 214 | "name": "punctuation.seperator.property.apex" 215 | } 216 | ] 217 | }, 218 | { 219 | "match": "@\\w*", 220 | "name": "storage.type.annotation.apex" 221 | } 222 | ] 223 | }, 224 | "anonymous-classes-and-new": { 225 | "begin": "\\b(?>>?|~|\\^)", 650 | "name": "keyword.operator.bitwise.apex" 651 | }, 652 | { 653 | "match": "((&|\\^|\\||<<|>>>?)=)", 654 | "name": "keyword.operator.assignment.bitwise.apex" 655 | }, 656 | { 657 | "match": "(===?|!=|<=|>=|<>|<|>)", 658 | "name": "keyword.operator.comparison.apex" 659 | }, 660 | { 661 | "match": "([+*/%-]=)", 662 | "name": "keyword.operator.assignment.arithmetic.apex" 663 | }, 664 | { 665 | "match": "(=)", 666 | "name": "keyword.operator.assignment.apex" 667 | }, 668 | { 669 | "match": "(\\-\\-|\\+\\+)", 670 | "name": "keyword.operator.increment-decrement.apex" 671 | }, 672 | { 673 | "match": "(\\-|\\+|\\*|\\/|%)", 674 | "name": "keyword.operator.arithmetic.apex" 675 | }, 676 | { 677 | "match": "(!|&&|\\|\\|)", 678 | "name": "keyword.operator.logical.apex" 679 | }, 680 | { 681 | "match": "(\\||&)", 682 | "name": "keyword.operator.bitwise.apex" 683 | }, 684 | { 685 | "match": "(?<=\\S)\\.(?=\\S)", 686 | "name": "keyword.operator.dereference.apex" 687 | }, 688 | { 689 | "match": ";", 690 | "name": "punctuation.terminator.apex" 691 | }, 692 | { 693 | "match": "\\b(?i:insert|update|delete|upsert)\\b", 694 | "name": "keyword.control.dml.apex" 695 | } 696 | ] 697 | }, 698 | "method-call": { 699 | "begin": "([\\w$]+)(\\()", 700 | "beginCaptures": { 701 | "1": { 702 | "name": "meta.method.apex" 703 | }, 704 | "2": { 705 | "name": "punctuation.definition.method-parameters.begin.apex" 706 | } 707 | }, 708 | "end": "\\)", 709 | "endCaptures": { 710 | "0": { 711 | "name": "punctuation.definition.method-parameters.end.apex" 712 | } 713 | }, 714 | "name": "meta.method-call.apex", 715 | "patterns": [ 716 | { 717 | "match": ",", 718 | "name": "punctuation.definition.seperator.parameter.apex" 719 | }, 720 | { 721 | "include": "#code" 722 | } 723 | ] 724 | }, 725 | "methods": { 726 | "begin": "(?!new)(?=[\\w<].*\\s+)(?=([^=/]|/(?!/))+\\()", 727 | "end": "(})|(?=;)", 728 | "endCaptures": { 729 | "1": { 730 | "name": "punctuation.section.method.end.apex" 731 | } 732 | }, 733 | "name": "meta.method.apex", 734 | "patterns": [ 735 | { 736 | "include": "#storage-modifiers" 737 | }, 738 | { 739 | "begin": "(\\w+)\\s*\\(", 740 | "beginCaptures": { 741 | "1": { 742 | "name": "entity.name.function.apex" 743 | } 744 | }, 745 | "end": "\\)", 746 | "name": "meta.method.identifier.apex", 747 | "patterns": [ 748 | { 749 | "include": "#parameters" 750 | } 751 | ] 752 | }, 753 | { 754 | "begin": "<", 755 | "end": ">", 756 | "name": "storage.type.token.apex", 757 | "patterns": [ 758 | { 759 | "include": "#object-types" 760 | }, 761 | { 762 | "begin": "<", 763 | "comment": "This is just to support <>'s with no actual type prefix", 764 | "end": ">|[^\\w\\s,\\[\\]<]", 765 | "name": "storage.type.generic.apex" 766 | } 767 | ] 768 | }, 769 | { 770 | "begin": "(?=\\w.*\\s+\\w+\\s*\\()", 771 | "end": "(?=\\w+\\s*\\()", 772 | "name": "meta.method.return-type.apex", 773 | "patterns": [ 774 | { 775 | "include": "#all-types" 776 | } 777 | ] 778 | }, 779 | { 780 | "include": "#throws" 781 | }, 782 | { 783 | "begin": "{", 784 | "beginCaptures": { 785 | "0": { 786 | "name": "punctuation.section.method.begin.apex" 787 | } 788 | }, 789 | "end": "(?=})", 790 | "name": "meta.method.body.apex", 791 | "patterns": [ 792 | { 793 | "include": "#code" 794 | } 795 | ] 796 | }, 797 | { 798 | "include": "#comments" 799 | } 800 | ] 801 | }, 802 | "object-types": { 803 | "patterns": [ 804 | { 805 | "begin": "\\b((?:[a-z]\\w*\\.)*[A-Z]+\\w*)<", 806 | "end": ">|[^\\w\\s,\\?<\\[\\]]", 807 | "name": "storage.type.generic.apex", 808 | "patterns": [ 809 | { 810 | "include": "#object-types" 811 | }, 812 | { 813 | "begin": "<", 814 | "comment": "This is just to support <>'s with no actual type prefix", 815 | "end": ">|[^\\w\\s,\\[\\]<]", 816 | "name": "storage.type.generic.apex" 817 | } 818 | ] 819 | }, 820 | { 821 | "begin": "\\b((?:[a-z]\\w*\\.)*[A-Z]+\\w*)(?=\\[)", 822 | "end": "(?=[^\\]\\s])", 823 | "name": "storage.type.object.array.apex", 824 | "patterns": [ 825 | { 826 | "begin": "\\[", 827 | "end": "\\]", 828 | "patterns": [ 829 | { 830 | "include": "#code" 831 | } 832 | ] 833 | } 834 | ] 835 | }, 836 | { 837 | "captures": { 838 | "1": { 839 | "name": "keyword.operator.dereference.apex" 840 | } 841 | }, 842 | "match": "\\b(?:[a-z]\\w*(\\.))*[A-Z]+\\w*\\b", 843 | "name": "storage.type.apex" 844 | } 845 | ] 846 | }, 847 | "object-types-inherited": { 848 | "patterns": [ 849 | { 850 | "begin": "\\b((?:[a-z]\\w*\\.)*[A-Z]+\\w*)<", 851 | "end": ">|[^\\w\\s,<]", 852 | "name": "entity.other.inherited-class.apex", 853 | "patterns": [ 854 | { 855 | "include": "#object-types" 856 | }, 857 | { 858 | "begin": "<", 859 | "comment": "This is just to support <>'s with no actual type prefix", 860 | "end": ">|[^\\w\\s,<]", 861 | "name": "storage.type.generic.apex" 862 | } 863 | ] 864 | }, 865 | { 866 | "captures": { 867 | "1": { 868 | "name": "keyword.operator.dereference.apex" 869 | } 870 | }, 871 | "match": "\\b(?:[a-z]\\w*(\\.))*[A-Z]+\\w*", 872 | "name": "entity.other.inherited-class.apex" 873 | } 874 | ] 875 | }, 876 | "parameters": { 877 | "patterns": [ 878 | { 879 | "match": "final", 880 | "name": "storage.modifier.apex" 881 | }, 882 | { 883 | "include": "#annotations" 884 | }, 885 | { 886 | "include": "#primitive-arrays" 887 | }, 888 | { 889 | "include": "#primitive-types" 890 | }, 891 | { 892 | "include": "#object-types" 893 | }, 894 | { 895 | "match": "\\w+", 896 | "name": "variable.parameter.apex" 897 | } 898 | ] 899 | }, 900 | "parens": { 901 | "begin": "\\(", 902 | "end": "\\)", 903 | "patterns": [ 904 | { 905 | "include": "#code" 906 | } 907 | ] 908 | }, 909 | "primitive-arrays": { 910 | "patterns": [ 911 | { 912 | "match": "\\b(?:void|long|double|blob|date|datetime|decimal|id|integer|Object|string)(\\[\\])*\\b", 913 | "name": "storage.type.primitive.array.apex" 914 | } 915 | ] 916 | }, 917 | "primitive-types": { 918 | "patterns": [ 919 | { 920 | "match": "\\b(?:void|long|double|blob|date|datetime|decimal|id|integer|Object|string)\\b", 921 | "name": "storage.type.primitive.apex" 922 | } 923 | ] 924 | }, 925 | "storage-modifiers": { 926 | "captures": { 927 | "1": { 928 | "name": "storage.modifier.apex" 929 | } 930 | }, 931 | "match": "\\b(global|public|private|protected|static|final|native|synchronized|abstract|threadsafe|transient)\\b" 932 | }, 933 | "strings": { 934 | "patterns": [ 935 | { 936 | "begin": "\"", 937 | "beginCaptures": { 938 | "0": { 939 | "name": "punctuation.definition.string.begin.apex" 940 | } 941 | }, 942 | "end": "\"", 943 | "endCaptures": { 944 | "0": { 945 | "name": "punctuation.definition.string.end.apex" 946 | } 947 | }, 948 | "name": "string.quoted.double.apex", 949 | "patterns": [ 950 | { 951 | "match": "\\\\.", 952 | "name": "constant.character.escape.apex" 953 | } 954 | ] 955 | }, 956 | { 957 | "begin": "'", 958 | "beginCaptures": { 959 | "0": { 960 | "name": "punctuation.definition.string.begin.apex" 961 | } 962 | }, 963 | "end": "'", 964 | "endCaptures": { 965 | "0": { 966 | "name": "punctuation.definition.string.end.apex" 967 | } 968 | }, 969 | "name": "string.quoted.single.apex", 970 | "patterns": [ 971 | { 972 | "match": "\\\\.", 973 | "name": "constant.character.escape.apex" 974 | } 975 | ] 976 | } 977 | ] 978 | }, 979 | "throws": { 980 | "begin": "throws", 981 | "beginCaptures": { 982 | "0": { 983 | "name": "storage.modifier.apex" 984 | } 985 | }, 986 | "end": "(?={|;)", 987 | "name": "meta.throwables.apex", 988 | "patterns": [ 989 | { 990 | "include": "#object-types" 991 | } 992 | ] 993 | }, 994 | "values": { 995 | "patterns": [ 996 | { 997 | "include": "#strings" 998 | }, 999 | { 1000 | "include": "#object-types" 1001 | }, 1002 | { 1003 | "include": "#constants-and-special-vars" 1004 | } 1005 | ] 1006 | }, 1007 | "variables": { 1008 | "applyEndPatternLast": 1, 1009 | "patterns": [ 1010 | { 1011 | "begin": "(?x:(?=\n (?:\n (?:private|protected|public|global|native|synchronized|abstract|threadsafe|transient|static|final) # visibility/modifier\n |\n (?:def)\n |\n (?:void|boolean|byte|char|short|int|float|long|double)\n |\n (?:(?:[a-z]\\w*\\.)*[A-Z]+\\w*) # object type\n )\n \\s+\n (?!private|protected|public|global|native|synchronized|abstract|threadsafe|transient|static|final|def|void|boolean|byte|char|short|int|float|long|double)\n [\\w\\d_<>\\[\\],\\?][\\w\\d_<>\\[\\],\\? \\t]*\n (?:=|$)\n \n\t\t\t\t\t))", 1012 | "end": "(?=;)", 1013 | "name": "meta.definition.variable.apex", 1014 | "patterns": [ 1015 | { 1016 | "match": "\\s" 1017 | }, 1018 | { 1019 | "captures": { 1020 | "1": { 1021 | "name": "constant.variable.apex" 1022 | } 1023 | }, 1024 | "match": "([A-Z_0-9]+)\\s+(?=\\=)" 1025 | }, 1026 | { 1027 | "captures": { 1028 | "1": { 1029 | "name": "meta.definition.variable.name.apex" 1030 | } 1031 | }, 1032 | "match": "(\\w[^\\s,]*)\\s+(?=\\=)" 1033 | }, 1034 | { 1035 | "begin": "=", 1036 | "beginCaptures": { 1037 | "0": { 1038 | "name": "keyword.operator.assignment.apex" 1039 | } 1040 | }, 1041 | "end": "(?=;)", 1042 | "patterns": [ 1043 | { 1044 | "include": "#code" 1045 | } 1046 | ] 1047 | }, 1048 | { 1049 | "captures": { 1050 | "1": { 1051 | "name": "meta.definition.variable.name.apex" 1052 | } 1053 | }, 1054 | "match": "(\\w[^\\s=]*)(?=\\s*;)" 1055 | }, 1056 | { 1057 | "include": "#code" 1058 | } 1059 | ] 1060 | } 1061 | ] 1062 | } 1063 | }, 1064 | "scopeName": "source.apex" 1065 | } -------------------------------------------------------------------------------- /grammars/log.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "MavensMate Log", 3 | "patterns": [ 4 | { 5 | "match": "\\s*Result: Success", 6 | "name": "markup.inserted" 7 | }, 8 | { 9 | "match": "([0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9][0-9]\\s)", 10 | "name": "keyword" 11 | }, 12 | { 13 | "match": "\\|[\"USER_DEBUG\"]+\\|", 14 | "name": "string" 15 | }, 16 | { 17 | "match": "\\|[\"EXCEPTION_THROWN\"]+\\|", 18 | "name": "support.type.exception" 19 | }, 20 | { 21 | "match": "\\|[\"FATAL_ERROR\"]+\\|", 22 | "name": "support.type.exception" 23 | }, 24 | { 25 | "match": "\\|[A-Z,_]+\\|", 26 | "name": "variable" 27 | }, 28 | { 29 | "match": "\\[[0-9]{0,5}\\]", 30 | "name": "variable.language" 31 | }, 32 | { 33 | "match": "(true|false)", 34 | "name": "keyword" 35 | } 36 | ], 37 | "fileTypes": [ 38 | "log" 39 | ], 40 | "scopeName": "source.log" 41 | } -------------------------------------------------------------------------------- /grammars/visualforce.json: -------------------------------------------------------------------------------- 1 | { 2 | "scopeName": "visualforce.text.html.basic", 3 | "fileTypes": [ 4 | "component", 5 | "page" 6 | ], 7 | "firstLineMatch": "<(?i:(!DOCTYPE\\s*)?html)", 8 | "injections": { 9 | "R:text.html - comment.block": { 10 | "comment": "Use R: to ensure this matches after any other injections.", 11 | "patterns": [ 12 | { 13 | "match": "<", 14 | "name": "invalid.illegal.bad-angle-bracket.html" 15 | } 16 | ] 17 | } 18 | }, 19 | "name": "Visualforce", 20 | "patterns": [ 21 | { 22 | "begin": "(<)([a-zA-Z0-9:]++)(?=[^>]*>)", 23 | "beginCaptures": { 24 | "1": { 25 | "name": "punctuation.definition.tag.html" 26 | }, 27 | "2": { 28 | "name": "entity.name.tag.html" 29 | } 30 | }, 31 | "end": "(>(<)/)(\\2)(>)", 32 | "endCaptures": { 33 | "1": { 34 | "name": "punctuation.definition.tag.html" 35 | }, 36 | "2": { 37 | "name": "meta.scope.between-tag-pair.html" 38 | }, 39 | "3": { 40 | "name": "entity.name.tag.html" 41 | }, 42 | "4": { 43 | "name": "punctuation.definition.tag.html" 44 | } 45 | }, 46 | "name": "meta.tag.any.html", 47 | "patterns": [ 48 | { 49 | "include": "#tag-stuff" 50 | } 51 | ] 52 | }, 53 | { 54 | "begin": "(<\\?)(xml)", 55 | "captures": { 56 | "1": { 57 | "name": "punctuation.definition.tag.html" 58 | }, 59 | "2": { 60 | "name": "entity.name.tag.xml.html" 61 | } 62 | }, 63 | "end": "(\\?>)", 64 | "name": "meta.tag.preprocessor.xml.html", 65 | "patterns": [ 66 | { 67 | "include": "#tag-generic-attribute" 68 | }, 69 | { 70 | "include": "#string-double-quoted" 71 | }, 72 | { 73 | "include": "#string-single-quoted" 74 | } 75 | ] 76 | }, 77 | { 78 | "begin": ""] 4 | } 5 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mavensmate", 3 | "displayName": "mavensmate", 4 | "description": "MavensMate Salesforce plugin for the Visual Studio Code editor", 5 | "version": "0.16.2", 6 | "publisher": "DavidHelmer", 7 | "repository": "https://github.com/kidtsunami/MavensMate-VisualStudioCode", 8 | "homepage": "http://mavensmate.com/", 9 | "bugs": { 10 | "url": "https://github.com/joeferraro/MavensMate/issues" 11 | }, 12 | "icon": "icon.png", 13 | "engines": { 14 | "vscode": "^1.1.0" 15 | }, 16 | "categories": [ 17 | "Other" 18 | ], 19 | "activationEvents": [ 20 | "*" 21 | ], 22 | "main": "./out/src/extension", 23 | "contributes": { 24 | "commands": [ 25 | { 26 | "command": "mavensmate.openProject", 27 | "title": "MavensMate: Open Project" 28 | }, 29 | { 30 | "command": "mavensmate.openUI", 31 | "title": "MavensMate: Open MavensMate UI" 32 | }, 33 | { 34 | "command": "mavensmate.openSettings", 35 | "title": "MavensMate: Open MavensMate Settings" 36 | }, 37 | { 38 | "command": "mavensmate.openGlobalSettings", 39 | "title": "MavensMate: Open Global Settings" 40 | }, 41 | { 42 | "command": "mavensmate.newProject", 43 | "title": "MavensMate: New Project" 44 | }, 45 | { 46 | "command": "mavensmate.convertProject", 47 | "title": "MavensMate: Convert To MavensMate Project" 48 | }, 49 | { 50 | "command": "mavensmate.editProject", 51 | "title": "MavensMate: Edit Project" 52 | }, 53 | { 54 | "command": "mavensmate.runTests", 55 | "title": "MavensMate: Run Apex Tests" 56 | }, 57 | { 58 | "command": "mavensmate.runTestsAsync", 59 | "title": "MavensMate: Run Tests In Editor (async)" 60 | }, 61 | { 62 | "command": "mavensmate.runTestsMethods", 63 | "title": "MavensMate: Run Apex Test Methods" 64 | }, 65 | { 66 | "command": "mavensmate.openSalesforce", 67 | "title": "MavensMate: Open Salesforce" 68 | }, 69 | { 70 | "command": "mavensmate.deploy", 71 | "title": "MavensMate: Deploy" 72 | }, 73 | { 74 | "command": "mavensmate.newLightningApp", 75 | "title": "MavensMate: New Lightning App" 76 | }, 77 | { 78 | "command": "mavensmate.newLightningComponent", 79 | "title": "MavensMate: New Lightning Component" 80 | }, 81 | { 82 | "command": "mavensmate.newLightningEvent", 83 | "title": "MavensMate: New Lightning Event" 84 | }, 85 | { 86 | "command": "mavensmate.newLightningInterface", 87 | "title": "MavensMate: New Lightning Interface" 88 | }, 89 | { 90 | "command": "mavensmate.newLightningToken", 91 | "title": "MavensMate: New Lightning Token" 92 | }, 93 | { 94 | "command": "mavensmate.executeApex", 95 | "title": "MavensMate: Execute Apex" 96 | }, 97 | { 98 | "command": "mavensmate.flushLogs", 99 | "title": "MavensMate: Flush Logs" 100 | }, 101 | { 102 | "command": "mavensmate.startLogging", 103 | "title": "MavensMate: Start Logging" 104 | }, 105 | { 106 | "command": "mavensmate.stopLogging", 107 | "title": "MavensMate: Stop Logging" 108 | }, 109 | { 110 | "command": "mavensmate.cleanProject", 111 | "title": "MavensMate: Clean Project" 112 | }, 113 | { 114 | "command": "mavensmate.compileAllTabs", 115 | "title": "MavensMate: Compile All Tabs" 116 | }, 117 | { 118 | "command": "mavensmate.compileFile", 119 | "title": "MavensMate: Compile File" 120 | }, 121 | { 122 | "command": "mavensmate.forceCompileFile", 123 | "title": "MavensMate: Force Compile File" 124 | }, 125 | { 126 | "command": "mavensmate.compileProject", 127 | "title": "MavensMate: Compile Project" 128 | }, 129 | { 130 | "command": "mavensmate.refreshFile", 131 | "title": "MavensMate: Refresh File" 132 | }, 133 | { 134 | "command": "mavensmate.refreshFolder", 135 | "title": "MavensMate: Refresh Folder" 136 | }, 137 | { 138 | "command": "mavensmate.openMetadata", 139 | "title": "MavensMate: Open File In Salesforce" 140 | }, 141 | { 142 | "command": "mavensmate.deleteFile", 143 | "title": "MavensMate: Delete File" 144 | }, 145 | { 146 | "command": "mavensmate.getCoverage", 147 | "title": "MavensMate: Get Apex Coverage" 148 | }, 149 | { 150 | "command": "mavensmate.hideCoverageCommand", 151 | "title": "MavensMate: Hide Coverage Indicators For This Class " 152 | }, 153 | { 154 | "command": "mavensmate.getOrgWideCoverage", 155 | "title": "MavensMate: Get Org Wide Apex Coverage" 156 | }, 157 | { 158 | "command": "mavensmate.indexMetadata", 159 | "title": "MavensMate: Index Metadata" 160 | }, 161 | { 162 | "command": "mavensmate.toggleOutput", 163 | "title": "MavensMate: Toggle Output" 164 | }, 165 | { 166 | "command": "mavensmate.runApexScript", 167 | "title": "MavensMate: Run Apex Script" 168 | }, 169 | { 170 | "command": "mavensmate.newApexScript", 171 | "title": "MavensMate: New Apex Script" 172 | }, 173 | { 174 | "command": "mavensmate.executeSoql", 175 | "title": "MavensMate: Execute SOQL" 176 | }, 177 | { 178 | "command": "mavensmate.flushSoql", 179 | "title": "MavensMate: Flush SOQL" 180 | }, 181 | { 182 | "command": "mavensmate.newApexClass", 183 | "title": "MavensMate: New Apex Class" 184 | }, 185 | { 186 | "command": "mavensmate.newApexTrigger", 187 | "title": "MavensMate: New Apex Trigger" 188 | }, 189 | { 190 | "command": "mavensmate.newVisualforceComponent", 191 | "title": "MavensMate: New Visualforce Component" 192 | }, 193 | { 194 | "command": "mavensmate.newVisualforcePage", 195 | "title": "MavensMate: New Visualforce Page" 196 | }, 197 | { 198 | "command": "mavensmate.createResourceBundle", 199 | "title": "MavensMate: Create Resource Bundle" 200 | }, 201 | { 202 | "command": "mavensmate.deployResourceBundle", 203 | "title": "MavensMate: Deploy Resource Bundle" 204 | } 205 | ], 206 | "languages": [ 207 | { 208 | "id": "apex", 209 | "extensions": [ 210 | ".apex", 211 | ".cls", 212 | ".trigger", 213 | ".tgr" 214 | ], 215 | "aliases": [ 216 | "Apex" 217 | ], 218 | "configuration": "./languages/apex-language-configuration.json" 219 | }, 220 | { 221 | "id": "visualforce", 222 | "extensions": [ 223 | ".component", 224 | ".page" 225 | ], 226 | "aliases": [ 227 | "Visualforce" 228 | ], 229 | "configuration": "./languages/visualforce-language-configuration.json" 230 | }, 231 | { 232 | "id": "staticResource", 233 | "extensions": [ 234 | ".resource" 235 | ] 236 | }, 237 | { 238 | "id": "xml", 239 | "extensions": [ 240 | ".object", 241 | ".layout", 242 | ".resource", 243 | ".remoteSite", 244 | ".labels", 245 | ".app", 246 | ".dashboard", 247 | ".permissionset", 248 | ".workflow", 249 | ".email", 250 | ".profile", 251 | ".scf", 252 | ".queue", 253 | ".reportType", 254 | ".report", 255 | ".weblink", 256 | ".tab", 257 | ".letter", 258 | ".role", 259 | ".homePageComponent", 260 | ".homePageLayout", 261 | ".objectTranslation", 262 | ".flow", 263 | ".datacategorygroup", 264 | ".snapshot", 265 | ".site", 266 | ".sharingRules", 267 | ".settings", 268 | ".callCenter", 269 | ".community", 270 | ".authProvider", 271 | ".customApplicationComponent", 272 | ".quickAction", 273 | ".approvalProcess", 274 | ".app", 275 | ".auradoc", 276 | ".cmp", 277 | ".evt", 278 | ".tokens", 279 | ".design", 280 | ".object" 281 | ] 282 | }, 283 | { 284 | "id": "log", 285 | "extensions": [ 286 | ".log" 287 | ], 288 | "aliaslint": [ 289 | "Debug Log" 290 | ] 291 | } 292 | ], 293 | "grammars": [ 294 | { 295 | "language": "apex", 296 | "scopeName": "source.apex", 297 | "path": "./grammars/apex.json" 298 | }, 299 | { 300 | "language": "visualforce", 301 | "scopeName": "visualforce.text.html.basic", 302 | "path": "./grammars/visualforce.json" 303 | }, 304 | { 305 | "language": "log", 306 | "scopeName": "source.log", 307 | "path": "./grammars/log.json" 308 | } 309 | ], 310 | "menus": { 311 | "editor/title": [ 312 | { 313 | "command": "mavensmate.newProject", 314 | "group": "mavensmate" 315 | } 316 | ], 317 | "explorer/context": [ 318 | { 319 | "when": "resourceLangId == 'apex'", 320 | "command": "mavensmate.compileFile", 321 | "group": "mavensmate" 322 | }, 323 | { 324 | "when": "resourceLangId == 'visualforce'", 325 | "command": "mavensmate.compileFile", 326 | "group": "mavensmate" 327 | }, 328 | { 329 | "when": "resourceLangId == 'xml'", 330 | "command": "mavensmate.compileFile", 331 | "group": "mavensmate" 332 | }, 333 | { 334 | "when": "resourceLangId == 'apex'", 335 | "command": "mavensmate.refreshFile", 336 | "group": "mavensmate" 337 | }, 338 | { 339 | "when": "resourceLangId == 'visualforce'", 340 | "command": "mavensmate.refreshFile", 341 | "group": "mavensmate" 342 | }, 343 | { 344 | "when": "resourceLangId == 'xml'", 345 | "command": "mavensmate.refreshFile", 346 | "group": "mavensmate" 347 | }, 348 | { 349 | "when": "explorerResourceIsFolder", 350 | "command": "mavensmate.refreshFolder", 351 | "group": "mavensmate" 352 | }, 353 | { 354 | "when": "resourceLangId == 'apex'", 355 | "command": "mavensmate.deleteFile", 356 | "group": "mavensmate" 357 | }, 358 | { 359 | "when": "resourceLangId == 'visualforce'", 360 | "command": "mavensmate.deleteFile", 361 | "group": "mavensmate" 362 | }, 363 | { 364 | "when": "resourceLangId == 'xml'", 365 | "command": "mavensmate.deleteFile", 366 | "group": "mavensmate" 367 | }, 368 | { 369 | "when": "resourceLangId == 'staticResource'", 370 | "command": "mavensmate.createResourceBundle", 371 | "group": "mavensmate" 372 | } 373 | ], 374 | "editor/context": [ 375 | { 376 | "when": "resourceLangId == 'apex'", 377 | "command": "mavensmate.compileFile", 378 | "group": "mavensmate" 379 | }, 380 | { 381 | "when": "resourceLangId == 'visualforce'", 382 | "command": "mavensmate.compileFile", 383 | "group": "mavensmate" 384 | }, 385 | { 386 | "when": "resourceLangId == 'xml'", 387 | "command": "mavensmate.compileFile", 388 | "group": "mavensmate" 389 | }, 390 | { 391 | "when": "resourceLangId == 'apex'", 392 | "command": "mavensmate.refreshFile", 393 | "group": "mavensmate" 394 | }, 395 | { 396 | "when": "resourceLangId == 'visualforce'", 397 | "command": "mavensmate.refreshFile", 398 | "group": "mavensmate" 399 | }, 400 | { 401 | "when": "resourceLangId == 'xml'", 402 | "command": "mavensmate.refreshFile", 403 | "group": "mavensmate" 404 | }, 405 | { 406 | "when": "resourceLangId == 'apex'", 407 | "command": "mavensmate.deleteFile", 408 | "group": "mavensmate" 409 | }, 410 | { 411 | "when": "resourceLangId == 'visualforce'", 412 | "command": "mavensmate.deleteFile", 413 | "group": "mavensmate" 414 | }, 415 | { 416 | "when": "resourceLangId == 'xml'", 417 | "command": "mavensmate.deleteFile", 418 | "group": "mavensmate" 419 | } 420 | ] 421 | }, 422 | "configuration": { 423 | "type": "object", 424 | "title": "MavensMate configuration", 425 | "properties": { 426 | "mavensMateDesktop.baseURL": { 427 | "type": "string", 428 | "default": "http://localhost:56248", 429 | "description": "The base URL of the MavensMate Desktop Server" 430 | }, 431 | "mavensMateDesktop.Path.osx": { 432 | "type": "string", 433 | "default": "/Applications/MavensMate.app", 434 | "description": "The absolute path to MavensMate Desktop when running OS X" 435 | }, 436 | "mavensMateDesktop.Path.windows": { 437 | "type": "string", 438 | "default": "C:\\Program Files\\MavensMate.exe", 439 | "description": "The absolute path to MavensMate Desktop when running Windows" 440 | }, 441 | "mavensMateDesktop.Path.linux": { 442 | "type": "string", 443 | "default": "", 444 | "description": "The absolute path to MavensMate Desktop when running Linux" 445 | }, 446 | "mavensMate.compileOnSave": { 447 | "type": "boolean", 448 | "default": "true", 449 | "description": "If true, MavensMate will attempt to compile the document on save, if it is one of the supported file types" 450 | }, 451 | "mavensMate.hideOutputOnSuccess": { 452 | "type": "boolean", 453 | "default": "true", 454 | "description": "If true, MavensMate will automatically hide the Output upon a successful operation" 455 | }, 456 | "mavensMate.hideOutputDelay": { 457 | "type": "number", 458 | "default": "0", 459 | "description": "The amount of time (in seconds)" 460 | }, 461 | "mavensMate.startMavensMateApp": { 462 | "type": "boolean", 463 | "default": "false", 464 | "description": "If true, MavensMate will automatically start MavensMate Desktop when VS Code opens" 465 | }, 466 | "mavensMate.pingMavensMateOnStartUp": { 467 | "type": "boolean", 468 | "default": "false", 469 | "description": "If true, MavensMate will ping MavensMate Desktop when VS Code opens to check it's availability" 470 | } 471 | } 472 | } 473 | }, 474 | "scripts": { 475 | "vscode:prepublish": "tsc -p ./", 476 | "compile": "tsc -watch -p ./", 477 | "postinstall": "node ./node_modules/vscode/bin/install", 478 | "test": "node ./node_modules/vscode/bin/test" 479 | }, 480 | "dependencies": { 481 | "axios": "^0.16.1", 482 | "bluebird": "3.5.0", 483 | "global": "^4.3.1", 484 | "moxios": "^0.4.0", 485 | "path": "0.12.7", 486 | "url-join": "2.0.1" 487 | }, 488 | "devDependencies": { 489 | "@types/bluebird": "3.5.0", 490 | "@types/mocha": "2.2.41", 491 | "@types/nock": "8.2.1", 492 | "@types/node": "^6.0.70", 493 | "@types/sinon": "2.1.0", 494 | "coveralls": "^2.11.14", 495 | "expect.js": "0.3.1", 496 | "nock": "8.2.1", 497 | "sinon": "2.1.0", 498 | "typescript": "2.2.2", 499 | "vscode": "^1.1.30" 500 | } 501 | } 502 | -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | let mavensMateMateExtension; 4 | 5 | export function activate(context: vscode.ExtensionContext) { 6 | let { MavensMateExtension } = require('./mavensMateExtension'); 7 | mavensMateMateExtension = MavensMateExtension.create(context); 8 | return mavensMateMateExtension.activate(); 9 | } 10 | 11 | export function deactivate() { 12 | mavensMateMateExtension.deactivate(); 13 | mavensMateMateExtension = null; 14 | } 15 | 16 | process.on("unhandledRejection", function(reason, promise) { 17 | console.error(`MavensMate Unhandled Exception: ${reason}`); 18 | }); -------------------------------------------------------------------------------- /src/mavensMateExtension.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import * as vscode from 'vscode'; 3 | import Promise = require('bluebird'); 4 | 5 | import { MavensMateChannel } from '../src/vscode/mavensMateChannel'; 6 | import { ProjectSettings } from '../src/mavensmate/projectSettings'; 7 | import { MavensMateClient } from '../src/mavensmate/mavensMateClient'; 8 | import { MavensMateStatus } from '../src/vscode/mavensMateStatus'; 9 | import { MavensMateCodeCoverage } from '../src/vscode/mavensMateCodeCoverage'; 10 | import * as CommandRegistrar from '../src/vscode/commandRegistrar'; 11 | import { getConfiguration } from './vscode/mavensMateConfiguration'; 12 | 13 | 14 | export class MavensMateExtension { 15 | context: vscode.ExtensionContext; 16 | mavensMateChannel: MavensMateChannel; 17 | mavensMateStatus: MavensMateStatus; 18 | mavensMateClient: MavensMateClient; 19 | mavensMateCodeCoverage: MavensMateCodeCoverage; 20 | 21 | static create(context: vscode.ExtensionContext){ 22 | return new MavensMateExtension(context); 23 | } 24 | 25 | constructor(context: vscode.ExtensionContext){ 26 | this.context = context; 27 | } 28 | 29 | activate(context: vscode.ExtensionContext) { 30 | 31 | this.mavensMateChannel = MavensMateChannel.getInstance(); 32 | this.mavensMateStatus = MavensMateStatus.getInstance(); 33 | this.mavensMateClient = MavensMateClient.getInstance(); 34 | this.mavensMateCodeCoverage = MavensMateCodeCoverage.getInstance(); 35 | this.mavensMateChannel.appendStatus('MavensMate: Activating'); 36 | 37 | return Promise.resolve().bind(this) 38 | .then(() => this.checkProjectSettingsAndSubscribe()) 39 | .then(() => CommandRegistrar.registerCommands()) 40 | .then(() => { 41 | if(getConfiguration('mavensMate.pingMavensMateOnStartUp')){ 42 | this.mavensMateClient.isAppAvailable(); 43 | } else { 44 | console.log(`MavensMate: Not pinging MavensMate Desktop on Startup, controlled by mavensMate.pingMavensMateOnStartUp`); 45 | } 46 | }); 47 | } 48 | 49 | checkProjectSettingsAndSubscribe(){ 50 | if(ProjectSettings.hasProjectSettings()){ 51 | let projectSettings = ProjectSettings.getProjectSettings(); 52 | this.mavensMateChannel.appendStatus(`Instantiating with Project: ${projectSettings.projectName} (${ projectSettings.instanceUrl })`); 53 | return this.subscribeToEvents(); 54 | } else { 55 | this.mavensMateChannel.appendStatus(`Instantiating without Project`); 56 | } 57 | } 58 | 59 | instantiateWithoutProject(){ 60 | 61 | let withProject = false; 62 | 63 | CommandRegistrar.registerCommands(); 64 | } 65 | 66 | subscribeToEvents(){ 67 | let saveEvent = vscode.workspace.onDidSaveTextDocument((textDocument) => { 68 | let compileOnSaveConfigured = getConfiguration('mavensMate.compileOnSave'); 69 | let isApexScript = textDocument.fileName.includes('apex-scripts'); 70 | let isMetadata = textDocument.fileName.includes('src') 71 | && !textDocument.fileName.endsWith('package.xml') 72 | && !textDocument.fileName.endsWith('destructiveChanges.xml') 73 | && !textDocument.fileName.endsWith('destructiveChangesPost.xml') 74 | && !textDocument.fileName.endsWith('destructiveChangesPre.xml'); 75 | 76 | if (!compileOnSaveConfigured) { 77 | console.info('MavensMate: compileOnSave is not configured.'); 78 | } else if (isApexScript) { 79 | console.info('MavensMate: silently ignoring the saving of a local apex script. (OK you got me, this isn\'t necessarily silence)'); 80 | } else if (isMetadata) { 81 | return vscode.commands.executeCommand('mavensmate.compileFile', textDocument.uri); 82 | } else { 83 | console.info('MavensMate: silently ignoring the saving of a non-metadata file (not under a src directory'); 84 | } 85 | }); 86 | this.context.subscriptions.push(saveEvent); 87 | 88 | this.mavensMateChannel.appendStatus('Subscribed to events'); 89 | } 90 | 91 | deactivate() { 92 | this.mavensMateChannel.appendStatus('Deactivating'); 93 | this.mavensMateChannel.dispose(); 94 | this.mavensMateClient.dispose(); 95 | this.mavensMateStatus.dispose(); 96 | this.mavensMateCodeCoverage.dispose(); 97 | console.info(`MavensMate: Finished Deactivating`); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/mavensmate/commands/baseCommand.ts: -------------------------------------------------------------------------------- 1 | import { MavensMateChannel } from '../../vscode/mavensMateChannel'; 2 | 3 | import Promise = require('bluebird'); 4 | import * as vscode from 'vscode'; 5 | import { ProjectSettings } from '../projectSettings'; 6 | 7 | export abstract class BaseCommand { 8 | label: string; 9 | mavensMateChannel: MavensMateChannel; 10 | allowWithoutProject: boolean; 11 | 12 | constructor(label: string) { 13 | this.label = label; 14 | 15 | this.mavensMateChannel = MavensMateChannel.getInstance(); 16 | } 17 | 18 | abstract execute(selectedResource?: vscode.Uri): Thenable; 19 | 20 | invoke(selectedResource?: vscode.Uri): Thenable{ 21 | if(ProjectSettings.hasProjectSettings() || this.allowWithoutProject === true){ 22 | try { 23 | return this.execute(selectedResource).then(null, this.handleAuthenticationError); 24 | } catch(commandException){ 25 | this.logAsErrorAndThrow(commandException); 26 | } 27 | } else { 28 | return this.promptToOpenProject(); 29 | } 30 | } 31 | 32 | private logAsErrorAndThrow(commandException){ 33 | console.error(`MavensMate: ${commandException}`); 34 | throw(commandException); 35 | } 36 | 37 | private handleAuthenticationError(response){ 38 | if(response && response.error && response.error.endsWith('Project requires re-authentication.')){ 39 | console.warn('MavensMate: Need to re-authenticate.'); 40 | return vscode.commands.executeCommand('mavensmate.oAuthProject'); 41 | } else { 42 | console.error(`MavensMate: ${response}`) 43 | } 44 | } 45 | 46 | private promptToOpenProject(){ 47 | let message = `${this.label} requires an open MavensMate project`; 48 | let openProject = 'Open Project'; 49 | return vscode.window.showWarningMessage(message, openProject) 50 | .then((answer) => { 51 | if(answer == openProject){ 52 | return vscode.commands.executeCommand('mavensmate.openProject'); 53 | } 54 | }); 55 | } 56 | } -------------------------------------------------------------------------------- /src/mavensmate/commands/cleanProject.ts: -------------------------------------------------------------------------------- 1 | import { ClientCommand } from './clientCommand'; 2 | import { BaseCommand } from './baseCommand'; 3 | import * as vscode from 'vscode'; 4 | 5 | class CleanProject extends ClientCommand { 6 | 7 | static create(): BaseCommand { 8 | return new CleanProject(); 9 | } 10 | 11 | constructor() { 12 | super('Clean Project', 'clean-project'); 13 | this.async = true; 14 | this.body = { 15 | args: { 16 | ui: false 17 | } 18 | } 19 | } 20 | 21 | execute(): Thenable { 22 | let confirmMessage = 'Confirm clean project? All local (non-server) files will be deleted and your project will be refreshed from the server'; 23 | return vscode.window.showWarningMessage(confirmMessage, 'Yes').then((answer) => { 24 | if(answer === 'Yes'){ 25 | return super.execute(); 26 | } else { 27 | return; 28 | } 29 | }); 30 | } 31 | } 32 | 33 | export = CleanProject; -------------------------------------------------------------------------------- /src/mavensmate/commands/clientCommand.ts: -------------------------------------------------------------------------------- 1 | import { BaseCommand } from './baseCommand'; 2 | import Promise = require('bluebird'); 3 | import { MavensMateStatus } from '../../vscode/mavensMateStatus'; 4 | import { MavensMateClient } from '../mavensMateClient'; 5 | export class SalesforceTest { 6 | testNameOrPath?: string; 7 | methodNames?: string[]; 8 | } 9 | export abstract class ClientCommand extends BaseCommand { 10 | id: string; 11 | async: boolean; 12 | mavensMateStatus: MavensMateStatus; 13 | mavensMateClient: MavensMateClient; 14 | body: { 15 | name?: string, 16 | paths?: string[], 17 | classes?: string[], 18 | tests?: SalesforceTest[], 19 | skipCoverage?: boolean, 20 | callThrough?: boolean, 21 | force?: boolean, 22 | soql?: string, 23 | global?: boolean, 24 | args: { 25 | ui?: boolean, 26 | type?: string, 27 | origin?: string 28 | } 29 | } 30 | 31 | constructor(label: string, id: string) { 32 | super(label); 33 | this.id = id; 34 | this.body = { 35 | args: {} 36 | }; 37 | 38 | this.mavensMateStatus = MavensMateStatus.getInstance(); 39 | this.mavensMateClient = MavensMateClient.getInstance(); 40 | } 41 | 42 | execute(): Thenable { 43 | return this.onStart() 44 | .bind(this) 45 | .then(this.sendCommand) 46 | .then(this.onSuccess) 47 | .catch(this.onFailure); 48 | } 49 | 50 | onStart(): Promise{ 51 | this.mavensMateChannel.waitingOnCount++; 52 | return Promise.resolve().then(() => { 53 | let statusText: string = `${this.label}: Starting`; 54 | return this.mavensMateChannel.appendStatus(statusText); 55 | }); 56 | } 57 | 58 | sendCommand(): Promise{ 59 | if(this.async === undefined){ 60 | this.async = true; 61 | } 62 | if(this.body.args.ui === undefined){ 63 | this.body.args.ui = false; 64 | } 65 | 66 | return this.mavensMateClient.sendCommand(this); 67 | } 68 | 69 | onSuccess(response): Promise{ 70 | this.mavensMateChannel.waitingOnCount--; 71 | if(this.mavensMateChannel.waitingOnCount === 0){ 72 | this.mavensMateStatus.showAppIsAvailable(); 73 | } 74 | return this.mavensMateChannel.appendStatus(`${this.label}: Finished`); 75 | } 76 | 77 | onFailure(response): Promise{ 78 | this.mavensMateChannel.waitingOnCount--; 79 | if(response.error){ 80 | let error: string = response.error; 81 | this.mavensMateChannel.appendError(`${this.label}: ${error}\n${response.stack}`); 82 | } else { 83 | this.mavensMateChannel.appendError(`${this.label}: Failed\n${response}`); 84 | } 85 | this.mavensMateStatus.showAppIsUnavailable(); 86 | console.error(`MavensMate Client Error Response:\n ${response}`); 87 | return Promise.reject(response); 88 | } 89 | } -------------------------------------------------------------------------------- /src/mavensmate/commands/compileAllTabs.ts: -------------------------------------------------------------------------------- 1 | import { ClientCommand } from './clientCommand'; 2 | import { MavensMateChannel } from '../../vscode/mavensMateChannel'; 3 | import { handleCompileResponse } from '../handlers/compileResponseHandler'; 4 | 5 | import * as vscode from 'vscode'; 6 | 7 | import BlueBirdPromise = require('bluebird'); 8 | 9 | let languagesToCompileOnSave = new Set(['apex', 'visualforce', 'xml', 'javascript']); 10 | 11 | class CompileAllTabs extends ClientCommand { 12 | static create(): ClientCommand{ 13 | return new CompileAllTabs(); 14 | } 15 | 16 | constructor() { 17 | super('Compile All Tabs', 'compile-metadata'); 18 | this.async = true; 19 | this.body.args.ui = false; 20 | } 21 | 22 | execute(selectedResource?: vscode.Uri): Thenable { 23 | return this.getOpenEditors().then((editors) => { 24 | let filesToSave: string[] = []; 25 | for(let i=0; i < editors.length; i++){ 26 | let editor = editors[i]; 27 | if (this.validPath(editor)){ 28 | filesToSave.push(editor.document.fileName); 29 | } 30 | } 31 | this.body.paths = filesToSave; 32 | return super.execute(); 33 | }); 34 | } 35 | 36 | onStart(): BlueBirdPromise { 37 | return super.onStart() 38 | .then(() => { 39 | for(let i=0; i < this.body.paths.length; i++){ 40 | this.mavensMateChannel.appendLine(this.body.paths[i]); 41 | } 42 | }); 43 | } 44 | 45 | onSuccess(response): BlueBirdPromise { 46 | return super.onSuccess(response) 47 | .then(() => handleCompileResponse(response)); 48 | } 49 | 50 | // Hack to get all open tabs. Taken from: 51 | // https://github.com/eamodio/vscode-restore-editors/blob/master/src/documentManager.ts#L43 52 | async getOpenEditors() { 53 | try { 54 | const active = vscode.window.activeTextEditor; 55 | 56 | const editorTracker = new ActiveEditorTracker(); 57 | 58 | let editor = active; 59 | const openEditors: vscode.TextEditor[] = []; 60 | do { 61 | openEditors.push(editor); 62 | 63 | vscode.commands.executeCommand('workbench.action.nextEditor'); 64 | editor = await editorTracker.wait(); 65 | console.log(editor); 66 | } while (active.document.fileName != editor.document.fileName); 67 | editorTracker.dispose(); 68 | 69 | return openEditors; 70 | } 71 | catch (ex) { 72 | } 73 | } 74 | 75 | private validPath(editor: vscode.TextEditor){ 76 | if (!languagesToCompileOnSave.has(editor.document.languageId)) { 77 | return false; 78 | } else if (editor.document.fileName.includes('apex-scripts')) { 79 | return false; 80 | } else if (editor.document.fileName.includes('resource-bundles')) { 81 | return false; 82 | } else { 83 | return true; 84 | } 85 | } 86 | } 87 | 88 | class ActiveEditorTracker extends vscode.Disposable { 89 | 90 | private _disposable: vscode.Disposable; 91 | private _resolver: (value?: vscode.TextEditor | PromiseLike) => void; 92 | 93 | constructor() { 94 | super(() => this.dispose()); 95 | 96 | this._disposable = vscode.window.onDidChangeActiveTextEditor( 97 | e => this._resolver(e) 98 | ); 99 | } 100 | 101 | dispose() { 102 | this._disposable && this._disposable.dispose(); 103 | } 104 | 105 | wait(): Promise { 106 | return new Promise( 107 | (resolve, reject) => this._resolver = resolve 108 | ); 109 | } 110 | } 111 | 112 | export = CompileAllTabs; -------------------------------------------------------------------------------- /src/mavensmate/commands/compileFile.ts: -------------------------------------------------------------------------------- 1 | import { PathsCommand } from './pathsCommand'; 2 | import { MavensMateChannel } from '../../vscode/mavensMateChannel'; 3 | import { handleCompileResponse } from '../handlers/compileResponseHandler'; 4 | 5 | import * as vscode from 'vscode'; 6 | import Promise = require('bluebird'); 7 | 8 | let languagesToCompileOnSave = new Set(['apex', 'visualforce', 'html', 'xml', 'javascript', 'css']); 9 | 10 | class CompileFile extends PathsCommand { 11 | static create(label?: string): PathsCommand{ 12 | if(!label){ 13 | label = 'Compile File'; 14 | } 15 | return new CompileFile(label); 16 | } 17 | 18 | constructor(label: string) { 19 | super(label, 'compile-metadata'); 20 | } 21 | 22 | protected confirmPath(): Thenable { 23 | let uriToOpen = vscode.Uri.file(this.filePath); 24 | let confirmPromise = vscode.workspace.openTextDocument(uriToOpen) 25 | .then((textDocument) => { 26 | if(!this.checkIsMetadata()) { 27 | throw new Error(`File is not metadata: ${this.filePath}`); 28 | } else if(!languagesToCompileOnSave.has(textDocument.languageId)){ 29 | throw new Error(`Can not compile this file: ${this.filePath}`); 30 | } else if(this.filePath.includes('apex-scripts')){ 31 | throw new Error(`Local Apex Scripts can't be compiled. You can run them with Run Apex Script`); 32 | }else if(this.filePath.includes('resource-bundles')){ 33 | throw new Error(`Files inside Resource Bundles cannot be compiled. Use Deploy Resource Bundle instead`); 34 | } else { 35 | return super.confirmPath(); 36 | } 37 | }); 38 | return confirmPromise; 39 | } 40 | 41 | onSuccess(response): Promise { 42 | return handleCompileResponse(response) 43 | .then(() => super.onSuccess(response)); 44 | } 45 | } 46 | export = CompileFile; -------------------------------------------------------------------------------- /src/mavensmate/commands/compileProject.ts: -------------------------------------------------------------------------------- 1 | import { ClientCommand } from './clientCommand'; 2 | import { handleCompileResponse } from '../handlers/compileResponseHandler'; 3 | 4 | import * as vscode from 'vscode'; 5 | import path = require('path'); 6 | 7 | class CompileProject extends ClientCommand { 8 | static create(): CompileProject{ 9 | return new CompileProject(); 10 | } 11 | 12 | constructor() { 13 | super('Compile Project', 'compile-project'); 14 | this.async = true; 15 | this.body.args.ui = false; 16 | } 17 | 18 | execute(selectedResource?: vscode.Uri): Thenable { 19 | let confirmMessage = 'Would you like to compile the project?'; 20 | return vscode.window.showInformationMessage(confirmMessage, 'Yes').then((answer) => { 21 | if(answer === 'Yes'){ 22 | return super.execute().then(handleCompileResponse); 23 | } else { 24 | return; 25 | } 26 | }); 27 | } 28 | } 29 | 30 | export = CompileProject; -------------------------------------------------------------------------------- /src/mavensmate/commands/convertProject.ts: -------------------------------------------------------------------------------- 1 | import { ClientCommand } from './clientCommand'; 2 | import { BaseCommand } from './baseCommand'; 3 | import * as vscode from 'vscode'; 4 | 5 | module.exports = class NewProjectFromExistingDirectory extends ClientCommand { 6 | static create(): BaseCommand { 7 | return new NewProjectFromExistingDirectory(); 8 | } 9 | 10 | constructor() { 11 | super('Convert To MavensMate Project', 'new-project-from-existing-directory'); 12 | this.async = false; 13 | this.body.args.ui = true; 14 | this.body.args.origin = vscode.workspace.rootPath; 15 | this.allowWithoutProject = true; 16 | } 17 | } -------------------------------------------------------------------------------- /src/mavensmate/commands/createResourceBundle.ts: -------------------------------------------------------------------------------- 1 | import { ClientCommand } from './clientCommand'; 2 | import StaticResourceQuickPick = require('../../vscode/staticResourceQuickPick'); 3 | 4 | import * as vscode from 'vscode'; 5 | import path = require('path'); 6 | import Promise = require('bluebird'); 7 | 8 | class CreateResourceBundle extends ClientCommand { 9 | filePath: string; 10 | baseName: string; 11 | 12 | static create(): ClientCommand{ 13 | return new CreateResourceBundle(); 14 | } 15 | 16 | constructor() { 17 | super('Create Resource Bundle', 'new-resource-bundle'); 18 | } 19 | 20 | execute(selectedResource?: vscode.Uri): Thenable { 21 | if(selectedResource && selectedResource.scheme === 'file'){ 22 | this.filePath = selectedResource.fsPath; 23 | } 24 | return this.confirmPath() 25 | .then(() => { 26 | this.body.paths = [this.filePath]; 27 | return super.execute(); 28 | }); 29 | } 30 | 31 | protected confirmPath(): Thenable { 32 | if(this.filePath){ 33 | let extension = path.extname(this.filePath); 34 | if(extension == 'resource'){ 35 | return this.promptForConfirmation(); 36 | } else { 37 | return Promise.reject(`${this.baseName} is not a Static Resource`); 38 | } 39 | } else { 40 | return StaticResourceQuickPick.showStaticResourceQuickPick() 41 | .then((selectedStaticResource: StaticResourceQuickPick.staticResourceQuickPickItem) => { 42 | this.filePath = selectedStaticResource.path; 43 | }); 44 | } 45 | } 46 | 47 | private promptForConfirmation(){ 48 | let confirmMessage = `Are you sure you want to create a resource bundle for ${ this.baseName }?`; 49 | return vscode.window.showInformationMessage(confirmMessage, 'Yes').then((answer) => { 50 | if(answer === 'Yes'){ 51 | return Promise.resolve(); 52 | } else { 53 | return Promise.reject(`${this.label} cancelled`); 54 | } 55 | }); 56 | } 57 | } 58 | export = CreateResourceBundle; -------------------------------------------------------------------------------- /src/mavensmate/commands/deleteFile.ts: -------------------------------------------------------------------------------- 1 | import { PathsCommand } from './pathsCommand'; 2 | 3 | import * as vscode from 'vscode'; 4 | 5 | module.exports = class DeleteFile extends PathsCommand { 6 | static create(){ 7 | return new DeleteFile(); 8 | } 9 | 10 | constructor() { 11 | super('Delete File', 'delete-metadata'); 12 | this.async = true; 13 | this.body.args.ui = false; 14 | } 15 | 16 | protected confirmPath(): Thenable { 17 | return super.confirmPath().then(() => this.promptForConfirmation()); 18 | } 19 | 20 | private promptForConfirmation(){ 21 | let confirmMessage = `Are you sure you want to delete ${ this.baseName } from Salesforce?`; 22 | return vscode.window.showInformationMessage(confirmMessage, 'Yes').then((answer) => { 23 | if(answer === 'Yes'){ 24 | return Promise.resolve(); 25 | } else { 26 | return Promise.reject('Delete File Cancelled'); 27 | } 28 | }); 29 | } 30 | 31 | } -------------------------------------------------------------------------------- /src/mavensmate/commands/deploy.ts: -------------------------------------------------------------------------------- 1 | import { ClientCommand } from './clientCommand'; 2 | import { BaseCommand } from './baseCommand'; 3 | 4 | module.exports = class Deploy extends ClientCommand { 5 | static create(): BaseCommand { 6 | return new Deploy(); 7 | } 8 | 9 | constructor() { 10 | super('Deploy', 'deploy'); 11 | this.async = false; 12 | this.body.args.ui = true; 13 | } 14 | } -------------------------------------------------------------------------------- /src/mavensmate/commands/deployResourceBundle.ts: -------------------------------------------------------------------------------- 1 | import { PathsCommand } from './pathsCommand'; 2 | import ResourceBundleQuickPick = require('../../vscode/resourceBundleQuickPick'); 3 | 4 | import * as vscode from 'vscode'; 5 | import Promise = require('bluebird'); 6 | 7 | class DeployResourceBundle extends PathsCommand { 8 | static create(): PathsCommand{ 9 | return new DeployResourceBundle(); 10 | } 11 | 12 | constructor() { 13 | super('Resource Bundle', 'deploy-resource-bundle'); 14 | } 15 | 16 | execute(selectedResource?: vscode.Uri): Thenable { 17 | return ResourceBundleQuickPick.showResourceBundleQuickPick() 18 | .then((selectedResourceBundle: ResourceBundleQuickPick.resourceBundleQuickPickItem) => { 19 | let selectedResource: vscode.Uri = vscode.Uri.file(selectedResourceBundle.path); 20 | return super.execute(selectedResource); 21 | }); 22 | } 23 | } 24 | export = DeployResourceBundle; -------------------------------------------------------------------------------- /src/mavensmate/commands/editProject.ts: -------------------------------------------------------------------------------- 1 | import { ClientCommand } from './clientCommand'; 2 | import { BaseCommand } from './baseCommand'; 3 | 4 | module.exports = class EditProject extends ClientCommand { 5 | static create(): BaseCommand { 6 | return new EditProject(); 7 | } 8 | 9 | constructor() { 10 | super('Edit Project', 'edit-project'); 11 | this.async = false; 12 | this.body.args.ui = true; 13 | } 14 | } -------------------------------------------------------------------------------- /src/mavensmate/commands/executeApex.ts: -------------------------------------------------------------------------------- 1 | import { ClientCommand } from './clientCommand'; 2 | import { BaseCommand } from './baseCommand'; 3 | 4 | module.exports = class ExecuteApex extends ClientCommand { 5 | static create(): BaseCommand { 6 | return new ExecuteApex(); 7 | } 8 | 9 | constructor() { 10 | super('Execute Apex', 'execute-apex'); 11 | this.async = false; 12 | this.body.args.ui = true; 13 | } 14 | } -------------------------------------------------------------------------------- /src/mavensmate/commands/executeSoql.ts: -------------------------------------------------------------------------------- 1 | import { ClientCommand } from './clientCommand'; 2 | 3 | import * as vscode from 'vscode'; 4 | import Promise = require('bluebird'); 5 | 6 | module.exports = class ExecuteSoql extends ClientCommand { 7 | static create(){ 8 | return new ExecuteSoql(); 9 | } 10 | 11 | constructor() { 12 | super('Execute SOQL', 'execute-soql'); 13 | this.async = true; 14 | this.body.soql = ''; 15 | } 16 | 17 | execute(selectedResource?: vscode.Uri): Thenable { 18 | let inputBoxOptions = { 19 | prompt: 'Enter SOQL to execute', 20 | ignoreFocusOut: true 21 | }; 22 | let inputBox = vscode.window.showInputBox(inputBoxOptions).then((soql) => { 23 | this.body.soql = soql; 24 | return super.execute(); 25 | }); 26 | return inputBox; 27 | } 28 | 29 | onStart(): Promise { 30 | return super.onStart() 31 | .then(() => { 32 | let executeSoqlMessage = 'Executing SOQL: ' + this.body.soql; 33 | this.mavensMateChannel.appendLine(executeSoqlMessage); 34 | }); 35 | } 36 | 37 | onSuccess(response): Promise { 38 | return super.onSuccess(response) 39 | .then(() => this.handleExecuteSoqlResponse(response)); 40 | } 41 | 42 | private handleExecuteSoqlResponse(response){ 43 | vscode.workspace.openTextDocument(response.result.path) 44 | .then((document) => { 45 | vscode.window.showTextDocument(document); 46 | }); 47 | } 48 | } -------------------------------------------------------------------------------- /src/mavensmate/commands/flushLogs.ts: -------------------------------------------------------------------------------- 1 | import { ClientCommand } from './clientCommand'; 2 | import { BaseCommand } from './baseCommand'; 3 | 4 | module.exports = class FlushLogs extends ClientCommand { 5 | static create(): BaseCommand { 6 | return new FlushLogs(); 7 | } 8 | 9 | constructor() { 10 | super('Flush Logs', 'flush-logs'); 11 | this.async = false; 12 | this.body.args.ui = true; 13 | } 14 | } -------------------------------------------------------------------------------- /src/mavensmate/commands/flushSoql.ts: -------------------------------------------------------------------------------- 1 | import { ClientCommand } from './clientCommand'; 2 | 3 | import * as vscode from 'vscode'; 4 | import path = require('path'); 5 | import Promise = require('bluebird'); 6 | 7 | 8 | module.exports = class FlushSoql extends ClientCommand { 9 | 10 | static create(){ 11 | return new FlushSoql(); 12 | } 13 | 14 | constructor() { 15 | super('Flush SOQL', 'flush-soql'); 16 | this.async = true; 17 | this.body.args.ui = false; 18 | } 19 | 20 | execute(selectedResource?: vscode.Uri): Thenable { 21 | return super.execute(); 22 | } 23 | } -------------------------------------------------------------------------------- /src/mavensmate/commands/forceCompileFile.ts: -------------------------------------------------------------------------------- 1 | import CompileFile = require('./compileFile'); 2 | 3 | module.exports = class ForceCompileFile extends CompileFile { 4 | static create(){ 5 | return new ForceCompileFile(); 6 | } 7 | 8 | constructor(){ 9 | super('Force Compile File'); 10 | 11 | this.body.force = true; 12 | } 13 | } -------------------------------------------------------------------------------- /src/mavensmate/commands/getCoverage.ts: -------------------------------------------------------------------------------- 1 | import { PathsCommand } from './pathsCommand'; 2 | import { BaseCommand } from './baseCommand'; 3 | import { MavensMateChannel } from '../../vscode/mavensMateChannel'; 4 | import { MavensMateCodeCoverage } from '../../vscode/mavensMateCodeCoverage'; 5 | 6 | import * as vscode from 'vscode'; 7 | import path = require('path'); 8 | import Promise = require('bluebird'); 9 | 10 | module.exports = class GetCoverage extends PathsCommand { 11 | mavensMateCodeCoverage: MavensMateCodeCoverage; 12 | static create() { 13 | return new GetCoverage(); 14 | } 15 | 16 | constructor() { 17 | super('Get Apex Code Coverage', 'get-coverage') 18 | this.mavensMateCodeCoverage = MavensMateCodeCoverage.getInstance(); 19 | } 20 | 21 | protected confirmPath(): Thenable { 22 | if(!this.checkIsMetadata()) { 23 | throw new Error(`File is not metadata: ${this.filePath}`); 24 | } else if (this.filePath.indexOf('apex-scripts') !== -1) { 25 | throw new Error(`Local Apex Scripts aren't covered by tests`); 26 | } else { 27 | return super.confirmPath(); 28 | } 29 | } 30 | 31 | onSuccess(response): Promise { 32 | return super.onSuccess(response) 33 | .then(() => this.handleCoverageResponse(response)); 34 | } 35 | 36 | private handleCoverageResponse(response) { 37 | if (response.result && response.result != []) { 38 | for (let pathEnd in response.result) { 39 | let workspaceRoot = vscode.workspace.rootPath; 40 | let filePath = path.join(workspaceRoot, 'src', 'classes', pathEnd); 41 | 42 | let coverageResult = response.result[pathEnd]; 43 | let uncoveredLines: number[] = coverageResult.uncoveredLines; 44 | 45 | this.mavensMateCodeCoverage.report(filePath, coverageResult.percentCovered, uncoveredLines); 46 | } 47 | } else { 48 | let message = `No Apex Code Coverage Available: ${this.baseName} (${this.filePath})`; 49 | this.mavensMateChannel.appendLine(message); 50 | vscode.window.showWarningMessage(message); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/mavensmate/commands/getOrgWideCoverage.ts: -------------------------------------------------------------------------------- 1 | import { ClientCommand } from './clientCommand'; 2 | import { BaseCommand } from './baseCommand'; 3 | import { MavensMateChannel } from '../../vscode/mavensMateChannel'; 4 | import { MavensMateCodeCoverage } from '../../vscode/mavensMateCodeCoverage'; 5 | 6 | import * as vscode from 'vscode'; 7 | import path = require('path'); 8 | import Promise = require('bluebird'); 9 | 10 | class GetOrgWideCoverage extends ClientCommand { 11 | mavensMateCodeCoverage: MavensMateCodeCoverage; 12 | static create(){ 13 | return new GetOrgWideCoverage(); 14 | } 15 | 16 | constructor() { 17 | super('Get Org Wide Apex Code Coverage', 'get-coverage') 18 | this.mavensMateCodeCoverage = MavensMateCodeCoverage.getInstance(); 19 | this.body.global = true; 20 | } 21 | 22 | onSuccess(response): Promise { 23 | return super.onSuccess(response) 24 | .then(() => this.handleCoverageResponse(response)); 25 | } 26 | 27 | private handleCoverageResponse(response){ 28 | if(response.result && typeof response.result == 'number') { 29 | let orgWideCoverage = `Org Wide Test Coverage: ${response.result}%`; 30 | vscode.window.showInformationMessage(orgWideCoverage); 31 | } else { 32 | let message = `No Apex Code Coverage Available Org Wide`; 33 | this.mavensMateChannel.appendLine(message); 34 | vscode.window.showWarningMessage(message); 35 | } 36 | } 37 | } 38 | 39 | export = GetOrgWideCoverage; 40 | -------------------------------------------------------------------------------- /src/mavensmate/commands/hideCoverageCommand.ts: -------------------------------------------------------------------------------- 1 | import { PathsCommand } from './pathsCommand'; 2 | import { BaseCommand } from './baseCommand'; 3 | import { MavensMateChannel } from '../../vscode/mavensMateChannel'; 4 | import { MavensMateCodeCoverage } from '../../vscode/mavensMateCodeCoverage'; 5 | 6 | import * as vscode from 'vscode'; 7 | import path = require('path'); 8 | import Promise = require('bluebird'); 9 | 10 | module.exports = class HideCoverage extends PathsCommand { 11 | mavensMateCodeCoverage: MavensMateCodeCoverage; 12 | static create() { 13 | return new HideCoverage(); 14 | } 15 | 16 | constructor() { 17 | super('Hide Apex Code Coverage', 'get-coverage') 18 | this.mavensMateCodeCoverage = MavensMateCodeCoverage.getInstance(); 19 | } 20 | 21 | protected confirmPath(): Thenable { 22 | if(!this.checkIsMetadata()) { 23 | throw new Error(`File is not metadata: ${this.filePath}`); 24 | } else if (this.filePath.indexOf('apex-scripts') !== -1) { 25 | throw new Error(`Local Apex Scripts aren't covered by tests`); 26 | } else { 27 | return super.confirmPath(); 28 | } 29 | } 30 | 31 | onSuccess(response): Promise { 32 | return super.onSuccess(response) 33 | .then(() => this.handleCoverageResponse(response)); 34 | } 35 | 36 | private handleCoverageResponse(response) { 37 | if (response.result && response.result != []) { 38 | for (let pathEnd in response.result) { 39 | let workspaceRoot = vscode.workspace.rootPath; 40 | let filePath = path.join(workspaceRoot, 'src', 'classes', pathEnd); 41 | 42 | let coverageResult = response.result[pathEnd]; 43 | let uncoveredLines: number[] = coverageResult.uncoveredLines; 44 | 45 | this.mavensMateCodeCoverage.clearReport(filePath, coverageResult.percentCovered); 46 | } 47 | } else { 48 | let message = `No Apex Code Coverage Available: ${this.baseName} (${this.filePath})`; 49 | this.mavensMateChannel.appendLine(message); 50 | vscode.window.showWarningMessage(message); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/mavensmate/commands/index.ts: -------------------------------------------------------------------------------- 1 | import fs = require('fs'); 2 | import path = require('path'); 3 | import { BaseCommand } from './baseCommand'; 4 | import { MavensMateChannel } from '../../vscode/mavensMateChannel'; 5 | import { MavensMateClient } from '../mavensMateClient'; 6 | 7 | interface CommandDirectoryInterface { [id: string]: any } 8 | 9 | export function commandDirectory(): CommandDirectoryInterface { 10 | let commandFiles = fs.readdirSync(__dirname); 11 | let commandDirectory: CommandDirectoryInterface = { }; 12 | for(let commandFile of commandFiles){ 13 | if(commandFile.endsWith('js')){ 14 | let commandBaseName = path.basename(commandFile, '.js'); 15 | let commandKey = 'mavensmate.' + commandBaseName; 16 | console.info(`MavensMate: Attempting to import ${commandKey}`); 17 | 18 | let importedCommand = require('./' + commandFile); 19 | if(importedCommand.create){ 20 | console.info(`MavensMate: Imported ${commandKey}`); 21 | commandDirectory[commandKey] = importedCommand; 22 | } else { 23 | console.warn(`MavensMate: ${commandKey} not imported because it does not have a static create method`); 24 | } 25 | } 26 | } 27 | 28 | return commandDirectory; 29 | } -------------------------------------------------------------------------------- /src/mavensmate/commands/indexMetadata.ts: -------------------------------------------------------------------------------- 1 | import { ClientCommand } from './clientCommand'; 2 | import { BaseCommand } from './baseCommand'; 3 | 4 | module.exports = class IndexMetadata extends ClientCommand { 5 | static create(): BaseCommand { 6 | return new IndexMetadata(); 7 | } 8 | 9 | constructor() { 10 | super('Index Metadata', 'index-metadata'); 11 | } 12 | } -------------------------------------------------------------------------------- /src/mavensmate/commands/newApexClass.ts: -------------------------------------------------------------------------------- 1 | import { ClientCommand } from './clientCommand'; 2 | import { BaseCommand } from './baseCommand'; 3 | 4 | module.exports = class NewApexClass extends ClientCommand { 5 | static create(): BaseCommand { 6 | return new NewApexClass(); 7 | } 8 | 9 | constructor() { 10 | super('New Apex Class', 'new-metadata'); 11 | this.async = false; 12 | this.body.args.ui = true; 13 | this.body.args.type = 'ApexClass'; 14 | } 15 | } -------------------------------------------------------------------------------- /src/mavensmate/commands/newApexScript.ts: -------------------------------------------------------------------------------- 1 | import { ClientCommand } from './clientCommand'; 2 | import { BaseCommand } from './baseCommand'; 3 | import * as vscode from 'vscode'; 4 | 5 | module.exports = class EditProject extends ClientCommand { 6 | static create(): BaseCommand { 7 | return new EditProject(); 8 | } 9 | 10 | constructor() { 11 | super('New Apex Script', 'new-apex-script'); 12 | this.async = false; 13 | this.body.args.ui = true; 14 | } 15 | 16 | execute(): Thenable { 17 | let inputBoxOptions = { 18 | prompt: 'Provide a name for the Apex Script', 19 | ignoreFocusOut: true 20 | }; 21 | let inputBoxPromise = vscode.window.showInputBox(inputBoxOptions).then((apexScriptName) => { 22 | if(apexScriptName && apexScriptName.length > 0){ 23 | this.body.name = apexScriptName; 24 | return super.execute(); 25 | } 26 | }); 27 | return inputBoxPromise; 28 | } 29 | } -------------------------------------------------------------------------------- /src/mavensmate/commands/newApexTrigger.ts: -------------------------------------------------------------------------------- 1 | import { ClientCommand } from './clientCommand'; 2 | import { BaseCommand } from './baseCommand'; 3 | 4 | module.exports = class NewApexTrigger extends ClientCommand { 5 | static create(): BaseCommand { 6 | return new NewApexTrigger(); 7 | } 8 | 9 | constructor() { 10 | super('New Apex Trigger', 'new-metadata'); 11 | this.async = false; 12 | this.body.args.ui = true; 13 | this.body.args.type = 'ApexTrigger'; 14 | } 15 | } -------------------------------------------------------------------------------- /src/mavensmate/commands/newLightningApp.ts: -------------------------------------------------------------------------------- 1 | import { ClientCommand } from './clientCommand'; 2 | import { BaseCommand } from './baseCommand'; 3 | 4 | module.exports = class NewLightningApp extends ClientCommand { 5 | static create(): BaseCommand { 6 | return new NewLightningApp(); 7 | } 8 | 9 | constructor() { 10 | super('New Lightning App', 'new-lightning-app'); 11 | this.async = false; 12 | this.body.args.ui = true; 13 | } 14 | } -------------------------------------------------------------------------------- /src/mavensmate/commands/newLightningComponent.ts: -------------------------------------------------------------------------------- 1 | import { ClientCommand } from './clientCommand'; 2 | import { BaseCommand } from './baseCommand'; 3 | 4 | module.exports = class NewLightningComponent extends ClientCommand { 5 | static create(): BaseCommand { 6 | return new NewLightningComponent(); 7 | } 8 | 9 | constructor() { 10 | super('New Lightning Component', 'new-lightning-component'); 11 | this.async = false; 12 | this.body.args.ui = true; 13 | } 14 | } -------------------------------------------------------------------------------- /src/mavensmate/commands/newLightningEvent.ts: -------------------------------------------------------------------------------- 1 | import { ClientCommand } from './clientCommand'; 2 | import { BaseCommand } from './baseCommand'; 3 | 4 | module.exports = class NewLightningEvent extends ClientCommand { 5 | static create(): BaseCommand { 6 | return new NewLightningEvent(); 7 | } 8 | 9 | constructor() { 10 | super('New Lightning Event', 'new-lightning-event'); 11 | this.async = false; 12 | this.body.args.ui = true; 13 | } 14 | } -------------------------------------------------------------------------------- /src/mavensmate/commands/newLightningInterface.ts: -------------------------------------------------------------------------------- 1 | import { ClientCommand } from './clientCommand'; 2 | import { BaseCommand } from './baseCommand'; 3 | 4 | module.exports = class NewLightningInterface extends ClientCommand { 5 | static create(): BaseCommand { 6 | return new NewLightningInterface(); 7 | } 8 | 9 | constructor() { 10 | super('New Lightning Interface', 'new-lightning-interface'); 11 | this.async = false; 12 | this.body.args.ui = true; 13 | } 14 | } -------------------------------------------------------------------------------- /src/mavensmate/commands/newLightningTokens.ts: -------------------------------------------------------------------------------- 1 | import { ClientCommand } from './clientCommand'; 2 | import { BaseCommand } from './baseCommand'; 3 | 4 | module.exports = class NewLightningTokens extends ClientCommand { 5 | static create(): BaseCommand { 6 | return new NewLightningTokens(); 7 | } 8 | 9 | constructor() { 10 | super('New Lightning Tokens', 'new-lightning-tokens'); 11 | this.async = false; 12 | this.body.args.ui = true; 13 | } 14 | } -------------------------------------------------------------------------------- /src/mavensmate/commands/newProject.ts: -------------------------------------------------------------------------------- 1 | import { ClientCommand } from './clientCommand'; 2 | import { BaseCommand } from './baseCommand'; 3 | 4 | module.exports = class NewProject extends ClientCommand { 5 | static create(): BaseCommand { 6 | return new NewProject(); 7 | } 8 | 9 | constructor() { 10 | super('New Project', 'new-project'); 11 | this.async = false; 12 | this.body.args.ui = true; 13 | this.allowWithoutProject = true; 14 | } 15 | } -------------------------------------------------------------------------------- /src/mavensmate/commands/newVisualforceComponent.ts: -------------------------------------------------------------------------------- 1 | import { ClientCommand } from './clientCommand'; 2 | import { BaseCommand } from './baseCommand'; 3 | 4 | module.exports = class NewVisualforceComponent extends ClientCommand { 5 | static create(): BaseCommand { 6 | return new NewVisualforceComponent(); 7 | } 8 | 9 | constructor() { 10 | super('New Visualforce Component', 'new-metadata'); 11 | this.async = false; 12 | this.body.args.ui = true; 13 | this.body.args.type = 'ApexComponent'; 14 | } 15 | } -------------------------------------------------------------------------------- /src/mavensmate/commands/newVisualforcePage.ts: -------------------------------------------------------------------------------- 1 | import { ClientCommand } from './clientCommand'; 2 | import { BaseCommand } from './baseCommand'; 3 | 4 | module.exports = class NewVisualforcePage extends ClientCommand { 5 | static create(): BaseCommand { 6 | return new NewVisualforcePage(); 7 | } 8 | 9 | constructor() { 10 | super('New Visualforce Page', 'new-metadata'); 11 | this.async = false; 12 | this.body.args.ui = true; 13 | this.body.args.type = 'ApexPage'; 14 | } 15 | } -------------------------------------------------------------------------------- /src/mavensmate/commands/oAuthProject.ts: -------------------------------------------------------------------------------- 1 | import { ClientCommand } from './clientCommand'; 2 | import { BaseCommand } from './baseCommand'; 3 | 4 | class OAuthProjectCommand extends ClientCommand { 5 | static create(): BaseCommand { 6 | return new OAuthProjectCommand(); 7 | } 8 | 9 | constructor() { 10 | super('oAuth Project', 'oauth-project'); 11 | this.async = false; 12 | this.body.args.ui = true; 13 | } 14 | } 15 | 16 | export = OAuthProjectCommand; -------------------------------------------------------------------------------- /src/mavensmate/commands/openGlobalSettings.ts: -------------------------------------------------------------------------------- 1 | import { ClientCommand } from './clientCommand'; 2 | import { BaseCommand } from './baseCommand'; 3 | 4 | module.exports = class OpenGlobalSettings extends ClientCommand { 5 | static create(): BaseCommand { 6 | return new OpenGlobalSettings(); 7 | } 8 | 9 | constructor() { 10 | super('Open Settings', 'open-settings'); 11 | this.async = false; 12 | this.body.args.ui = true; 13 | this.allowWithoutProject = true; 14 | } 15 | } -------------------------------------------------------------------------------- /src/mavensmate/commands/openMetadata.ts: -------------------------------------------------------------------------------- 1 | import { PathsCommand } from './pathsCommand'; 2 | 3 | import * as vscode from 'vscode'; 4 | let languagesToCompileOnSave = new Set(['apex', 'visualforce', 'xml', 'javascript']); 5 | 6 | module.exports = class OpenMetadata extends PathsCommand { 7 | static create(){ 8 | return new OpenMetadata(); 9 | } 10 | 11 | constructor() { 12 | super('Open Metadata', 'open-metadata'); 13 | this.async = false; 14 | this.body.callThrough = true; 15 | this.body.args.ui = false; 16 | } 17 | } -------------------------------------------------------------------------------- /src/mavensmate/commands/openProject.ts: -------------------------------------------------------------------------------- 1 | import { BaseCommand } from './baseCommand'; 2 | import ProjectQuickPick = require('../../vscode/projectQuickPick'); 3 | 4 | module.exports = class OpenProject extends BaseCommand { 5 | static create() { 6 | return new OpenProject(); 7 | } 8 | 9 | constructor() { 10 | super('Open Project'); 11 | this.allowWithoutProject = true; 12 | } 13 | 14 | execute(): Thenable { 15 | return Promise.resolve(ProjectQuickPick.showProjectListAndOpen()); 16 | } 17 | } -------------------------------------------------------------------------------- /src/mavensmate/commands/openSalesforce.ts: -------------------------------------------------------------------------------- 1 | import { ClientCommand } from './clientCommand'; 2 | import { BaseCommand } from './baseCommand'; 3 | 4 | module.exports = class EditProject extends ClientCommand { 5 | static create(): BaseCommand { 6 | return new EditProject(); 7 | } 8 | 9 | constructor() { 10 | super('Open Salesforce', 'open-sfdc'); 11 | this.async = false; 12 | this.body.args.ui = true; 13 | } 14 | } -------------------------------------------------------------------------------- /src/mavensmate/commands/openSettings.ts: -------------------------------------------------------------------------------- 1 | import { ClientCommand } from './clientCommand'; 2 | import { BaseCommand } from './baseCommand'; 3 | import * as vscode from 'vscode'; 4 | 5 | class OpenSettings extends ClientCommand { 6 | static create(): BaseCommand { 7 | return new OpenSettings(); 8 | } 9 | 10 | constructor() { 11 | super('Open Settings', 'open-settings'); 12 | this.async = false; 13 | this.body.args.ui = true; 14 | this.allowWithoutProject = true; 15 | } 16 | } 17 | 18 | export = OpenSettings; -------------------------------------------------------------------------------- /src/mavensmate/commands/openUI.ts: -------------------------------------------------------------------------------- 1 | import { ClientCommand } from './clientCommand'; 2 | import { BaseCommand } from './baseCommand'; 3 | import * as vscode from 'vscode'; 4 | 5 | class OpenUI extends ClientCommand { 6 | static create(): BaseCommand { 7 | return new OpenUI(); 8 | } 9 | 10 | constructor() { 11 | super('Open UI', 'open-ui'); 12 | this.async = false; 13 | this.body.args.ui = true; 14 | this.allowWithoutProject = true; 15 | } 16 | 17 | onSuccess(response) { 18 | return super.onSuccess(response); 19 | } 20 | } 21 | 22 | export = OpenUI; -------------------------------------------------------------------------------- /src/mavensmate/commands/pathsCommand.ts: -------------------------------------------------------------------------------- 1 | import { ClientCommand } from './clientCommand'; 2 | 3 | import * as vscode from 'vscode'; 4 | import Promise = require('bluebird'); 5 | import path = require('path'); 6 | 7 | export abstract class PathsCommand extends ClientCommand { 8 | filePath: string; 9 | baseName: string; 10 | 11 | constructor(label: string, id: string) { 12 | super(label, id); 13 | } 14 | 15 | execute(selectedResource?: vscode.Uri): Thenable { 16 | if(selectedResource && selectedResource.scheme === 'file'){ 17 | this.filePath = selectedResource.fsPath; 18 | } else if(vscode.window.activeTextEditor && vscode.window.activeTextEditor.document) { 19 | this.filePath = vscode.window.activeTextEditor.document.uri.fsPath; 20 | } 21 | return this.confirmPath() 22 | .then(() => { 23 | this.body.paths = [this.filePath]; 24 | return super.execute(); 25 | }); 26 | } 27 | 28 | protected confirmPath(): Thenable { 29 | if(this.filePath && this.filePath.length > 0){ 30 | this.baseName = path.basename(this.filePath); 31 | return Promise.resolve(true); 32 | } else { 33 | throw new Error(`A file path is required for ${this.label}`); 34 | } 35 | } 36 | 37 | protected checkIsMetadata(): boolean { 38 | let isMetadata: boolean = false; 39 | const ignoredFileNames = ['package.xml', 'destructiveChanges.xml', 'destructiveChangesPost.xml', 'destructiveChangesPre.xml']; 40 | 41 | if(this.filePath) { 42 | let srcDirectoryPath = path.join(vscode.workspace.rootPath, 'src'); 43 | let underWorkspaceSrcDirectory = this.filePath.startsWith(srcDirectoryPath); 44 | let ignoredFileNamesContainsBaseName = ignoredFileNames.indexOf(this.baseName) == -1; 45 | 46 | isMetadata = underWorkspaceSrcDirectory && ignoredFileNamesContainsBaseName; 47 | } 48 | 49 | return isMetadata; 50 | } 51 | 52 | onStart(): Promise{ 53 | return super.onStart() 54 | .then(() => { 55 | return this.outputPathProcessed(); 56 | }); 57 | } 58 | 59 | private outputPathProcessed(){ 60 | let message = `${this.baseName} (${this.filePath})` 61 | return this.mavensMateChannel.appendLine(message); 62 | } 63 | 64 | onSuccess(response): Promise { 65 | return super.onSuccess(response) 66 | .then(() => { 67 | this.outputPathProcessed(); 68 | return response; 69 | }); 70 | } 71 | 72 | onFailure(response): Promise { 73 | return super.onFailure(response) 74 | .then(() => { 75 | this.outputPathProcessed().then(response); 76 | return response; 77 | }); 78 | } 79 | } -------------------------------------------------------------------------------- /src/mavensmate/commands/refreshFile.ts: -------------------------------------------------------------------------------- 1 | import { PathsCommand } from './pathsCommand'; 2 | 3 | import * as vscode from 'vscode'; 4 | 5 | module.exports = class RefreshFile extends PathsCommand { 6 | static create(){ 7 | return new RefreshFile(); 8 | } 9 | 10 | constructor() { 11 | super('Refresh File', 'refresh-metadata'); 12 | this.async = true; 13 | this.body.args.ui = false; 14 | } 15 | 16 | protected confirmPath(): Thenable { 17 | return super.confirmPath().then(() => this.promptForConfirmation()); 18 | } 19 | 20 | private promptForConfirmation(){ 21 | let confirmMessage = `Are you sure you want to refresh ${ this.baseName } from Salesforce?`; 22 | return vscode.window.showInformationMessage(confirmMessage, 'Yes').then((answer) => { 23 | if(answer === 'Yes'){ 24 | return Promise.resolve(); 25 | } else { 26 | return Promise.reject('Refresh File Cancelled'); 27 | } 28 | }); 29 | } 30 | } -------------------------------------------------------------------------------- /src/mavensmate/commands/refreshFolder.ts: -------------------------------------------------------------------------------- 1 | import { PathsCommand } from './pathsCommand'; 2 | 3 | import * as vscode from 'vscode'; 4 | 5 | module.exports = class RefreshFolder extends PathsCommand { 6 | static create(){ 7 | return new RefreshFolder(); 8 | } 9 | 10 | constructor() { 11 | super('Refresh Folder', 'refresh-metadata'); 12 | this.async = true; 13 | this.body.args.ui = false; 14 | } 15 | 16 | protected confirmPath(): Thenable { 17 | return super.confirmPath().then(() => this.promptForConfirmation()); 18 | } 19 | 20 | private promptForConfirmation(){ 21 | let confirmMessage = `Are you sure you want to refresh ${ this.baseName } from Salesforce?`; 22 | return vscode.window.showInformationMessage(confirmMessage, 'Yes').then((answer) => { 23 | if(answer === 'Yes'){ 24 | return Promise.resolve(); 25 | } else { 26 | return Promise.reject('Refresh File Cancelled'); 27 | } 28 | }); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/mavensmate/commands/runApexScript.ts: -------------------------------------------------------------------------------- 1 | import { PathsCommand } from './pathsCommand'; 2 | 3 | import * as vscode from 'vscode'; 4 | import Promise = require('bluebird'); 5 | import path = require('path'); 6 | 7 | let languagesToRun = new Set(['apex']); 8 | 9 | class RunApexScript extends PathsCommand { 10 | static create(label?: string){ 11 | if(!label){ 12 | label = 'Run Apex Script'; 13 | } 14 | return new RunApexScript(label); 15 | } 16 | 17 | constructor(label: string) { 18 | super(label, 'run-apex-script'); 19 | } 20 | 21 | protected confirmPath(): Thenable { 22 | if(this.filePath.includes('apex-scripts')){ 23 | return super.confirmPath(); 24 | } else { 25 | return Promise.reject(`Run Apex Script is only for running local Apex scripts.`); 26 | } 27 | } 28 | 29 | 30 | onSuccess(response): Promise { 31 | return super.onSuccess(response) 32 | .then((response) => { 33 | for(let scriptName in response.result){ 34 | let scriptResult = response.result[scriptName]; 35 | if(scriptResult.success == true && scriptResult.compiled == true){ 36 | let message = 'Sucessfully Ran Apex Script: ' + scriptName; 37 | this.mavensMateChannel.appendLine(message); 38 | } else if(!scriptResult.success || scriptResult.success == false){ 39 | this.handleFailedRun(scriptResult); 40 | } 41 | } 42 | }); 43 | } 44 | 45 | private handleFailedRun(scriptResult){ 46 | let compileProblem = scriptResult.compileProblem; 47 | if(compileProblem && compileProblem != null){ 48 | let lineNumber = scriptResult.line; 49 | let column = scriptResult.column; 50 | 51 | let message = `[Line: ${lineNumber}, Column: ${column}] ${compileProblem}`; 52 | this.mavensMateChannel.appendLine(message); 53 | } 54 | 55 | let exceptionMessage = scriptResult.exceptionMessage; 56 | if(exceptionMessage && exceptionMessage != null){ 57 | this.mavensMateChannel.appendLine(exceptionMessage); 58 | } 59 | 60 | let exceptionStackTrace = scriptResult.exceptionStackTrace; 61 | if(exceptionStackTrace && exceptionStackTrace != null){ 62 | this.mavensMateChannel.appendLine(exceptionStackTrace); 63 | } 64 | } 65 | } 66 | export = RunApexScript; -------------------------------------------------------------------------------- /src/mavensmate/commands/runTests.ts: -------------------------------------------------------------------------------- 1 | import { ClientCommand } from './clientCommand'; 2 | import * as vscode from 'vscode'; 3 | import Promise = require('bluebird'); 4 | import path = require('path'); 5 | 6 | 7 | class RunTests extends ClientCommand { 8 | filePath: string; 9 | baseName: string; 10 | 11 | static create() { 12 | return new RunTests(); 13 | } 14 | 15 | constructor() { 16 | super('Run Apex Tests', 'run-tests'); 17 | this.async = false; 18 | this.body.args.ui = true; 19 | } 20 | 21 | execute(selectedResource?: vscode.Uri): Thenable { 22 | if(selectedResource && selectedResource.scheme === 'file'){ 23 | this.filePath = selectedResource.fsPath; 24 | } else if(vscode.window.activeTextEditor && vscode.window.activeTextEditor.document) { 25 | this.filePath = vscode.window.activeTextEditor.document.uri.fsPath; 26 | } 27 | return this.confirmPath() 28 | .then(() => { 29 | this.body.classes = [this.baseName]; 30 | return super.execute(); 31 | }); 32 | } 33 | 34 | protected confirmPath(): Thenable { 35 | if(this.filePath && this.filePath.length > 0){ 36 | this.baseName = path.basename(this.filePath, '.cls'); 37 | } 38 | return Promise.resolve(); 39 | } 40 | 41 | onStart(): Promise{ 42 | return super.onStart() 43 | .then(() => { 44 | return this.outputPathProcessed(); 45 | }); 46 | } 47 | 48 | private outputPathProcessed(){ 49 | if(this.baseName && this.filePath){ 50 | let message = `${this.baseName} (${this.filePath})` 51 | return this.mavensMateChannel.appendLine(message); 52 | } else { 53 | return Promise.resolve(); 54 | } 55 | } 56 | 57 | onSuccess(response): Promise { 58 | return super.onSuccess(response) 59 | .then(() => { 60 | this.outputPathProcessed(); 61 | return response; 62 | }); 63 | } 64 | 65 | onFailure(response): Promise { 66 | return super.onFailure(response) 67 | .then(() => { 68 | this.outputPathProcessed().then(response); 69 | return response; 70 | }); 71 | } 72 | } 73 | 74 | export = RunTests; -------------------------------------------------------------------------------- /src/mavensmate/commands/runTestsAsync.ts: -------------------------------------------------------------------------------- 1 | import { ClientCommand } from './clientCommand'; 2 | import * as vscode from 'vscode'; 3 | import Promise = require('bluebird'); 4 | import path = require('path'); 5 | 6 | 7 | class RunTestsAsync extends ClientCommand { 8 | filePath: string; 9 | baseName: string; 10 | 11 | static create() { 12 | return new RunTestsAsync(); 13 | } 14 | 15 | constructor() { 16 | super('Run Apex Tests', 'run-tests'); 17 | this.async = true; 18 | this.body.args.ui = false; 19 | } 20 | 21 | execute(selectedResource?: vscode.Uri): Thenable { 22 | if(selectedResource && selectedResource.scheme === 'file'){ 23 | this.filePath = selectedResource.fsPath; 24 | } else if(vscode.window.activeTextEditor && vscode.window.activeTextEditor.document) { 25 | this.filePath = vscode.window.activeTextEditor.document.uri.fsPath; 26 | } 27 | return this.confirmPath() 28 | .then(() => { 29 | this.body.classes = [this.baseName]; 30 | return super.execute(); 31 | }); 32 | } 33 | 34 | protected confirmPath(): Thenable { 35 | if(this.filePath && this.filePath.length > 0){ 36 | this.baseName = path.basename(this.filePath, '.cls'); 37 | } 38 | return Promise.resolve(); 39 | } 40 | 41 | onStart(): Promise{ 42 | return super.onStart() 43 | .then(() => { 44 | return this.outputPathProcessed(); 45 | }); 46 | } 47 | 48 | private outputPathProcessed(){ 49 | if(this.baseName && this.filePath){ 50 | let message = `${this.baseName} (${this.filePath})` 51 | return this.mavensMateChannel.appendLine(message); 52 | } else { 53 | return Promise.resolve(); 54 | } 55 | } 56 | 57 | onSuccess(response): Promise { 58 | return super.onSuccess(response) 59 | .then(() => { 60 | this.outputPathProcessed(); 61 | let classResults = response.result.testResults[this.baseName]; 62 | this.mavensMateChannel.appendLine(classResults.ExtendedStatus, classResults.Status); 63 | for(let i = 0; i < classResults.results.length; i++){ 64 | let testResult = classResults.results[i]; 65 | this.mavensMateChannel.appendLine(testResult.MethodName, testResult.Outcome); 66 | if(testResult.Outcome == 'Fail'){ 67 | this.mavensMateChannel.appendLine(testResult.Message); 68 | this.mavensMateChannel.appendLine(testResult.StackTrace); 69 | } 70 | } 71 | return response; 72 | }); 73 | } 74 | 75 | onFailure(response): Promise { 76 | return super.onFailure(response) 77 | .then(() => { 78 | this.outputPathProcessed().then(response); 79 | return response; 80 | }); 81 | } 82 | } 83 | 84 | export = RunTestsAsync; -------------------------------------------------------------------------------- /src/mavensmate/commands/runTestsMethods.ts: -------------------------------------------------------------------------------- 1 | import { ClientCommand, SalesforceTest} from './clientCommand'; 2 | import * as vscode from 'vscode'; 3 | import Promise = require('bluebird'); 4 | import path = require('path'); 5 | 6 | class runTestsMethods extends ClientCommand { 7 | filePath: string; 8 | baseName: string; 9 | static create() { 10 | return new runTestsMethods(); 11 | } 12 | 13 | constructor() { 14 | super('Run Apex Test Methods', 'run-test-method'); 15 | this.async = false; 16 | this.body.args.ui = false; 17 | } 18 | 19 | execute(selectedResource?: vscode.Uri): Thenable { 20 | if (selectedResource && selectedResource.scheme === 'file') { 21 | this.filePath = selectedResource.fsPath; 22 | } else if (vscode.window.activeTextEditor && vscode.window.activeTextEditor.document) { 23 | this.filePath = vscode.window.activeTextEditor.document.uri.fsPath; 24 | } 25 | return this.confirmPath() 26 | .then(() => this.getUserInput()) 27 | .then((input: any) => { 28 | let methodNames = input.replace(' ','') 29 | .split(',') 30 | .filter(test => test != ''); 31 | this.body.tests = [ 32 | { 33 | testNameOrPath : this.baseName + '.cls', 34 | methodNames: methodNames 35 | } 36 | ]; 37 | this.body.skipCoverage = true; 38 | return super.execute(); 39 | }); 40 | } 41 | protected getUserInput(): Thenable { 42 | return vscode.window.showInputBox(); 43 | } 44 | 45 | protected confirmPath(): Thenable { 46 | if (this.filePath && this.filePath.length > 0) { 47 | this.baseName = path.basename(this.filePath, '.cls'); 48 | } 49 | return Promise.resolve(); 50 | } 51 | 52 | onStart(): Promise { 53 | return super.onStart() 54 | .then(() => { 55 | return this.outputPathProcessed(); 56 | }); 57 | } 58 | 59 | private outputPathProcessed() { 60 | if (this.baseName && this.filePath) { 61 | let message = `${this.baseName} (${this.filePath})` 62 | return this.mavensMateChannel.appendLine(message); 63 | } else { 64 | return Promise.resolve(); 65 | } 66 | } 67 | 68 | onSuccess(response): Promise { 69 | return super.onSuccess(response) 70 | .then(() => { 71 | this.outputPathProcessed(); 72 | let classResults = response.testResults[this.baseName]; 73 | this.mavensMateChannel.appendLine(classResults.ExtendedStatus, classResults.Status); 74 | for (let i = 0; i < classResults.results.length; i++) { 75 | let testResult = classResults.results[i]; 76 | this.mavensMateChannel.appendLine(testResult.MethodName, testResult.Outcome); 77 | if (testResult.Outcome == 'Fail') { 78 | this.mavensMateChannel.appendLine(testResult.Message); 79 | this.mavensMateChannel.appendLine(testResult.StackTrace); 80 | } 81 | } 82 | return response; 83 | }); 84 | } 85 | 86 | onFailure(response): Promise { 87 | return super.onFailure(response) 88 | .then(() => { 89 | this.outputPathProcessed().then(response); 90 | return response; 91 | }); 92 | } 93 | } 94 | 95 | export = runTestsMethods; -------------------------------------------------------------------------------- /src/mavensmate/commands/startLogging.ts: -------------------------------------------------------------------------------- 1 | import { ClientCommand } from './clientCommand'; 2 | import { BaseCommand } from './baseCommand'; 3 | 4 | module.exports = class StartLogging extends ClientCommand { 5 | static create(): BaseCommand { 6 | return new StartLogging(); 7 | } 8 | 9 | constructor() { 10 | super('Start Logging', 'start-logging'); 11 | this.async = false; 12 | this.body.args.ui = true; 13 | } 14 | } -------------------------------------------------------------------------------- /src/mavensmate/commands/stopLogging.ts: -------------------------------------------------------------------------------- 1 | import { ClientCommand } from './clientCommand'; 2 | import { BaseCommand } from './baseCommand'; 3 | 4 | module.exports = class StopLogging extends ClientCommand { 5 | static create(): BaseCommand { 6 | return new StopLogging(); 7 | } 8 | 9 | constructor() { 10 | super('Stop Logging', 'stop-logging'); 11 | this.async = false; 12 | this.body.args.ui = true; 13 | } 14 | } -------------------------------------------------------------------------------- /src/mavensmate/commands/toggleOutput.ts: -------------------------------------------------------------------------------- 1 | import { BaseCommand } from './baseCommand'; 2 | import { MavensMateChannel } from '../../vscode/mavensMateChannel'; 3 | 4 | module.exports = class ToggleOutput extends BaseCommand { 5 | static create(){ 6 | return new ToggleOutput(); 7 | } 8 | 9 | constructor() { 10 | super('Toggle Output'); 11 | this.allowWithoutProject = true; 12 | } 13 | 14 | execute(): Thenable { 15 | return this.mavensMateChannel.toggle(); 16 | } 17 | } -------------------------------------------------------------------------------- /src/mavensmate/handlers/compileResponseHandler.ts: -------------------------------------------------------------------------------- 1 | import * as CompileResultParser from './parsers/compileResultParser'; 2 | import { getPathEnd } from '../../workspace/componentPath'; 3 | import * as vscode from 'vscode'; 4 | import path = require('path'); 5 | import Promise = require('bluebird'); 6 | import { MavensMateDiagnostics } from '../../vscode/mavensMateDiagnostics'; 7 | import * as DiagnosticFactory from '../../vscode/diagnosticFactory'; 8 | 9 | let mavensMateDiagnostics: MavensMateDiagnostics = MavensMateDiagnostics.getInstance(); 10 | 11 | export function handleCompileResponse(compileResponse): Promise{ 12 | let result = compileResponse.result; 13 | 14 | let handlePromise: Promise; 15 | if(result){ 16 | if(result.status && result.status === 'Conflict'){ 17 | handlePromise = handleConflict(result); 18 | } else { 19 | handlePromise = handleSuccess(result); 20 | } 21 | } else { 22 | console.error(`MavensMate Compile Error Response ${compileResponse}`); 23 | return Promise.reject(compileResponse.error); 24 | } 25 | return handlePromise; 26 | } 27 | 28 | function handleConflict(result){ 29 | let conflictPromises: Thenable[] = []; 30 | let conflicts: any[] = result.details.conflicts; 31 | for(let conflictFile in conflicts){ 32 | let conflict = conflicts[conflictFile]; 33 | let lastModifiedBy = conflict.remote.LastModifiedBy.Name; 34 | let lastModifiedDate = conflict.remote.LastModifiedDate; 35 | 36 | let conflictMessage = `A conflict has been detected. ${conflictFile} ` 37 | + `was last modified by ${lastModifiedBy} on ${lastModifiedDate}`; 38 | let overwriteMessage = 'Overwrite Server Copy'; 39 | let conflictPromise = vscode.window.showWarningMessage(conflictMessage, overwriteMessage) 40 | .then((answer) => { 41 | if(answer == overwriteMessage){ 42 | return forceCompileConflictFile(conflict); 43 | } 44 | }); 45 | conflictPromises.push(conflictPromise); 46 | } 47 | 48 | return Promise.all(conflictPromises); 49 | } 50 | 51 | function forceCompileConflictFile(conflict){ 52 | let fileName = conflict.local.fileName; 53 | let pathEnd = getPathEnd(fileName); 54 | let workspaceRoot = vscode.workspace.rootPath; 55 | let documentUri = vscode.Uri.file(path.join(workspaceRoot, 'src', pathEnd)); 56 | return vscode.commands.executeCommand('mavensmate.forceCompileFile', documentUri); 57 | } 58 | 59 | function handleSuccess(result): Promise{ 60 | let componentSuccesses = CompileResultParser.getSuccessesFromDetails(result); 61 | let componentFailures = CompileResultParser.getFailuresFromDetails(result); 62 | 63 | return promiseClearDiagnosticsForSuccesses(componentSuccesses) 64 | .then(() => { 65 | return promiseDiagnosticsFromFailures(componentFailures) 66 | }); 67 | } 68 | 69 | function promiseClearDiagnosticsForSuccesses(componentSuccesses): Promise{ 70 | return promiseComponentsWithMatchingDocuments(componentSuccesses) 71 | .then(clearDiagnostics); 72 | } 73 | 74 | function promiseComponentsWithMatchingDocuments(components){ 75 | let promisedComponents = []; 76 | for(let component of components){ 77 | let pathEnd = getPathEnd(component.fileName); 78 | let workspaceRoot = vscode.workspace.rootPath; 79 | let documentUri = path.join(workspaceRoot, 'src', pathEnd); 80 | 81 | let withMatchingDocument = vscode.workspace.openTextDocument(documentUri) 82 | .then((document) => { 83 | component.fullPath = document.uri.fsPath; 84 | component.document = document; 85 | return component; 86 | }); 87 | promisedComponents.push(withMatchingDocument); 88 | } 89 | 90 | return Promise.all(promisedComponents); 91 | } 92 | 93 | function clearDiagnostics(componentSuccesses){ 94 | let filePathsToClear: Set = new Set(); 95 | for(let componentSuccess of componentSuccesses){ 96 | filePathsToClear.add(componentSuccess.fullPath); 97 | } 98 | filePathsToClear.forEach((filePath) => { 99 | let fileUri = vscode.Uri.file(filePath); 100 | mavensMateDiagnostics.diagnostics.set(fileUri, []); 101 | }); 102 | } 103 | 104 | function promiseDiagnosticsFromFailures(componentFailures){ 105 | return promiseComponentsWithMatchingDocuments(componentFailures) 106 | .then(DiagnosticFactory.buildDiagnosticsByFilePath) 107 | .then(updateDiagnostics) 108 | .then(showProblemsPanel) 109 | } 110 | 111 | function updateDiagnostics(diagnosticsByFilePath: { [filePath: string]: vscode.Diagnostic[] }){ 112 | for(let filePath in diagnosticsByFilePath){ 113 | let fileUri = vscode.Uri.file(filePath); 114 | mavensMateDiagnostics.diagnostics.set(fileUri, diagnosticsByFilePath[filePath]); 115 | } 116 | return diagnosticsByFilePath; 117 | } 118 | 119 | function showProblemsPanel(diagnosticsByFilePath: { [filePath: string]: vscode.Diagnostic[] }){ 120 | if (Object.keys(diagnosticsByFilePath).length > 0) { 121 | return vscode.commands.executeCommand('workbench.actions.view.problems'); 122 | } 123 | } -------------------------------------------------------------------------------- /src/mavensmate/handlers/parsers/compileResultParser.ts: -------------------------------------------------------------------------------- 1 | export function getFailuresFromDetails(result){ 2 | let failures = []; 3 | let componentFailures = result.details.componentFailures; 4 | if(componentFailures){ 5 | if(componentFailures instanceof Array){ 6 | processComponentsAsArray(failures, componentFailures, 'componentFailures'); 7 | } else { 8 | failures.push(componentFailures); 9 | } 10 | } 11 | return failures; 12 | } 13 | 14 | function processComponentsAsArray(compileComponents, detailComponents, componentType){ 15 | for(let detailComponent of detailComponents){ 16 | if(detailComponent.DeployDetails){ 17 | pushDeployDetailComponents(compileComponents, detailComponent.DeployDetails[componentType]); 18 | } else { 19 | compileComponents.push(detailComponent); 20 | } 21 | } 22 | } 23 | 24 | function pushDeployDetailComponents(compileComponents, deployDetailComponents){ 25 | for(let deployDetailComponent of deployDetailComponents){ 26 | compileComponents.push(deployDetailComponent); 27 | } 28 | } 29 | 30 | export function getSuccessesFromDetails(result){ 31 | let successes = []; 32 | let componentSuccesses = result.details.componentSuccesses; 33 | if(componentSuccesses){ 34 | if(componentSuccesses instanceof Array){ 35 | processComponentsAsArray(successes, componentSuccesses, 'componentSuccesses'); 36 | } else { 37 | successes.push(componentSuccesses); 38 | } 39 | } 40 | return successes; 41 | } -------------------------------------------------------------------------------- /src/mavensmate/mavensMateAppConfig.ts: -------------------------------------------------------------------------------- 1 | import path = require('path'); 2 | import * as operatingSystem from '../../src/workspace/operatingSystem'; 3 | import * as jsonFile from '../../src/workspace/jsonFile'; 4 | 5 | export function getConfig(){ 6 | let configFileDirectory = getUserHomeDirectory(); 7 | let configFileName = '.mavensmate-config.json'; 8 | let appConfigFilePath = path.join(configFileDirectory, configFileName); 9 | 10 | return jsonFile.open(appConfigFilePath); 11 | } 12 | 13 | function getUserHomeDirectory() { 14 | if(operatingSystem.isWindows()){ 15 | return process.env.USERPROFILE; 16 | } else if(operatingSystem.isMac() || operatingSystem.isLinux()){ 17 | return process.env.HOME; 18 | } else { 19 | throw new operatingSystem.UnexpectedPlatformError('Was not windows, mac, or linux'); 20 | } 21 | } -------------------------------------------------------------------------------- /src/mavensmate/mavensMateClient.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import * as vscode from 'vscode'; 3 | import axios, { AxiosRequestConfig, AxiosInstance, AxiosPromise } from 'axios'; 4 | import urlJoin = require('url-join'); 5 | import Promise = require('bluebird'); 6 | import { ClientCommand } from './commands/clientCommand'; 7 | import { ProjectSettings } from './projectSettings'; 8 | import { MavensMateStatus } from '../vscode/mavensMateStatus'; 9 | 10 | export interface Options { 11 | baseURL: string; 12 | projectId?: string; 13 | } 14 | 15 | export class MavensMateClient implements vscode.Disposable { 16 | baseURL: string; 17 | projectId: string; 18 | mavensMateStatus: MavensMateStatus; 19 | 20 | private static _instance: MavensMateClient = null; 21 | 22 | static getInstance(){ 23 | if(MavensMateClient._instance == null){ 24 | MavensMateClient._instance = new MavensMateClient(); 25 | } 26 | return MavensMateClient._instance; 27 | } 28 | 29 | constructor(){ 30 | this.baseURL = vscode.workspace.getConfiguration().get('mavensMateDesktop.baseURL'); 31 | this.mavensMateStatus = MavensMateStatus.getInstance(); 32 | let projectSettings = ProjectSettings.getProjectSettings(); 33 | if(projectSettings){ 34 | this.projectId = projectSettings.id; 35 | } 36 | } 37 | 38 | isAppAvailable(){ 39 | let isAvailableURL = urlJoin(this.baseURL, '/app/home/index'); 40 | let getOptions: AxiosRequestConfig = { 41 | url: isAvailableURL 42 | }; 43 | return axios(isAvailableURL).then(() => { 44 | this.mavensMateStatus.showAppIsAvailable(); 45 | }, () => { 46 | this.mavensMateStatus.showAppIsUnavailable(); 47 | }); 48 | } 49 | 50 | sendCommand(command: ClientCommand) : Promise { 51 | let postOptions = this.getPostOptionsForCommand(command, this.baseURL); 52 | let promiseCommandSend = Promise.resolve(axios(postOptions)); 53 | this.mavensMateStatus.showAppIsThinking(); 54 | return promiseCommandSend.bind(this).then(this.handleResponse); 55 | } 56 | 57 | private getPostOptionsForCommand(command: ClientCommand, baseURL: string){ 58 | let asyncParam: number = (command.async ? 1 : 0); 59 | 60 | let commandParmeters = 'command=' + command.id +'&async=' + asyncParam; 61 | 62 | if(this.hasProjectId()){ 63 | commandParmeters += '&pid=' + this.projectId; 64 | } 65 | let commandURL = urlJoin(baseURL, '/execute?' + commandParmeters); 66 | let commandHeaders = { 67 | 'Content-Type': 'application/json', 68 | 'MavensMate-Editor-Agent': 'vscode' 69 | }; 70 | 71 | let postOptions: AxiosRequestConfig = { 72 | method: 'POST', 73 | url: commandURL, 74 | headers: commandHeaders, 75 | data: command.body 76 | }; 77 | return postOptions; 78 | } 79 | 80 | handleResponse(commandResponse){ 81 | if(commandResponse.data && commandResponse.data.status && commandResponse.data.status == 'pending'){ 82 | this.mavensMateStatus.showAppIsThinking(); 83 | return Promise.delay(500, commandResponse) 84 | .bind(this) 85 | .then(this.poll) 86 | .then(this.handleResponse); 87 | } else { 88 | return commandResponse.data; 89 | } 90 | } 91 | 92 | poll(commandResponse){ 93 | let statusURL = urlJoin(this.baseURL, '/execute/' + commandResponse.data.id); 94 | let statusHeaders = { 95 | 'MavensMate-Editor-Agent': 'vscode' 96 | }; 97 | 98 | let getOptions: AxiosRequestConfig = { 99 | url: statusURL, 100 | headers: statusHeaders 101 | }; 102 | 103 | return axios(getOptions); 104 | } 105 | 106 | private hasProjectId(){ 107 | return this.projectId && this.projectId != ''; 108 | } 109 | 110 | dispose(){ 111 | 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/mavensmate/projectSettings.ts: -------------------------------------------------------------------------------- 1 | import path = require('path'); 2 | import vscode = require('vscode'); 3 | import file = require('../workspace/jsonFile'); 4 | import Promise = require('bluebird'); 5 | 6 | export class ProjectSettings { 7 | id: string; 8 | projectName: string; 9 | instanceUrl: string; 10 | 11 | private static _instances: { [projectPath:string]: ProjectSettings } = {}; 12 | 13 | static getProjectSettings(projectPath?: string): ProjectSettings { 14 | projectPath = workspaceRootIfBlank(projectPath); 15 | 16 | if(projectPath && !ProjectSettings._instances[projectPath]){ 17 | let settingsPath = buildSettingsPath(projectPath); 18 | console.info(`Retrieving settings at path: ${ settingsPath }`); 19 | ProjectSettings._instances[projectPath] = file.open(settingsPath); 20 | } 21 | 22 | return ProjectSettings._instances[projectPath]; 23 | } 24 | 25 | static hasProjectSettings(projectPath?: string): boolean { 26 | projectPath = workspaceRootIfBlank(projectPath); 27 | if(!ProjectSettings._instances[projectPath]){ 28 | ProjectSettings.getProjectSettings(projectPath); 29 | } 30 | return ProjectSettings._instances[projectPath] !== null 31 | && ProjectSettings._instances[projectPath] !== undefined; 32 | } 33 | } 34 | 35 | function workspaceRootIfBlank(projectPath?: string): string { 36 | return projectPath || vscode.workspace.rootPath; 37 | } 38 | 39 | function buildSettingsPath(projectPath: string){ 40 | return path.join(projectPath, 'config', '.settings'); 41 | } 42 | -------------------------------------------------------------------------------- /src/vscode/commandRegistrar.ts: -------------------------------------------------------------------------------- 1 | import vscode = require('vscode'); 2 | import commandIndex = require('../mavensmate/commands/index'); 3 | 4 | export function registerCommands(){ 5 | let registerCommand = vscode.commands.registerCommand; 6 | let commandDirectory = commandIndex.commandDirectory(); 7 | 8 | for(let commandKey in commandDirectory){ 9 | let Command = commandDirectory[commandKey]; 10 | 11 | registerCommand(commandKey, (selectedResource?: vscode.Uri) => { 12 | let command = Command.create(); 13 | return command.invoke(selectedResource); 14 | }); 15 | } 16 | } -------------------------------------------------------------------------------- /src/vscode/diagnosticFactory.ts: -------------------------------------------------------------------------------- 1 | import { Diagnostic, Position, Range, DiagnosticCollection, languages, Uri, workspace, TextDocument } from 'vscode'; 2 | 3 | export function buildDiagnosticsByFilePath(componentFailures){ 4 | let diagnosticsByFilePath = {}; 5 | for(let componentFailure of componentFailures){ 6 | if(!diagnosticsByFilePath[componentFailure.fullPath]){ 7 | diagnosticsByFilePath[componentFailure.fullPath] = []; 8 | } 9 | let diagnostic = buildDiagnostic(componentFailure); 10 | diagnosticsByFilePath[componentFailure.fullPath].push(diagnostic); 11 | } 12 | return diagnosticsByFilePath; 13 | } 14 | 15 | export function buildDiagnostic(componentFailure): Diagnostic { 16 | let lineNumber: number = 0; 17 | let columnNumber: number = 0; 18 | 19 | if(componentFailure.lineNumber){ 20 | lineNumber = componentFailure.lineNumber - 1; 21 | } 22 | if(componentFailure.columnNumber){ 23 | columnNumber = componentFailure.columnNumber - 1; 24 | } 25 | 26 | let start = new Position(lineNumber, columnNumber); 27 | let end = new Position(lineNumber+1, 0); 28 | let range = new Range(start, end); 29 | 30 | if(componentFailure.document){ 31 | let document: TextDocument = componentFailure.document; 32 | let wordRange = document.getWordRangeAtPosition(start); 33 | if(wordRange){ 34 | range = wordRange; 35 | } 36 | } 37 | 38 | let newDiagnostic = new Diagnostic(range, componentFailure.problem); 39 | 40 | return newDiagnostic; 41 | } -------------------------------------------------------------------------------- /src/vscode/mavensMateChannel.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import { window, OutputChannel, Disposable } from 'vscode'; 3 | import { MavensMateClient } from '../mavensmate/mavensMateClient'; 4 | import { getConfiguration } from './mavensMateConfiguration'; 5 | import Promise = require('bluebird'); 6 | 7 | export class MavensMateChannel implements Disposable { 8 | channel: OutputChannel; 9 | waitingOnCount: number; 10 | waitingDelay: number; 11 | isShowing: boolean; 12 | isWaiting: boolean; 13 | 14 | private static _instance: MavensMateChannel = null; 15 | static getInstance(): MavensMateChannel{ 16 | if(MavensMateChannel._instance == null){ 17 | MavensMateChannel._instance = new MavensMateChannel(); 18 | } 19 | return MavensMateChannel._instance; 20 | } 21 | 22 | constructor(){ 23 | if(MavensMateChannel._instance){ 24 | throw new Error("Error: Instantiation failed. Singleton module! Use .getInstance() instead of new."); 25 | } 26 | MavensMateChannel._instance = this; 27 | this.channel = window.createOutputChannel('MavensMate'); 28 | this.waitingOnCount = 0; 29 | this.waitingDelay = getConfiguration('mavensMate.hideOutputDelay'); 30 | this.isShowing = false; 31 | this.isWaiting = false; 32 | } 33 | 34 | appendStatus(message: string){ 35 | return this.appendLine(message, 'STATUS'); 36 | } 37 | 38 | appendError(message: string){ 39 | return this.appendLine(message, 'ERROR'); 40 | } 41 | 42 | appendLine(message: string, level?: string){ 43 | return Promise.resolve().then(() => { 44 | let promiseResult = null; 45 | let tabs = (level && level.length > 5 ? 1 : 2); 46 | let formattedMessage = `${ '\t'.repeat(tabs) }${message}`; 47 | if(level){ 48 | formattedMessage = `[${level}]${formattedMessage}`; 49 | } else { 50 | formattedMessage = '\t\t' + formattedMessage; 51 | } 52 | this.channel.appendLine(formattedMessage); 53 | 54 | if(!this.isShowing) { 55 | this.show(); 56 | } 57 | 58 | if(this.waitingOnCount == 0 && this.isWaiting == false){ 59 | this.isWaiting = true; 60 | promiseResult = Promise.delay(this.waitingDelay).then(() => { 61 | if(this.waitingOnCount == 0){ 62 | if(level == 'STATUS' && getConfiguration('mavensMate.hideOutputOnSuccess')){ 63 | this.hide(); 64 | } 65 | this.isWaiting = false; 66 | } 67 | }); 68 | } 69 | return promiseResult; 70 | }); 71 | } 72 | 73 | show(){ 74 | let preserveFocus = true; 75 | this.channel.show(preserveFocus); 76 | this.isShowing = true; 77 | } 78 | 79 | hide(){ 80 | this.channel.hide(); 81 | this.isShowing = false; 82 | } 83 | 84 | toggle(): Thenable{ 85 | if(this.isShowing){ 86 | this.hide(); 87 | return Promise.resolve(); 88 | } else { 89 | this.waitingOnCount++; 90 | this.show(); 91 | return Promise.delay(this.waitingDelay).then(() => { 92 | this.waitingOnCount--; 93 | }); 94 | } 95 | } 96 | 97 | dispose(){ 98 | this.channel.dispose(); 99 | } 100 | } -------------------------------------------------------------------------------- /src/vscode/mavensMateCodeCoverage.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | export class MavensMateCodeCoverage implements vscode.Disposable { 4 | decorationType: vscode.TextEditorDecorationType; 5 | uncoveredRangesByPath: { [fsPath: string]: vscode.Range[] } 6 | percentCoveredByPath: { [fsPath: string]: number } 7 | coverageStatus: vscode.StatusBarItem; 8 | 9 | private static _instance: MavensMateCodeCoverage = null; 10 | 11 | static getInstance(): MavensMateCodeCoverage { 12 | if (MavensMateCodeCoverage._instance == null) { 13 | MavensMateCodeCoverage._instance = new MavensMateCodeCoverage(); 14 | } 15 | 16 | return MavensMateCodeCoverage._instance; 17 | } 18 | 19 | constructor() { 20 | this.decorationType = this.getDecorationType(); 21 | this.uncoveredRangesByPath = {}; 22 | this.percentCoveredByPath = {}; 23 | this.coverageStatus = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 1); 24 | this.coverageStatus.command = 'mavensmate.getCoverage'; 25 | 26 | this.refreshActivePercentCovered(); 27 | this.coverageStatus.show(); 28 | vscode.window.onDidChangeActiveTextEditor((textEditor) => { 29 | this.onDidChangeActiveTextEditor(textEditor); 30 | }); 31 | } 32 | 33 | private getDecorationType(): vscode.TextEditorDecorationType { 34 | let options: vscode.DecorationRenderOptions = { 35 | isWholeLine: true, 36 | backgroundColor: 'rgba(215, 44, 44, 0.3)' 37 | }; 38 | return vscode.window.createTextEditorDecorationType(options); 39 | } 40 | 41 | private onDidChangeActiveTextEditor(textEditor: vscode.TextEditor) { 42 | if (textEditor.document.languageId === 'apex') { 43 | this.refreshUncoveredDecorations(); 44 | this.refreshActivePercentCovered(); 45 | this.coverageStatus.show(); 46 | } else { 47 | this.coverageStatus.hide(); 48 | } 49 | } 50 | 51 | report(fsPath, percentCovered: number, uncoveredLines: number[]) { 52 | let uncoveredRanges: vscode.Range[] = uncoveredLines.map(asRange); 53 | this.uncoveredRangesByPath[fsPath] = uncoveredRanges; 54 | this.percentCoveredByPath[fsPath] = percentCovered; 55 | this.refreshActivePercentCovered(); 56 | this.refreshUncoveredDecorations(); 57 | } 58 | 59 | clearReport(fsPath, percentCovered: number) { 60 | this.percentCoveredByPath[fsPath] = percentCovered; 61 | this.refreshActivePercentCovered(); 62 | this.clearDecorations(); 63 | } 64 | 65 | private refreshUncoveredDecorations() { 66 | for (let textEditor of vscode.window.visibleTextEditors) { 67 | let uncoveredRanges = this.uncoveredRangesByPath[textEditor.document.fileName]; 68 | if (uncoveredRanges !== undefined) { 69 | textEditor.setDecorations(this.decorationType, uncoveredRanges); 70 | } 71 | } 72 | } 73 | 74 | private clearDecorations() { 75 | var textEditor = vscode.window.activeTextEditor; 76 | let options: vscode.DecorationRenderOptions = { 77 | isWholeLine: true, 78 | backgroundColor: 'rgba(255, 255, 255, 1.0)' 79 | }; 80 | var decoration: vscode.TextEditorDecorationType = vscode.window.createTextEditorDecorationType(options); 81 | var lineNumber = textEditor.document.lineCount; 82 | var range = new vscode.Range(new vscode.Position(0, 0), new vscode.Position(lineNumber - 1, 0)); 83 | textEditor.setDecorations(decoration, [range]); 84 | } 85 | 86 | private refreshActivePercentCovered() { 87 | if (vscode.window.activeTextEditor && vscode.window.activeTextEditor.document) { 88 | let activePath = vscode.window.activeTextEditor.document.uri.fsPath; 89 | if (this.percentCoveredByPath[activePath] != undefined) { 90 | let percentCovered = this.percentCoveredByPath[activePath]; 91 | this.coverageStatus.text = `${percentCovered}% Covered`; 92 | } else { 93 | this.coverageStatus.text = `Get Test Coverage`; 94 | } 95 | } 96 | } 97 | 98 | dispose() { 99 | this.coverageStatus.dispose(); 100 | } 101 | } 102 | 103 | function asRange(lineNumber) { 104 | let vscodeLineNumber = lineNumber - 1; 105 | let start = new vscode.Position(vscodeLineNumber, 0); 106 | let end = new vscode.Position(vscodeLineNumber, 0); 107 | return new vscode.Range(start, end); 108 | } -------------------------------------------------------------------------------- /src/vscode/mavensMateConfiguration.ts: -------------------------------------------------------------------------------- 1 | import { workspace } from 'vscode'; 2 | 3 | export function getConfiguration(key: string): T{ 4 | return workspace.getConfiguration().get(key); 5 | } -------------------------------------------------------------------------------- /src/vscode/mavensMateDiagnostics.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | export class MavensMateDiagnostics { 4 | diagnostics: vscode.DiagnosticCollection; 5 | 6 | private static _instance: MavensMateDiagnostics = null; 7 | 8 | static getInstance(): MavensMateDiagnostics { 9 | if(MavensMateDiagnostics._instance == null){ 10 | MavensMateDiagnostics._instance = new MavensMateDiagnostics(); 11 | } 12 | return MavensMateDiagnostics._instance; 13 | } 14 | 15 | constructor() { 16 | this.diagnostics = vscode.languages.createDiagnosticCollection('mavensmate'); 17 | } 18 | } -------------------------------------------------------------------------------- /src/vscode/mavensMateStatus.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import { window, StatusBarAlignment, StatusBarItem, Disposable } from 'vscode'; 3 | import { MavensMateChannel } from '../../src/vscode/mavensMateChannel'; 4 | import Promise = require('bluebird'); 5 | 6 | export class MavensMateStatus implements Disposable { 7 | appStatus: StatusBarItem; 8 | thinkingIndex: number; 9 | thinkingMax: number = 5; 10 | 11 | private static _instance: MavensMateStatus = null; 12 | 13 | static getInstance(): MavensMateStatus{ 14 | if(MavensMateStatus._instance == null){ 15 | MavensMateStatus._instance = MavensMateStatus.create(); 16 | } 17 | return MavensMateStatus._instance; 18 | } 19 | 20 | static create(): MavensMateStatus { 21 | return new MavensMateStatus(); 22 | } 23 | 24 | constructor(){ 25 | this.appStatus = window.createStatusBarItem(StatusBarAlignment.Left,2); 26 | this.appStatus.command = 'mavensmate.toggleOutput'; 27 | this.appStatus.text = "MavensMate"; 28 | this.appStatus.show(); 29 | 30 | this.thinkingIndex = 0; 31 | } 32 | 33 | showAppIsThinking(){ 34 | let thinkingLeftSpace = '$(dash)'.repeat(this.thinkingIndex); 35 | let thinkingRightSpace = '$(dash)'.repeat(this.thinkingMax - this.thinkingIndex); 36 | this.appStatus.text = `MavensMate [${thinkingLeftSpace}$(chevron-right)${thinkingRightSpace}]`; 37 | this.thinkingIndex++; 38 | if(this.thinkingIndex > this.thinkingMax){ 39 | this.thinkingIndex = 0; 40 | } 41 | } 42 | 43 | showAppIsAvailable(){ 44 | this.appStatus.text = "MavensMate $(check)"; 45 | } 46 | 47 | showAppIsUnavailable(){ 48 | this.appStatus.text = "MavensMate $(alert)"; 49 | } 50 | 51 | dispose(){ 52 | this.appStatus.dispose(); 53 | } 54 | } -------------------------------------------------------------------------------- /src/vscode/projectQuickPick.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import Promise = require('bluebird'); 3 | 4 | import { window, QuickPickItem, commands, Uri } from 'vscode'; 5 | import { promiseList, projectDirectory } from '../../src/workspace/projectList'; 6 | 7 | export interface projectQuickPickItem extends QuickPickItem { 8 | path: string; 9 | } 10 | 11 | export function showProjectQuickPick() : Thenable{ 12 | return promiseList().then((projects) => { 13 | return Promise.map(projects, buildQuickPickProject) 14 | .then(window.showQuickPick); 15 | }); 16 | } 17 | 18 | function buildQuickPickProject(project: projectDirectory) : projectQuickPickItem{ 19 | return { 20 | description: project.workspace, 21 | detail: project.path, 22 | label: project.name, 23 | path: project.path 24 | }; 25 | } 26 | 27 | export function openProject(projectItem: projectQuickPickItem) { 28 | if(projectItem){ 29 | let projectUri = Uri.parse(projectItem.path); 30 | return commands.executeCommand('vscode.openFolder', projectUri).then(null, console.error); 31 | } else { 32 | console.warn('MavensMate: No project selected'); 33 | return; 34 | } 35 | } 36 | 37 | export function showProjectListAndOpen(): Thenable{ 38 | return showProjectQuickPick().then(openProject); 39 | } -------------------------------------------------------------------------------- /src/vscode/resourceBundleQuickPick.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import Promise = require('bluebird'); 3 | 4 | import { window, QuickPickItem, commands, Uri } from 'vscode'; 5 | import { promiseList, resourceBundle } from '../../src/workspace/resourceBundleList'; 6 | 7 | export interface resourceBundleQuickPickItem extends QuickPickItem { 8 | path: string; 9 | } 10 | 11 | export function showResourceBundleQuickPick() : Thenable{ 12 | return promiseList().then((resourceBundles) => { 13 | if(resourceBundles && resourceBundles.length > 0){ 14 | return Promise.map(resourceBundles, buildQuickPickResourceBundle) 15 | .then(window.showQuickPick); 16 | } else { 17 | return window.showWarningMessage('No Resource Bundles Found'); 18 | } 19 | }); 20 | } 21 | 22 | function buildQuickPickResourceBundle(resourceBundle: resourceBundle) : resourceBundleQuickPickItem{ 23 | return { 24 | description: resourceBundle.name, 25 | detail: resourceBundle.path, 26 | label: resourceBundle.name, 27 | path: resourceBundle.path 28 | }; 29 | } -------------------------------------------------------------------------------- /src/vscode/staticResourceQuickPick.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import Promise = require('bluebird'); 3 | 4 | import { window, QuickPickItem, commands, Uri } from 'vscode'; 5 | import { promiseList, staticResource } from '../../src/workspace/staticResourceList'; 6 | 7 | export interface staticResourceQuickPickItem extends QuickPickItem { 8 | path: string; 9 | } 10 | 11 | export function showStaticResourceQuickPick() : Thenable{ 12 | return promiseList().then((staticResources) => { 13 | return Promise.map(staticResources, buildQuickPickStaticResource) 14 | .then(window.showQuickPick); 15 | }); 16 | } 17 | 18 | function buildQuickPickStaticResource(staticResource: staticResource) : staticResourceQuickPickItem{ 19 | return { 20 | description: staticResource.name, 21 | detail: staticResource.path, 22 | label: staticResource.name, 23 | path: staticResource.path 24 | }; 25 | } -------------------------------------------------------------------------------- /src/workspace/componentPath.ts: -------------------------------------------------------------------------------- 1 | let pathEndRegEx = /(unpackaged)?([^\\/]*[\\/]\w+[\.]\w+)/; 2 | 3 | export function getPathEnd(path: string): string{ 4 | let pathEnd: string = path; 5 | let matches: string[] = pathEndRegEx.exec(path); 6 | 7 | if(matches && matches.length > 0){ 8 | pathEnd = matches[matches.length-1]; 9 | } else { 10 | console.error(`MavensMate: Failed to get the pathEnd from: ${path}`); 11 | } 12 | return pathEnd; 13 | } -------------------------------------------------------------------------------- /src/workspace/jsonFile.ts: -------------------------------------------------------------------------------- 1 | let fs = require('fs'); 2 | 3 | export function open(filePath : string){ 4 | try { 5 | let fileBody = fs.readFileSync(filePath); 6 | return JSON.parse(fileBody); 7 | } catch(openException){ 8 | console.warn('Failed to open ' + filePath); 9 | return null; 10 | } 11 | } -------------------------------------------------------------------------------- /src/workspace/operatingSystem.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import os = require('os'); 4 | 5 | export function isWindows(){ 6 | return os.platform().startsWith('win'); 7 | } 8 | 9 | export function isLinux(){ 10 | return os.platform().startsWith('linux'); 11 | 12 | } 13 | export function isMac(){ 14 | return os.platform().startsWith('darwin'); 15 | } 16 | 17 | export class UnexpectedPlatformError extends Error { 18 | constructor(message: string){ 19 | message = 'Unexpected Platform, current platform('+ os.platform() + '):' + message; 20 | super(message); 21 | } 22 | } -------------------------------------------------------------------------------- /src/workspace/projectList.ts: -------------------------------------------------------------------------------- 1 | import path = require('path'); 2 | import * as fs from './readDirAsync'; 3 | import Promise = require('bluebird'); 4 | import mavensMateAppConfig = require('../mavensmate/mavensMateAppConfig'); 5 | import { ProjectSettings } from '../mavensmate/projectSettings'; 6 | 7 | export interface projectDirectory { 8 | name: string, 9 | path: string, 10 | workspace: string 11 | } 12 | 13 | interface appConfig { 14 | mm_workspace: string[]; 15 | } 16 | 17 | export function promiseList() : Promise { 18 | let projects : Promise[] = []; 19 | let config : appConfig = mavensMateAppConfig.getConfig(); 20 | for(let workspace of config.mm_workspace){ 21 | let listProjects = (fileList) => { 22 | return listProjectsInWorkspaceFileList(workspace, fileList); 23 | }; 24 | let listProjectsInWorkplace = fs.readdir(workspace) 25 | .then(listProjects, console.error); 26 | 27 | projects.push(listProjectsInWorkplace); 28 | } 29 | return Promise.all(projects).then(flattenToListOfProjects); 30 | } 31 | 32 | function listProjectsInWorkspaceFileList(workspace, fileList) : Promise { 33 | let projects: Promise[] = []; 34 | for(let fileName of fileList){ 35 | if(notHiddenFile(fileName)){ 36 | projects.push(getProjectFromFileName(workspace, fileName)); 37 | } 38 | } 39 | 40 | return Promise.all(projects); 41 | } 42 | 43 | function notHiddenFile(fileName){ 44 | return !fileName.startsWith('.'); 45 | } 46 | 47 | function getProjectFromFileName(workspace, fileName){ 48 | let projectPath = path.join(workspace, fileName); 49 | 50 | return Promise.resolve().then(() => { 51 | if(ProjectSettings.hasProjectSettings(projectPath)){ 52 | return { name: fileName, path: projectPath, workspace: baseName(workspace) }; 53 | } else { 54 | console.warn(`MavensMate: No project settings found at ${ projectPath }`); 55 | } 56 | }); 57 | } 58 | 59 | function baseName(path){ 60 | return path.split(/[\\/]/).filter(notBlank).pop(); 61 | } 62 | 63 | function notBlank(string: String){ 64 | return string != null && string.length > 0 65 | } 66 | 67 | function flattenToListOfProjects(listsOfProjects){ 68 | return Array.prototype.concat.apply([], listsOfProjects).filter(project => project); 69 | } 70 | -------------------------------------------------------------------------------- /src/workspace/readDirAsync.ts: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | import bbPromise = require('bluebird'); 3 | 4 | export function readdir(filePath: string): bbPromise { 5 | return new bbPromise((resolve, reject) => { 6 | fs.readdir(filePath, (err, files) => { 7 | if (err) { 8 | console.error("Error reading directory " + filePath); 9 | reject(); 10 | } 11 | return resolve(files); 12 | }); 13 | }); 14 | } 15 | -------------------------------------------------------------------------------- /src/workspace/resourceBundleList.ts: -------------------------------------------------------------------------------- 1 | import path = require('path'); 2 | import * as fs from './readDirAsync'; 3 | import Promise = require('bluebird'); 4 | import * as vscode from 'vscode'; 5 | 6 | export interface resourceBundle { 7 | name: string, 8 | path: string 9 | } 10 | 11 | export function promiseList() : Promise { 12 | let resourceBundlesPath = buildResourceBundlesPath(); 13 | 14 | let listResourceBundlesInWorkspace = fs.readdir(resourceBundlesPath) 15 | .then(resourceBundlesFromFileList); 16 | 17 | return listResourceBundlesInWorkspace; 18 | } 19 | 20 | function buildResourceBundlesPath(): string{ 21 | let workspaceRoot = vscode.workspace.rootPath; 22 | let resourceBundlesPath = path.join(workspaceRoot, 'resource-bundles'); 23 | return resourceBundlesPath; 24 | } 25 | 26 | function resourceBundlesFromFileList(fileList) : Promise { 27 | let resourceBundles: Promise[] = []; 28 | for(let fileName of fileList){ 29 | if(isResourceBundle(fileName)){ 30 | resourceBundles.push(getResourceBundleFrom(fileName)); 31 | } 32 | } 33 | 34 | return Promise.all(resourceBundles); 35 | } 36 | 37 | function isResourceBundle(fileName: string){ 38 | return !fileName.startsWith('.'); 39 | } 40 | 41 | function getResourceBundleFrom(fileName): Promise{ 42 | let resourceBundlesPath = buildResourceBundlesPath(); 43 | let resourceBundlePath = path.join(resourceBundlesPath, fileName); 44 | let resourceBundleInfo: resourceBundle = { 45 | name: fileName, path: resourceBundlePath 46 | }; 47 | return Promise.resolve(resourceBundleInfo); 48 | } 49 | -------------------------------------------------------------------------------- /src/workspace/staticResourceList.ts: -------------------------------------------------------------------------------- 1 | import path = require('path'); 2 | import * as fs from './readDirAsync'; 3 | import Promise = require('bluebird'); 4 | import * as vscode from 'vscode'; 5 | 6 | export interface staticResource { 7 | name: string, 8 | path: string 9 | } 10 | 11 | export function promiseList() : Promise { 12 | let staticResourcesPath = buildStaticResourcesPath(); 13 | 14 | let listStaticResourcesInWorkspace = fs.readdir(staticResourcesPath) 15 | .then(staticResourcesFromFileList); 16 | 17 | return listStaticResourcesInWorkspace; 18 | } 19 | 20 | function buildStaticResourcesPath(): string{ 21 | let workspaceRoot = vscode.workspace.rootPath; 22 | let staticResourcesPath = path.join(workspaceRoot, 'src', 'staticresources'); 23 | return staticResourcesPath; 24 | } 25 | 26 | function staticResourcesFromFileList(fileList) : Promise { 27 | let staticResources: Promise[] = []; 28 | for(let fileName of fileList){ 29 | if(isStaticResource(fileName)){ 30 | staticResources.push(getStaticResourceFrom(fileName)); 31 | } 32 | } 33 | 34 | return Promise.all(staticResources); 35 | } 36 | 37 | function isStaticResource(fileName: string){ 38 | return !(fileName.startsWith('.') || fileName.includes('-meta.xml')); 39 | } 40 | 41 | function getStaticResourceFrom(fileName): Promise{ 42 | let staticResourcesPath = buildStaticResourcesPath(); 43 | let staticResourcePath = path.join(staticResourcesPath, fileName); 44 | let staticResourceInfo = { 45 | name: fileName, path: staticResourcePath 46 | }; 47 | return Promise.resolve(staticResourceInfo); 48 | } 49 | -------------------------------------------------------------------------------- /test/index.ts: -------------------------------------------------------------------------------- 1 | // 2 | // PLEASE DO NOT MODIFY / DELETE UNLESS YOU KNOW WHAT YOU ARE DOING 3 | // 4 | // This file is providing the test runner to use when running extension tests. 5 | // By default the test runner in use is Mocha based. 6 | // 7 | // You can provide your own test runner if you want to override it by exporting 8 | // a function run(testRoot: string, clb: (error:Error) => void) that the extension 9 | // host can call to run the tests. The test runner is expected to use console.log 10 | // to report the results back to the caller. When the tests are finished, return 11 | // a possible error to the callback or null if none. 12 | 13 | var testRunner = require('vscode/lib/testrunner'); 14 | 15 | // You can directly control Mocha options by uncommenting the following lines 16 | // See https://github.com/mochajs/mocha/wiki/Using-mocha-programmatically#set-options for more info 17 | testRunner.configure({ 18 | ui: 'tdd', // the TDD UI is being used in extension.test.ts (suite, test, etc.) 19 | useColors: true, // colored output from test results 20 | timeout: 5000 21 | }); 22 | 23 | module.exports = testRunner; -------------------------------------------------------------------------------- /test/mavensmate/mavensMateAppConfig.test.ts: -------------------------------------------------------------------------------- 1 | import assert = require('assert'); 2 | import sinon = require('sinon'); 3 | import path = require('path'); 4 | 5 | import operatingSystem = require('../../src/workspace/operatingSystem'); 6 | import jsonFile = require('../../src/workspace/jsonFile'); 7 | 8 | import mavensMateAppConfig = require('../../src/mavensmate/mavensMateAppConfig'); 9 | 10 | suite('mavensMate App Config', () => { 11 | let originalUserProfile = process.env.USERPROFILE; 12 | let originalHome = process.env.HOME; 13 | 14 | let isLinuxStub : sinon.SinonStub; 15 | let isMacStub : sinon.SinonStub; 16 | let isWindowsStub : sinon.SinonStub; 17 | 18 | let windowsJson = { isWindows: true }; 19 | let nonWindowsJson = { isWindows: false }; 20 | let openStub : sinon.SinonStub; 21 | 22 | setup(() => { 23 | process.env.USERPROFILE = 'userprofiletest'; 24 | process.env.HOME = 'hometest'; 25 | 26 | openStub = sinon.stub(jsonFile, 'open'); 27 | openStub.withArgs(path.normalize('userprofiletest/.mavensmate-config.json')).returns(windowsJson); 28 | openStub.withArgs(path.normalize('hometest/.mavensmate-config.json')).returns(nonWindowsJson); 29 | }); 30 | 31 | teardown(() => { 32 | process.env.USERPROFILE = originalUserProfile; 33 | process.env.HOME = originalHome; 34 | openStub.restore(); 35 | }); 36 | 37 | suite('when on a mac', () => { 38 | setup(() => { 39 | isLinuxStub = sinon.stub(operatingSystem, 'isLinux').returns(false); 40 | isMacStub = sinon.stub(operatingSystem, 'isMac').returns(true); 41 | isWindowsStub = sinon.stub(operatingSystem, 'isWindows').returns(false); 42 | }); 43 | 44 | test('it uses home', () => { 45 | mavensMateAppConfig.getConfig(); 46 | 47 | sinon.assert.calledWith(openStub, path.normalize('hometest/.mavensmate-config.json')); 48 | sinon.assert.neverCalledWith(openStub, path.normalize('userprofiletest/.mavensmate-config.json')); 49 | }); 50 | 51 | test('it parses correct json', () => { 52 | let returnedJson = mavensMateAppConfig.getConfig(); 53 | 54 | assert.equal(returnedJson.isWindows, false); 55 | }); 56 | 57 | teardown(() => { 58 | isLinuxStub.restore(); 59 | isMacStub.restore(); 60 | isWindowsStub.restore(); 61 | }); 62 | }); 63 | 64 | suite('when on windows', () => { 65 | setup(() => { 66 | isLinuxStub = sinon.stub(operatingSystem, 'isLinux').returns(false); 67 | isMacStub = sinon.stub(operatingSystem, 'isMac').returns(false); 68 | isWindowsStub = sinon.stub(operatingSystem, 'isWindows').returns(true); 69 | }); 70 | 71 | test('it uses userprofile', () => { 72 | mavensMateAppConfig.getConfig(); 73 | 74 | sinon.assert.calledWith(openStub, path.normalize('userprofiletest/.mavensmate-config.json')); 75 | sinon.assert.neverCalledWith(openStub, path.normalize('hometest/.mavensmate-config.json')); 76 | }); 77 | 78 | test('it parses correct json', () => { 79 | let returnedJson = mavensMateAppConfig.getConfig(); 80 | 81 | assert.equal(returnedJson.isWindows, true); 82 | }); 83 | 84 | teardown(() => { 85 | isLinuxStub.restore(); 86 | isMacStub.restore(); 87 | isWindowsStub.restore(); 88 | }); 89 | }); 90 | 91 | suite('when on linux', () => { 92 | setup(() => { 93 | isLinuxStub = sinon.stub(operatingSystem, 'isLinux').returns(true); 94 | isMacStub = sinon.stub(operatingSystem, 'isMac').returns(false); 95 | isWindowsStub = sinon.stub(operatingSystem, 'isWindows').returns(false); 96 | }); 97 | 98 | test('it uses home', () => { 99 | mavensMateAppConfig.getConfig(); 100 | 101 | sinon.assert.calledWith(openStub, path.normalize('hometest/.mavensmate-config.json')); 102 | sinon.assert.neverCalledWith(openStub, path.normalize('userprofiletest/.mavensmate-config.json')); 103 | }); 104 | 105 | test('it parses correct json', () => { 106 | let returnedJson = mavensMateAppConfig.getConfig(); 107 | 108 | assert.equal(returnedJson.isWindows, false); 109 | }); 110 | 111 | teardown(() => { 112 | isLinuxStub.restore(); 113 | isMacStub.restore(); 114 | isWindowsStub.restore(); 115 | }); 116 | }); 117 | }); 118 | -------------------------------------------------------------------------------- /test/mavensmate/mavensMateClient.test.ts: -------------------------------------------------------------------------------- 1 | import expect = require('expect.js'); 2 | import * as sinon from 'sinon'; 3 | import Promise = require('bluebird'); 4 | import vscode = require('vscode'); 5 | import axios, { AxiosStatic } from 'axios'; 6 | import moxios = require('moxios'); 7 | import querystring = require('querystring'); 8 | 9 | import { MavensMateClient } from '../../src/mavensmate/mavensMateClient'; 10 | import { MavensMateStatus } from '../../src/vscode/mavensMateStatus'; 11 | import { ClientCommand } from '../../src/mavensmate/commands/clientCommand'; 12 | import OpenUI = require('../../src/mavensmate/commands/openUI'); 13 | import CleanProject = require('../../src/mavensmate/commands/cleanProject'); 14 | 15 | suite("MavensMate Client", () => { 16 | let baseURL: string = 'http://localhost:55555'; 17 | let mavensMateClient: MavensMateClient = MavensMateClient.getInstance(); 18 | 19 | suite("getInstance", () => { 20 | teardown(() => { 21 | MavensMateClient.getInstance().baseURL = baseURL; 22 | }); 23 | 24 | test("returns client", () => { 25 | let returnedClient = MavensMateClient.getInstance(); 26 | 27 | expect(returnedClient).to.not.be(undefined); 28 | expect(typeof (returnedClient.isAppAvailable)).to.equal('function'); 29 | }); 30 | 31 | test("returns same client on subsequent calls", () => { 32 | let returnedClient = MavensMateClient.getInstance(); 33 | returnedClient.baseURL = 'blahhhhh'; 34 | 35 | let secondClient = MavensMateClient.getInstance(); 36 | expect(returnedClient).to.be(secondClient); 37 | expect(secondClient.baseURL).to.equal('blahhhhh'); 38 | }); 39 | }); 40 | 41 | suite("isAppAvailable", () => { 42 | let showAppIsAvailableSpy: sinon.SinonSpy; 43 | let showAppIsUnavailableSpy: sinon.SinonSpy; 44 | 45 | setup(() => { 46 | let mavensMateStatus = MavensMateStatus.getInstance(); 47 | 48 | showAppIsAvailableSpy = sinon.spy(mavensMateStatus, 'showAppIsAvailable'); 49 | showAppIsUnavailableSpy = sinon.spy(mavensMateStatus, 'showAppIsUnavailable'); 50 | }); 51 | 52 | teardown(() => { 53 | showAppIsAvailableSpy.restore(); 54 | showAppIsUnavailableSpy.restore(); 55 | }); 56 | 57 | suite("server is up", () => { 58 | 59 | setup(() => { 60 | moxios.install(); 61 | }); 62 | 63 | teardown(() => { 64 | moxios.uninstall(); 65 | }); 66 | 67 | test("returns true", (done) => { 68 | mavensMateClient.isAppAvailable(); 69 | moxios.wait(() => { 70 | let request = moxios.requests.mostRecent(); 71 | request.respondWith({ 72 | status: 200, 73 | response: 'OK' 74 | }).then(() => { 75 | expect(showAppIsAvailableSpy.calledOnce).to.be(true); 76 | expect(showAppIsUnavailableSpy.calledOnce).to.be(false); 77 | done(); 78 | }); 79 | }); 80 | }); 81 | }); 82 | 83 | suite("server is down", () => { 84 | 85 | setup(() => { 86 | moxios.install(); 87 | }); 88 | 89 | teardown(() => { 90 | moxios.uninstall(); 91 | }); 92 | 93 | test("returns an error", (done) => { 94 | mavensMateClient.isAppAvailable(); 95 | moxios.wait(() => { 96 | let request = moxios.requests.mostRecent(); 97 | request.respondWith({ 98 | status: 'ERR', 99 | response: 'ERR' 100 | }).then(() => { 101 | expect(showAppIsAvailableSpy.calledOnce).to.be(false); 102 | expect(showAppIsUnavailableSpy.calledOnce).to.be(true); 103 | done(); 104 | }); 105 | }); 106 | }); 107 | }); 108 | }); 109 | 110 | suite("sendCommand", () => { 111 | 112 | setup(() => { 113 | moxios.install(); 114 | }); 115 | 116 | teardown(() => { 117 | moxios.uninstall(); 118 | }); 119 | 120 | test("sends synchronous command", (done) => { 121 | let openUICommand: ClientCommand = new OpenUI(); 122 | 123 | mavensMateClient.sendCommand(openUICommand).then(() => { 124 | done() 125 | }, assertIfError); 126 | 127 | moxios.wait(() => { 128 | let request = moxios.requests.mostRecent(); 129 | expect(request.headers['Content-Type']).to.be('application/json'); 130 | expect(request.headers['MavensMate-Editor-Agent']).to.be('vscode'); 131 | expect(request.config.data).to.be('{"args":{"ui":true}}'); 132 | request.respondWith({ 133 | status: 200, 134 | response: 'OK' 135 | }); 136 | }); 137 | }); 138 | 139 | test("sends Asynchronous command", (done) => { 140 | let pendingResponse = { 141 | id: 'e14b82c0-2d98-11e6-a468-5bbc3ff5e056', 142 | status: 'pending' 143 | }; 144 | 145 | let completedResponse = { 146 | id: 'e14b82c0-2d98-11e6-a468-5bbc3ff5e056', 147 | complete: true, 148 | operation: "clean-project", 149 | result: { 150 | "message": "Success" 151 | } 152 | } 153 | let cleanProjectCommand: ClientCommand = new CleanProject(); 154 | 155 | mavensMateClient.sendCommand(cleanProjectCommand) 156 | .then(finalResponse => { 157 | expect(finalResponse.complete).to.be(true); 158 | done(); 159 | }); 160 | 161 | moxios.wait(() => { 162 | let request = moxios.requests.mostRecent(); 163 | expect(request.headers['Content-Type']).to.be('application/json'); 164 | expect(request.headers['MavensMate-Editor-Agent']).to.be('vscode'); 165 | expect(request.config.data).to.be('{"args":{"ui":false}}'); 166 | request.respondWith({ 167 | status: 200, 168 | response: pendingResponse 169 | }); 170 | moxios.wait(() => { 171 | let request = moxios.requests.mostRecent(); 172 | expect(request.headers['MavensMate-Editor-Agent']).to.be('vscode'); 173 | request.respondWith({ 174 | status: 200, 175 | response: pendingResponse 176 | }); 177 | moxios.wait(() => { 178 | let request = moxios.requests.mostRecent(); 179 | expect(request.headers['MavensMate-Editor-Agent']).to.be('vscode'); 180 | request.respondWith({ 181 | status: 200, 182 | response: pendingResponse 183 | }); 184 | moxios.wait(() => { 185 | let request = moxios.requests.mostRecent(); 186 | expect(request.headers['MavensMate-Editor-Agent']).to.be('vscode'); 187 | request.respondWith({ 188 | status: 200, 189 | response: completedResponse 190 | }); 191 | }, 550); 192 | }, 550); 193 | }, 550); 194 | }); 195 | }); 196 | }); 197 | }); 198 | 199 | function assertIfError(error) { 200 | expect().fail(error); 201 | } 202 | -------------------------------------------------------------------------------- /test/mavensmate/projectSettings.test.ts: -------------------------------------------------------------------------------- 1 | import expect = require('expect.js'); 2 | import sinon = require('sinon'); 3 | import path = require('path'); 4 | import fs = require('fs'); 5 | import Promise = require('bluebird'); 6 | 7 | import jsonFile = require('../../src/workspace/jsonFile'); 8 | import vscode = require('vscode'); 9 | 10 | import { ProjectSettings } from '../../src/mavensmate/projectSettings'; 11 | let workspacePath = vscode.workspace.rootPath; 12 | let workspaceSettingsPath = path.join(workspacePath, 'config', '.settings'); 13 | let projectPath = 'someProject'; 14 | let projectSettingsPath = path.join(projectPath, 'config', '.settings'); 15 | 16 | let testSettings: ProjectSettings = { 17 | id: 'testid1', 18 | projectName: 'project name', 19 | instanceUrl: 'instance' 20 | }; 21 | 22 | 23 | suite('projectSettings', () => { 24 | let statStub: sinon.SinonStub; 25 | let jsonFileStub: sinon.SinonStub; 26 | 27 | suite('when it does not exist', () => { 28 | setup(() => { 29 | let statFailure = Promise.reject('no settings'); 30 | 31 | statStub = sinon.stub(fs, 'stat'); 32 | statStub.withArgs(workspaceSettingsPath).returns(statFailure); 33 | statStub.withArgs(projectSettingsPath).returns(statFailure); 34 | }); 35 | 36 | teardown(() => { 37 | statStub.restore(); 38 | }); 39 | 40 | test('hasProjectSettings fails with no projectPath', () => { 41 | let result = ProjectSettings.hasProjectSettings(); 42 | 43 | expect(result).to.be(false); 44 | }); 45 | 46 | test('hasProjectSettings fails with projectPath', () => { 47 | let result = ProjectSettings.hasProjectSettings(); 48 | 49 | expect(result).to.be(false); 50 | }); 51 | }); 52 | suite('when it does exist', () => { 53 | setup(() => { 54 | let statSuccess = Promise.resolve(); 55 | 56 | statStub = sinon.stub(fs, 'stat'); 57 | statStub.withArgs(workspaceSettingsPath).returns(statSuccess); 58 | statStub.withArgs(projectSettingsPath).returns(statSuccess); 59 | 60 | jsonFileStub = sinon.stub(jsonFile, 'open').returns(testSettings); 61 | }); 62 | 63 | teardown(() => { 64 | statStub.restore(); 65 | jsonFileStub.restore(); 66 | }); 67 | 68 | test('hasProjectSettings succeeds with no projectPath', () => { 69 | let result = ProjectSettings.hasProjectSettings(); 70 | 71 | expect(result).to.be(true); 72 | }); 73 | 74 | test('hasProjectSettings succeeds with projectPath', () => { 75 | let result = ProjectSettings.hasProjectSettings(projectSettingsPath); 76 | 77 | expect(result).to.be(true); 78 | }); 79 | 80 | test('getProjectSettings gets settings with no projectPath', () => { 81 | let actualSettings = ProjectSettings.getProjectSettings(); 82 | 83 | expect(actualSettings.id).to.equal(testSettings.id, 'id of settings'); 84 | }); 85 | 86 | test('getProjectSettings gets settings with projectPath', () => { 87 | let actualSettings = ProjectSettings.getProjectSettings(projectPath); 88 | 89 | expect(actualSettings.id).to.equal(testSettings.id, 'id of settings'); 90 | }); 91 | }); 92 | }); 93 | -------------------------------------------------------------------------------- /test/testWorkspace/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "mavensMateDesktop.baseURL": "http://localhost:55555" 4 | } -------------------------------------------------------------------------------- /test/testWorkspace/sample.txt: -------------------------------------------------------------------------------- 1 | This is a sample file for testing purposes; -------------------------------------------------------------------------------- /test/testWorkspace/testApexClass.cls: -------------------------------------------------------------------------------- 1 | @isTest 2 | public class AccountTestFactoryMVN { 3 | public static Integer AccountBuildIndex = 0; 4 | 5 | public static Account insertPhysician(){ 6 | Account physician = buildPhysician(); 7 | 8 | insert physician; 9 | 10 | return physician; 11 | } 12 | 13 | public static Account buildPhysician(){ 14 | Account physician = new Account( 15 | FirstName = 'Physician', 16 | LastName = 'Account #' + AccountBuildIndex, 17 | ); 18 | AccountBuildIndex++; 19 | 20 | return physician; 21 | } 22 | 23 | public static List insertPhysicians(Integer count){ 24 | List physicians = buildPhysicians(count); 25 | 26 | insert physicians; 27 | 28 | return physicians; 29 | } 30 | 31 | public static List buildPhysicians(Integer count){ 32 | List physicians = new List(); 33 | 34 | for(Integer index = 0; index < count; index++){ 35 | physicians.add(buildPhysician()); 36 | } 37 | 38 | return physicians; 39 | } 40 | } -------------------------------------------------------------------------------- /test/vscode/commandRegistrar.test.ts: -------------------------------------------------------------------------------- 1 | import expect = require('expect.js'); 2 | import sinon = require('sinon'); 3 | import Promise = require('bluebird'); 4 | 5 | import vscode = require('vscode'); 6 | import { registerCommands } from '../../src/vscode/commandRegistrar'; 7 | 8 | import commandIndex = require('../../src/mavensmate/commands/index'); 9 | import CleanProject = require('../../src/mavensmate/commands/cleanProject'); 10 | import CompileFile = require('../../src/mavensmate/commands/compileFile'); 11 | import CompileProject = require('../../src/mavensmate/commands/compileProject'); 12 | 13 | let directoryOfCommands = { 14 | 'clean-project': CleanProject, 15 | 'compile-file': CompileFile, 16 | 'compile-project': CompileProject 17 | } 18 | 19 | suite('commandRegistrar', () => { 20 | let commandDirectoryStub: sinon.SinonStub; 21 | let registerCommandStub: sinon.SinonStub; 22 | 23 | let createSpys: sinon.SinonSpy[]; 24 | let invokeStubs: sinon.SinonStub[]; 25 | 26 | setup(() => { 27 | commandDirectoryStub = sinon.stub(commandIndex, 'commandDirectory').returns(directoryOfCommands); 28 | registerCommandStub = sinon.stub(vscode.commands, 'registerCommand'); 29 | 30 | createSpys = []; 31 | invokeStubs = []; 32 | 33 | createSpys.push(sinon.spy(CleanProject, 'create')); 34 | createSpys.push(sinon.spy(CompileFile, 'create')); 35 | createSpys.push(sinon.spy(CompileProject, 'create')); 36 | 37 | invokeStubs.push(sinon.stub(CleanProject.prototype, 'invoke')); 38 | invokeStubs.push(sinon.stub(CompileFile.prototype, 'invoke')); 39 | invokeStubs.push(sinon.stub(CompileProject.prototype, 'invoke')); 40 | }); 41 | 42 | teardown(() => { 43 | 44 | commandDirectoryStub.restore(); 45 | registerCommandStub.restore(); 46 | createSpys.forEach((stub) => { 47 | stub.restore(); 48 | }); 49 | invokeStubs.forEach((stub) => { 50 | stub.restore(); 51 | }); 52 | }); 53 | 54 | test('registerCommands', (testDone) => { 55 | registerCommands(); 56 | 57 | sinon.assert.calledThrice(registerCommandStub); 58 | expect(registerCommandStub.getCall(0).args[0]).to.equal('clean-project'); 59 | expect(registerCommandStub.getCall(1).args[0]).to.equal('compile-file'); 60 | expect(registerCommandStub.getCall(2).args[0]).to.equal('compile-project'); 61 | 62 | let testPath = 'atestpath'; 63 | 64 | let registeredPromises: Promise[] = [ 65 | registerCommandStub.getCall(0).args[1](testPath), 66 | registerCommandStub.getCall(1).args[1](testPath), 67 | registerCommandStub.getCall(2).args[1](testPath) 68 | ]; 69 | 70 | Promise.all(registeredPromises).then(() => { 71 | createSpys.forEach((stub) => { 72 | sinon.assert.calledOnce(stub); 73 | }); 74 | invokeStubs.forEach((stub) => { 75 | sinon.assert.calledWith(stub, testPath); 76 | }); 77 | }).then(testDone); 78 | }); 79 | }); 80 | -------------------------------------------------------------------------------- /test/vscode/mavensMateStatus.test.ts: -------------------------------------------------------------------------------- 1 | import expect = require('expect.js'); 2 | import sinon = require('sinon'); 3 | 4 | import { MavensMateStatus } from '../../src/vscode/mavensMateStatus'; 5 | 6 | import vscode = require('vscode'); 7 | 8 | suite("MavensMateStatus", () => { 9 | let mavensMateStatus; 10 | 11 | suite("updateAppStatus", () => { 12 | let statusBarItem: any; 13 | let createStatusBarItemStub; 14 | 15 | setup(() => { 16 | statusBarItem = {}; 17 | statusBarItem.show = sinon.stub(); 18 | 19 | createStatusBarItemStub = sinon.stub(vscode.window, "createStatusBarItem"); 20 | createStatusBarItemStub.returns(statusBarItem); 21 | 22 | mavensMateStatus = MavensMateStatus.create(); 23 | 24 | expect(createStatusBarItemStub.calledOnce).to.be(true); 25 | expect(statusBarItem.show.calledOnce).to.be(true); 26 | expect(statusBarItem.text).to.equal("MavensMate"); 27 | expect(statusBarItem.command).to.equal("mavensmate.toggleOutput"); 28 | }); 29 | 30 | teardown(() => { 31 | createStatusBarItemStub.restore(); 32 | }); 33 | 34 | suite("when MavensMateApp is Available", () => { 35 | test("it sets the statusBarItem to Check", () => { 36 | mavensMateStatus.showAppIsAvailable(); 37 | expect(statusBarItem.text).to.equal("MavensMate $(check)"); 38 | }); 39 | }); 40 | 41 | suite("when MavensMateApp is not Available", () => { 42 | test("it sets the statusBarItem to alert", () => { 43 | mavensMateStatus.showAppIsUnavailable(); 44 | expect(statusBarItem.text).to.equal("MavensMate $(alert)"); 45 | }); 46 | }); 47 | }); 48 | }); -------------------------------------------------------------------------------- /test/vscode/projectQuickPick.test.ts: -------------------------------------------------------------------------------- 1 | import assert = require('assert'); 2 | import sinon = require('sinon'); 3 | 4 | import projectList = require('../../src/workspace/projectList'); 5 | import { window, QuickPickItem, commands, Uri } from 'vscode'; 6 | 7 | import { showProjectQuickPick, projectQuickPickItem, openProject, showProjectListAndOpen } from '../../src/vscode/projectQuickPick'; 8 | 9 | 10 | suite('project Quick Pick', () => { 11 | suite('show', () => { 12 | let promisedProjects: projectList.projectDirectory[] = [ 13 | { name: 'project1', workspace: 'workspace1', path: 'path1' }, 14 | { name: 'project2', workspace: 'workspace1', path: 'path2' }, 15 | { name: 'project3', workspace: 'workspace2', path: 'path3' } 16 | ]; 17 | let expectedQuickPickItems: projectQuickPickItem[] = [ 18 | { label: 'project1', description: 'workspace1', detail: 'path1', path: 'path1' }, 19 | { label: 'project2', description: 'workspace1', detail: 'path2', path: 'path2' }, 20 | { label: 'project3', description: 'workspace2', detail: 'path3', path: 'path3' } 21 | ]; 22 | let promiseListStub: sinon.SinonStub; 23 | let showQuickPickStub: sinon.SinonStub; 24 | 25 | setup(() => { 26 | promiseListStub = sinon.stub(projectList, 'promiseList').returns(Promise.resolve(promisedProjects)); 27 | showQuickPickStub = sinon.stub(window, 'showQuickPick').returns(Promise.resolve()); 28 | }); 29 | 30 | teardown(() => { 31 | promiseListStub.restore(); 32 | showQuickPickStub.restore(); 33 | }); 34 | 35 | test('it shows expectedQuickPickItems', (testDone) => { 36 | showProjectQuickPick().then(() => { 37 | sinon.assert.calledOnce(showQuickPickStub); 38 | sinon.assert.calledWithExactly(showQuickPickStub, expectedQuickPickItems); 39 | testDone(); 40 | }, console.error); 41 | }); 42 | }); 43 | 44 | suite('open', () => { 45 | let projectItem: projectQuickPickItem = { 46 | label: 'project1', 47 | description: 'workspace1', 48 | detail: 'path1', 49 | path: 'path1' 50 | }; 51 | let validUri = 'a valid uri'; 52 | let uriParseStub: sinon.SinonStub; 53 | let executeCommandStub: sinon.SinonStub; 54 | 55 | setup(() => { 56 | uriParseStub = sinon.stub(Uri, 'parse').returns(validUri); 57 | executeCommandStub = sinon.stub(commands, 'executeCommand').returns(Promise.resolve()); 58 | }); 59 | 60 | teardown(() => { 61 | uriParseStub.restore(); 62 | executeCommandStub.restore(); 63 | }); 64 | 65 | test('it executes openFolder command with projectUri', (testDone) => { 66 | openProject(projectItem).then(() => { 67 | sinon.assert.calledOnce(uriParseStub); 68 | sinon.assert.calledWithExactly(uriParseStub, 'path1'); 69 | sinon.assert.calledOnce(executeCommandStub); 70 | sinon.assert.calledWithExactly(executeCommandStub, 'vscode.openFolder', validUri); 71 | testDone(); 72 | }, (error) => { 73 | assert.fail(null, null, error, null); 74 | testDone(); 75 | }); 76 | }); 77 | }); 78 | 79 | suite('show & open', () => { 80 | let promisedProjects: projectList.projectDirectory[] = [ 81 | { name: 'project1', workspace: 'workspace1', path: 'path1' }, 82 | { name: 'project2', workspace: 'workspace1', path: 'path2' }, 83 | { name: 'project3', workspace: 'workspace2', path: 'path3' } 84 | ]; 85 | let expectedQuickPickItems: projectQuickPickItem[] = [ 86 | { label: 'project1', description: 'workspace1', detail: 'path1', path: 'path1' }, 87 | { label: 'project2', description: 'workspace1', detail: 'path2', path: 'path2' }, 88 | { label: 'project3', description: 'workspace2', detail: 'path3', path: 'path3' } 89 | ]; 90 | let promiseListStub: sinon.SinonStub; 91 | let showQuickPickStub: sinon.SinonStub; 92 | let projectItem: projectQuickPickItem = { 93 | label: 'project5', 94 | description: 'workspace5', 95 | detail: 'path5', 96 | path: 'path5' 97 | }; 98 | let validUri = 'a valid uri 5'; 99 | let uriParseStub: sinon.SinonStub; 100 | let executeCommandStub: sinon.SinonStub; 101 | 102 | setup(() => { 103 | promiseListStub = sinon.stub(projectList, 'promiseList').returns(Promise.resolve(promisedProjects)); 104 | showQuickPickStub = sinon.stub(window, 'showQuickPick').returns(Promise.resolve(projectItem)); 105 | uriParseStub = sinon.stub(Uri, 'parse').returns(validUri); 106 | executeCommandStub = sinon.stub(commands, 'executeCommand').returns(Promise.resolve()); 107 | }); 108 | 109 | teardown(() => { 110 | promiseListStub.restore(); 111 | showQuickPickStub.restore(); 112 | uriParseStub.restore(); 113 | executeCommandStub.restore(); 114 | }); 115 | 116 | test('it executes openFolder command with projectUri', (testDone) => { 117 | showProjectListAndOpen().then(() => { 118 | sinon.assert.calledOnce(uriParseStub); 119 | sinon.assert.calledWithExactly(uriParseStub, 'path5'); 120 | sinon.assert.calledOnce(executeCommandStub); 121 | sinon.assert.calledWithExactly(executeCommandStub, 'vscode.openFolder', validUri); 122 | testDone(); 123 | }, (error) => { 124 | assert.fail(null, null, error, null); 125 | testDone(); 126 | }); 127 | }); 128 | }); 129 | }); 130 | -------------------------------------------------------------------------------- /test/vscode/testExtensionContext.ts: -------------------------------------------------------------------------------- 1 | import { ExtensionContext, Memento } from 'vscode'; 2 | 3 | export class TestExtensionContext implements ExtensionContext { 4 | subscriptions: { dispose(): any }[] = []; 5 | 6 | workspaceState: Memento; 7 | 8 | globalState: Memento; 9 | 10 | extensionPath: string; 11 | 12 | asAbsolutePath(relativePath: string): string{ 13 | return relativePath; 14 | } 15 | 16 | storagePath: string; 17 | } -------------------------------------------------------------------------------- /test/workspace/jsonFile.test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | import * as sinon from 'sinon'; 3 | 4 | import fs = require('fs'); 5 | 6 | import * as file from '../../src/workspace/jsonFile'; 7 | 8 | suite('file', () => { 9 | let openFileSyncStub : sinon.SinonStub; 10 | 11 | suite('when it returns a json file', () => { 12 | let testFileName = 'testfile.txt'; 13 | setup(() => { 14 | openFileSyncStub = sinon.stub(fs, 'readFileSync'); 15 | openFileSyncStub.withArgs(testFileName).returns('{"isValid": true, "myString": "test string"}'); 16 | }); 17 | 18 | test('returns parsed json object', () => { 19 | let returnedObject = file.open(testFileName); 20 | assert.equal(returnedObject.isValid, true); 21 | assert.equal(returnedObject.myString, 'test string'); 22 | }); 23 | 24 | teardown(() => { 25 | openFileSyncStub.restore(); 26 | }); 27 | }); 28 | }); -------------------------------------------------------------------------------- /test/workspace/operatingSystem.test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | import * as sinon from 'sinon'; 3 | 4 | import os = require('os'); 5 | 6 | import * as operatingSystem from '../../src/workspace/operatingSystem'; 7 | 8 | 9 | suite('operatingSystem', () => { 10 | let platformStub : sinon.SinonStub; 11 | suite("when on a mac (darwin)", () => { 12 | setup(() => { 13 | platformStub = sinon.stub(os, 'platform'); 14 | platformStub.returns('darwin'); 15 | }); 16 | 17 | test("isMac is true", () => { 18 | assert.equal(operatingSystem.isMac(), true); 19 | }); 20 | test("isWindows is false", () => { 21 | assert.equal(operatingSystem.isWindows(), false); 22 | }); 23 | test("isLinux is false", () => { 24 | assert.equal(operatingSystem.isLinux(), false); 25 | }); 26 | 27 | teardown(() => { 28 | platformStub.restore(); 29 | }); 30 | }); 31 | 32 | suite("when on a linux machine", () => { 33 | setup(() => { 34 | platformStub = sinon.stub(os, 'platform'); 35 | platformStub.returns('linux'); 36 | }); 37 | 38 | test("isMac is false", () => { 39 | assert.equal(operatingSystem.isMac(), false); 40 | }); 41 | test("isWindows is false", () => { 42 | assert.equal(operatingSystem.isWindows(), false); 43 | }); 44 | test("isLinux is true", () => { 45 | assert.equal(operatingSystem.isLinux(), true); 46 | }); 47 | 48 | teardown(() => { 49 | platformStub.restore(); 50 | }); 51 | }); 52 | 53 | suite("when on a windows machine (win32)", () => { 54 | setup(() => { 55 | platformStub = sinon.stub(os, 'platform'); 56 | platformStub.returns('win32'); 57 | }); 58 | 59 | test("isMac is false", () => { 60 | assert.equal(operatingSystem.isMac(), false); 61 | }); 62 | test("isWindows is true", () => { 63 | assert.equal(operatingSystem.isWindows(), true); 64 | }); 65 | test("isLinux is false", () => { 66 | assert.equal(operatingSystem.isLinux(), false); 67 | }); 68 | 69 | teardown(() => { 70 | platformStub.restore(); 71 | }); 72 | }); 73 | }); -------------------------------------------------------------------------------- /test/workspace/projectList.test.ts: -------------------------------------------------------------------------------- 1 | import assert = require('assert'); 2 | import sinon = require('sinon'); 3 | import * as fs from '../../src/workspace/readDirAsync'; 4 | import path = require('path'); 5 | 6 | import jsonFile = require('../../src/workspace/jsonFile'); 7 | import { ProjectSettings } from '../../src/mavensmate/projectSettings'; 8 | import projectList = require('../../src/workspace/projectList'); 9 | import mavensMateAppConfig = require('../../src/mavensmate/mavensMateAppConfig'); 10 | 11 | let appConfig = { 12 | mm_workspace: ['workspace1', 'workspace2/','missingWorkspace'] 13 | }; 14 | let workspace1Projects = ['.shouldIgnoreMe', 'project1', 'project2']; 15 | let workspace2Projects = ['project1', 'project3','doesNotExist']; 16 | 17 | let testSettings: ProjectSettings = { 18 | id: 'testid1', 19 | projectName: 'project name', 20 | instanceUrl: 'instance' 21 | }; 22 | 23 | let promiseWorkspace1Projects = Promise.resolve(workspace1Projects); 24 | let promiseWorkspace2Projects = Promise.resolve(workspace2Projects); 25 | 26 | suite('projectList', () => { 27 | let getConfigStub: sinon.SinonStub; 28 | let readDirStub: sinon.SinonStub; 29 | let jsonFileStub: sinon.SinonStub; 30 | setup(() => { 31 | getConfigStub = sinon.stub(mavensMateAppConfig, 'getConfig').returns(appConfig); 32 | 33 | readDirStub = sinon.stub(fs,'readdir'); 34 | readDirStub.withArgs('workspace1').returns(promiseWorkspace1Projects); 35 | readDirStub.withArgs('workspace2/').returns(promiseWorkspace2Projects); 36 | readDirStub.withArgs('missingWorkspace').returns(Promise.reject('missingWorkspace is missing as intended')); 37 | 38 | jsonFileStub = sinon.stub(jsonFile, 'open'); 39 | jsonFileStub.withArgs(path.normalize('workspace1/project1/config/.settings')).returns(testSettings); 40 | jsonFileStub.withArgs(path.normalize('workspace1/project2/config/.settings')).returns(testSettings); 41 | jsonFileStub.withArgs(path.normalize('workspace2/project1/config/.settings')).returns(testSettings); 42 | jsonFileStub.withArgs(path.normalize('workspace2/project3/config/.settings')).returns(testSettings); 43 | jsonFileStub.withArgs(path.normalize('workspace2/doesNotExist/config/.settings')).returns(null); 44 | }); 45 | 46 | teardown(() => { 47 | getConfigStub.restore(); 48 | readDirStub.restore(); 49 | jsonFileStub.restore(); 50 | }); 51 | 52 | test('gets the 4 actual projects', (testDone) => { 53 | projectList.promiseList().then((projects) => { 54 | assert.equal(projects.length, 4); 55 | assertIsProject(projects[0], 'project1', path.normalize('workspace1/project1'), 'workspace1'); 56 | assertIsProject(projects[1], 'project2', path.normalize('workspace1/project2'), 'workspace1'); 57 | assertIsProject(projects[2], 'project1', path.normalize('workspace2/project1'), 'workspace2'); 58 | assertIsProject(projects[3], 'project3', path.normalize('workspace2/project3'), 'workspace2'); 59 | }) 60 | .done(testDone, console.error); 61 | }); 62 | }); 63 | 64 | function assertIsProject(project, name, path, workspace){ 65 | assert.equal(project.name, name); 66 | assert.equal(project.path, path); 67 | assert.equal(project.workspace, workspace); 68 | } 69 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "outDir": "out", 6 | "lib": [ "es6" ], 7 | "sourceMap": true, 8 | "rootDir": "." 9 | }, 10 | "include": [ 11 | "node_modules/@types", 12 | "src", 13 | "test" 14 | ] 15 | } 16 | --------------------------------------------------------------------------------