├── CHANGELOG.md ├── CHANGELOG.md.meta ├── CONTRIBUTING.md ├── CONTRIBUTING.md.meta ├── Documentation~ ├── Images │ └── external-tools-tab.png ├── README.md ├── TableOfContents.md ├── index.md └── using-visual-studio-editor.md ├── Editor.meta ├── Editor ├── AppleEventIntegration~ │ ├── AppleEventIntegration.xcodeproj │ │ └── project.pbxproj │ ├── AppleEventIntegration │ │ ├── Info.plist │ │ └── main.mm │ └── howtobuild.txt ├── AssemblyInfo.cs ├── AssemblyInfo.cs.meta ├── AsyncOperation.cs ├── AsyncOperation.cs.meta ├── COMIntegration.meta ├── COMIntegration │ ├── COMIntegration~ │ │ ├── BStrHolder.h │ │ ├── CMakeLists.txt │ │ ├── COMIntegration.cpp │ │ ├── ComPtr.h │ │ ├── dte80a.tlh │ │ └── howtobuild.txt │ ├── Release.meta │ └── Release │ │ ├── COMIntegration.exe │ │ └── COMIntegration.exe.meta ├── Cli.cs ├── Cli.cs.meta ├── Discovery.cs ├── Discovery.cs.meta ├── FileUtility.cs ├── FileUtility.cs.meta ├── Image.cs ├── Image.cs.meta ├── KnownAssemblies.cs ├── KnownAssemblies.cs.meta ├── Messaging.meta ├── Messaging │ ├── Deserializer.cs │ ├── Deserializer.cs.meta │ ├── ExceptionEventArgs.cs │ ├── ExceptionEventArgs.cs.meta │ ├── Message.cs │ ├── Message.cs.meta │ ├── MessageEventArgs.cs │ ├── MessageEventArgs.cs.meta │ ├── MessageType.cs │ ├── MessageType.cs.meta │ ├── Messenger.cs │ ├── Messenger.cs.meta │ ├── Serializer.cs │ ├── Serializer.cs.meta │ ├── TcpClient.cs │ ├── TcpClient.cs.meta │ ├── TcpListener.cs │ ├── TcpListener.cs.meta │ ├── UdpSocket.cs │ └── UdpSocket.cs.meta ├── Plugins.meta ├── Plugins │ ├── AppleEventIntegration.bundle.meta │ └── AppleEventIntegration.bundle │ │ ├── Contents.meta │ │ └── Contents │ │ ├── Info.plist │ │ ├── Info.plist.meta │ │ ├── MacOS.meta │ │ ├── MacOS │ │ ├── AppleEventIntegration │ │ └── AppleEventIntegration.meta │ │ ├── _CodeSignature.meta │ │ └── _CodeSignature │ │ ├── CodeResources │ │ └── CodeResources.meta ├── ProcessRunner.cs ├── ProcessRunner.cs.meta ├── ProjectGeneration.meta ├── ProjectGeneration │ ├── AssemblyNameProvider.cs │ ├── AssemblyNameProvider.cs.meta │ ├── FileIOProvider.cs │ ├── FileIOProvider.cs.meta │ ├── GUIDProvider.cs │ ├── GUIDProvider.cs.meta │ ├── LegacyStyleProjectGeneration.cs │ ├── LegacyStyleProjectGeneration.cs.meta │ ├── ProjectGeneration.cs │ ├── ProjectGeneration.cs.meta │ ├── ProjectGenerationFlag.cs │ ├── ProjectGenerationFlag.cs.meta │ ├── ProjectProperties.cs │ ├── ProjectProperties.cs.meta │ ├── SdkStyleProjectGeneration.cs │ └── SdkStyleProjectGeneration.cs.meta ├── SimpleJSON.cs ├── SimpleJSON.cs.meta ├── Solution.cs ├── Solution.cs.meta ├── SolutionParser.cs ├── SolutionParser.cs.meta ├── SolutionProjectEntry.cs ├── SolutionProjectEntry.cs.meta ├── SolutionProperties.cs ├── SolutionProperties.cs.meta ├── Symbols.cs ├── Symbols.cs.meta ├── Testing.meta ├── Testing │ ├── TestAdaptor.cs │ ├── TestAdaptor.cs.meta │ ├── TestResultAdaptor.cs │ ├── TestResultAdaptor.cs.meta │ ├── TestRunnerApiListener.cs │ ├── TestRunnerApiListener.cs.meta │ ├── TestRunnerCallbacks.cs │ ├── TestRunnerCallbacks.cs.meta │ ├── TestStatusAdaptor.cs │ └── TestStatusAdaptor.cs.meta ├── UnityInstallation.cs ├── UnityInstallation.cs.meta ├── UsageUtility.cs ├── UsageUtility.cs.meta ├── VSWhere.meta ├── VSWhere │ ├── vswhere.exe │ └── vswhere.exe.meta ├── VersionPair.cs ├── VersionPair.cs.meta ├── VisualStudioCodiumInstallation.cs ├── VisualStudioCodiumInstallation.cs.meta ├── VisualStudioCursorInstallation.cs ├── VisualStudioCursorInstallation.cs.meta ├── VisualStudioEditor.cs ├── VisualStudioEditor.cs.meta ├── VisualStudioInstallation.cs ├── VisualStudioInstallation.cs.meta ├── VisualStudioIntegration.cs ├── VisualStudioIntegration.cs.meta ├── com.unity.ide.visualstudio.asmdef └── com.unity.ide.visualstudio.asmdef.meta ├── LICENSE.md ├── LICENSE.md.meta ├── README.md ├── README.md.meta ├── ThirdPartyNotices.md ├── ThirdPartyNotices.md.meta ├── ValidationConfig.json ├── ValidationConfig.json.meta ├── ValidationExceptions.json ├── ValidationExceptions.json.meta ├── package.json ├── package.json.meta └── rules ├── etrules.mdc └── etrules.mdc.meta /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Code Editor Package for Visual Studio 2 | 3 | ## [2.0.22] - 2023-10-03 4 | 5 | Integration: 6 | 7 | - Add support for `XDG_DATA_DIRS` and `.desktop` files on Linux for `VS Code` discovery. 8 | - Use compile-time platform-specifics instead of using runtime conditions. 9 | 10 | Project generation: 11 | 12 | - Suppress `USG0001` warnings. 13 | - Mark referenced assemblies as private (to not copy extra files to output directory when building). 14 | - Add Unity capability to SDK-Style projects. 15 | - Prevent circular dependency errors with SDK-Style projects. 16 | 17 | 18 | ## [2.0.21] - 2023-09-05 19 | 20 | Integration: 21 | 22 | - Only disable the legacy `com.unity.ide.vscode` package going forward. 23 | - Fix json parsing issues with specific non-UTF code pages. 24 | 25 | Project generation: 26 | 27 | - Target `netstandard2.1` instead of `netstandard2.0`. 28 | - Set `defaultSolution` in `settings.json`. 29 | - Remove `files.exclude` entries for root `csproj` and `sln` files in `settings.json` when needed. 30 | - Add `vstuc` launch configuration to `launch.json` when needed. 31 | - Add `visualstudiotoolsforunity.vstuc` entry to `extensions.json` when needed. 32 | - You can prevent the package from patching those configuration files by creating a `.vscode/.vstupatchdisable` file. 33 | 34 | ## [2.0.20] - 2023-06-27 35 | 36 | Integration: 37 | 38 | - Internal API refactoring. 39 | - Add support for Visual Studio Code. 40 | 41 | Project generation: 42 | 43 | - Add support for Sdk Style project generation. 44 | - Fix an issue related to missing properties with 2021.3. 45 | 46 | ## [2.0.18] - 2023-03-17 47 | 48 | Integration: 49 | 50 | - Performance improvements with `EditorApplication.update` callbacks. 51 | 52 | Project generation: 53 | 54 | - Add extra compiler options for analyzers and source generators. 55 | 56 | ## [2.0.17] - 2022-12-06 57 | 58 | Integration: 59 | 60 | - Fix rare deadlocks while discovering or launching Visual Studio on Windows. 61 | - Improve launching Visual Studio on macOs. 62 | 63 | Project generation: 64 | 65 | - Include analyzers from response files. 66 | - Update supported C# versions. 67 | - Performance improvements. 68 | 69 | ## [2.0.16] - 2022-06-08 70 | 71 | Integration: 72 | 73 | - Prevent ADB Refresh while being in safe-mode with a URP project 74 | - Fixed an issue keeping the progress bar visible even after opening a script with Visual Studio. 75 | 76 | ## [2.0.15] - 2022-03-21 77 | 78 | Integration: 79 | 80 | - Improved project generation performance. 81 | - Added support for keeping file/folder structure when working with external packages. 82 | - Fixed project generation not being refreshed when selecting Visual Studio as the preferred external editor. 83 | 84 | ## [2.0.14] - 2022-01-14 85 | 86 | Integration: 87 | 88 | - Remove package version checking. 89 | 90 | ## [2.0.13] - 2022-01-12 91 | 92 | Integration: 93 | 94 | - Fixed wrong path to analyzers in generated projects when using external packages. 95 | - Fixed selective project generation not creating Analyzer/LangVersion nodes. 96 | - Fixed asmdef references with Player projects. 97 | 98 | Documentation: 99 | 100 | - Added new documentation including ToC, overview, how to use and images. 101 | 102 | ## [2.0.12] - 2021-10-20 103 | 104 | Integration: 105 | 106 | - Do not block asset opening when only a VS instance without a loaded solution is found. 107 | - Only check package version once per Unity session. 108 | - Improved support for Visual Studio For Mac 2022. 109 | 110 | ## [2.0.11] - 2021-07-01 111 | 112 | Integration: 113 | 114 | - Added support for Visual Studio and Visual Studio For Mac 2022. 115 | - Fixed an issue when the package was enabled for background processes. 116 | 117 | Project generation: 118 | 119 | - Use absolute paths for Analyzers and rulesets. 120 | 121 | ## [2.0.10] - 2021-06-10 122 | 123 | Project generation: 124 | 125 | - Improved project generation performance when a file is moved, deleted or modified. 126 | 127 | Integration: 128 | 129 | - Improved Inner-loop performance by avoiding to call the package manager when looking up `vswhere` utility. 130 | - Fixed a network issue preventing the communication between Visual Studio and Unity on Windows. 131 | 132 | ## [2.0.9] - 2021-05-04 133 | 134 | Project generation: 135 | 136 | - Added support for CLI. 137 | 138 | Integration: 139 | 140 | - Improved performance when discovering Visual Studio installations. 141 | - Warn when legacy assemblies are present in the project. 142 | - Warn when the package version is not up-to-date. 143 | 144 | ## [2.0.8] - 2021-04-09 145 | 146 | Project generation: 147 | 148 | - Improved generation performance (especially with DOTS enabled projects). 149 | - Improved stability. 150 | - Updated Analyzers lookup strategy. 151 | - Fixed .vsconfig file not generated when using "regenerate all". 152 | 153 | Integration: 154 | 155 | - Improved automation plugins. 156 | 157 | Documentation: 158 | 159 | - Open sourced automation plugins. 160 | 161 | ## [2.0.7] - 2021-02-02 162 | 163 | Integration: 164 | 165 | - Remove `com.unity.nuget.newtonsoft-json` dependency in favor of the built-in JsonUtility for the VS Test Runner. 166 | 167 | ## [2.0.6] - 2021-01-20 168 | 169 | Project generation: 170 | 171 | - Improved language version detection. 172 | 173 | Integration: 174 | 175 | - Added support for the VS Test Runner. 176 | - Added initial support for displaying asset usage. 177 | - Fixed remaining issues with special characters in file/path. 178 | 179 | ## [2.0.5] - 2020-10-30 180 | 181 | Integration: 182 | 183 | - Disable legacy pdb symbol checking for Unity packages. 184 | 185 | ## [2.0.4] - 2020-10-15 186 | 187 | Project generation: 188 | 189 | - Added support for embedded Roslyn analyzer DLLs and ruleset files. 190 | - Warn the user when the opened script is not part of the generation scope. 191 | - Warn the user when the selected Visual Studio installation is not found. 192 | - Generate a .vsconfig file to ensure Visual Studio installation is compatible. 193 | 194 | Integration: 195 | 196 | - Fix automation issues on MacOS, where a new Visual Studio instance is opened every time. 197 | 198 | ## [2.0.3] - 2020-09-09 199 | 200 | Project generation: 201 | 202 | - Added C#8 language support. 203 | - Added `UnityProjectGeneratorVersion` property. 204 | - Local and Embedded packages are now selected by default for generation. 205 | - Added support for asmdef root namespace. 206 | 207 | Integration: 208 | 209 | - When the user disabled auto-refresh in Unity, do not try to force refresh the Asset database. 210 | - Fix Visual Studio detection issues with languages using special characters. 211 | 212 | 213 | ## [2.0.2] - 2020-05-27 214 | 215 | - Added support for solution folders. 216 | - Only bind the messenger when the VS editor is selected. 217 | - Warn when unable to create the messenger. 218 | - Fixed an initialization issue triggering legacy code generation. 219 | - Allow package source in assembly to be generated when referenced from asmref. 220 | 221 | 222 | ## [2.0.1] - 2020-03-19 223 | 224 | - When Visual Studio installation is compatible with C# 8.0, setup the language version to not prompt the user with unsupported constructs. (So far Unity only supports C# 7.3). 225 | - Use Unity's `TypeCache` to improve project generation speed. 226 | - Properly check for a managed assembly before displaying a warning regarding legacy PDB usage. 227 | - Add support for selective project generation (embedded, local, registry, git, builtin, player). 228 | 229 | ## [2.0.0] - 2019-11-06 230 | 231 | - Improved Visual Studio and Visual Studio for Mac automatic discovery. 232 | - Added support for the VSTU messaging system (start/stop features from Visual Studio). 233 | - Added support for solution roundtrip (preserves references to external projects and solution properties). 234 | - Added support for VSTU Analyzers (requires Visual Studio 2019 16.3, Visual Studio for Mac 8.3). 235 | - Added a warning when using legacy pdb symbol files. 236 | - Fixed issues while Opening Visual Studio on Windows. 237 | - Fixed issues while Opening Visual Studio on Mac. 238 | 239 | ## [1.1.1] - 2019-05-29 240 | 241 | - Fix Bridge assembly loading with non VS2017 editors. 242 | 243 | ## [1.1.0] - 2019-05-27 244 | 245 | - Move internal extension handling to package. 246 | 247 | ## [1.0.11] - 2019-05-21 248 | 249 | - Fix detection of visual studio for mac installation. 250 | 251 | ## [1.0.10] - 2019-05-04 252 | 253 | - Fix ignored comintegration executable. 254 | 255 | ## [1.0.9] - 2019-03-05 256 | 257 | - Updated MonoDevelop support, to pass correct arguments, and not import VSTU plugin. 258 | - Use release build of COMIntegration for Visual Studio. 259 | 260 | ## [1.0.7] - 2019-04-30 261 | 262 | - Ensure asset database is refreshed when generating csproj and solution files. 263 | 264 | ## [1.0.6] - 2019-04-27 265 | 266 | - Add support for generating all csproj files. 267 | 268 | ## [1.0.5] - 2019-04-18 269 | 270 | - Fix relative package paths. 271 | - Fix opening editor on mac. 272 | 273 | ## [1.0.4] - 2019-04-12 274 | 275 | - Fixing null reference issue for callbacks to `AssetPostProcessor`. 276 | - Ensure `Path.GetFullPath` does not get an empty string. 277 | 278 | ## [1.0.3] - 2019-01-01 279 | 280 | ### This is the first release of *Unity Package visualstudio_editor*. 281 | 282 | - Using the newly created api to integrate Visual Studio with Unity. 283 | -------------------------------------------------------------------------------- /CHANGELOG.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e3cceb39029f44e4d8c39cb2a333bed2 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## All contributions are subject to the [Unity Contribution Agreement(UCA)](https://unity3d.com/legal/licenses/Unity_Contribution_Agreement) and [Microsoft Contributor License Agreement (CLA)](https://cla.opensource.microsoft.com/) 4 | By making a pull request, you are confirming agreement to the terms and conditions of the UCA and CLA, including that your contributions are your original creation and that you have complete right and authority to make your contributions. 5 | 6 | ## Once you have a change ready following these ground rules. Simply make a pull request -------------------------------------------------------------------------------- /CONTRIBUTING.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: eeba022f52d13c04e992f4d8b0624352 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Documentation~/Images/external-tools-tab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liyingsong99/com.unity.ide.cursor-et/2ff42a4c32bdbe24a12d24762bf43da7cdccc76a/Documentation~/Images/external-tools-tab.png -------------------------------------------------------------------------------- /Documentation~/README.md: -------------------------------------------------------------------------------- 1 | # Code Editor Package for Visual Studio 2 | 3 | This package is not intended to be modified by users. 4 | Nor does it provide any api intended to be included in user projects. -------------------------------------------------------------------------------- /Documentation~/TableOfContents.md: -------------------------------------------------------------------------------- 1 | * [About Visual Studio Editor](index.md) 2 | * [Using the Visual Studio Editor package](using-visual-studio-editor.md) -------------------------------------------------------------------------------- /Documentation~/index.md: -------------------------------------------------------------------------------- 1 | # Code Editor Package for Visual Studio 2 | 3 | ## About Visual Studio Editor 4 | 5 | The Visual Studio Editor package provides the Unity Editor with support for Unity-specific features from the [Visual Studio Tools for Unity](https://docs.microsoft.com/en-us/visualstudio/gamedev/unity/get-started/visual-studio-tools-for-unity) extension in [Visual Studio](https://visualstudio.microsoft.com/) and [Visual Studio for Mac](https://visualstudio.microsoft.com/vs/mac/). These include IntelliSense auto-complete suggestions, C# editing, and debugging. 6 | 7 | ## Installation 8 | 9 | This package is a built-in package and installed by default. 10 | 11 | **Note**: If you’re using a version of the Unity Editor before 2019.4, you’ll need to install this package through the package manager. 12 | 13 | ## Requirements 14 | 15 | This version of the Visual Studio Editor package is compatible with the following versions of the Unity Editor: 16 | 17 | * 2019.4 and later 18 | 19 | To use this package, you must have the following third-party products installed: 20 | 21 | * **On Windows**: Visual Studio 2019 version 16.9 or newer with Visual Studio Tools for Unity 4.0.9 or newer. 22 | * **On macOS**: Visual Studio for Mac 2019 version 8.9 or newer with Visual Studio Tools for Unity 2.0.9 or newer. 23 | 24 | For more information about using Visual Studio with Unity, see [Microsoft’s Visual Studio Tools for Unity documentation](https://docs.microsoft.com/en-us/visualstudio/gamedev/unity/get-started/visual-studio-tools-for-unity). 25 | 26 | ## Submitting issues 27 | 28 | This package is maintained by Microsoft and Unity. Submit issues directly from Visual Studio and Visual Studio for Mac from the **Help** > **Submit Feedback** > **Report a Problem** menu. Unity will make this package accessible to the public on GitHub in the future. -------------------------------------------------------------------------------- /Documentation~/using-visual-studio-editor.md: -------------------------------------------------------------------------------- 1 | # Using the Visual Studio Editor package 2 | 3 | To use the package, go to **Edit** > **Preferences** > **External Tools** > **External Script Editor** and select the version of **Visual Studio** you have installed. When you select this option, the window reloads and displays settings that control production of .csproj files. 4 | 5 | ![External Tools tab in the Preferences window](Images/external-tools-tab.png) 6 | 7 | ## Generate .csproj files 8 | 9 | Each setting in the table below enables or disables the production of .csproj files for a different type of package.When you click **Regenerate project files**, Unity updates the existing .csproj files and creates the necessary new ones based on the settings you choose. 10 | 11 | 12 | These settings control whether to generate .csproj files for any installed packages. For more information on how to install packages, see [Adding and removing packages](https://docs.unity3d.com/Manual/upm-ui-actions.html). 13 | 14 | | **Property** | **Description** | 15 | |---|---| 16 | | **Embedded packages** | Any package that appears under your project’s Packages folder is an embedded package. An embedded package is not necessarily built-in; you can create your own packages and embed them inside your project. This setting is enabled by default.

For more information on embedded packages, see [Embedded dependencies](https://docs.unity3d.com/Manual/upm-embed.html). | 17 | | **Local packages** | Any package that you install from a local repository stored on your machine, but from outside of your Unity project. This setting is enabled by default. | 18 | | **Registry packages** | Any package that you install from either the official Unity registry or a custom registry. Packages in the Unity registry are available to install directly from the Package Manager. For more information about the Unity package registry, see The Package Registry section of the [Unity Package Manager documentation](https://docs.unity3d.com/Packages/com.unity.package-manager-ui@1.8/manual/index.html#PackManRegistry).

For information on how to create and use custom registries in addition to the Unity registry, see [Scoped package registries](https://docs.unity3d.com/Manual/upm-scoped.html). | 19 | | **Git packages** | Any package you install directly from a Git repository using a URL. | 20 | | **Built-in packages** | Any package that is already installed as part of the default Unity installation. | 21 | | **Tarball packages** | Any package you install from a GZip tarball archive on the local machine, outside of your Unity project. | 22 | | **Unknown packages** | Any package which Unity cannot determine an origin for. This could be because the package doesn’t list its origin, or that Unity doesn’t recognize the origin listed. | 23 | | **Player projects** | For each player project, generate an additional .csproj file named ‘originalProjectName.Player.csproj’. This allows different project types to have their code included in Visual Studio’s systems, such as assembly definitions or testing suites. | -------------------------------------------------------------------------------- /Editor.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: bf5103b4b65105d449e08afc5b0313be 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Editor/AppleEventIntegration~/AppleEventIntegration.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | E08E02FF236392D000A4B1BE /* main.mm in Sources */ = {isa = PBXBuildFile; fileRef = E08E02FE236392D000A4B1BE /* main.mm */; }; 11 | E08E03022363933B00A4B1BE /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E08E03012363933B00A4B1BE /* AppKit.framework */; }; 12 | /* End PBXBuildFile section */ 13 | 14 | /* Begin PBXFileReference section */ 15 | E08E02F5236392A300A4B1BE /* AppleEventIntegration.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AppleEventIntegration.bundle; sourceTree = BUILT_PRODUCTS_DIR; }; 16 | E08E02F8236392A300A4B1BE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 17 | E08E02FE236392D000A4B1BE /* main.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = main.mm; sourceTree = ""; }; 18 | E08E03012363933B00A4B1BE /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; 19 | /* End PBXFileReference section */ 20 | 21 | /* Begin PBXFrameworksBuildPhase section */ 22 | E08E02F2236392A300A4B1BE /* Frameworks */ = { 23 | isa = PBXFrameworksBuildPhase; 24 | buildActionMask = 2147483647; 25 | files = ( 26 | E08E03022363933B00A4B1BE /* AppKit.framework in Frameworks */, 27 | ); 28 | runOnlyForDeploymentPostprocessing = 0; 29 | }; 30 | /* End PBXFrameworksBuildPhase section */ 31 | 32 | /* Begin PBXGroup section */ 33 | E08E02EC236392A300A4B1BE = { 34 | isa = PBXGroup; 35 | children = ( 36 | E08E02F7236392A300A4B1BE /* AppleEventIntegration */, 37 | E08E02F6236392A300A4B1BE /* Products */, 38 | E08E03002363933B00A4B1BE /* Frameworks */, 39 | ); 40 | sourceTree = ""; 41 | }; 42 | E08E02F6236392A300A4B1BE /* Products */ = { 43 | isa = PBXGroup; 44 | children = ( 45 | E08E02F5236392A300A4B1BE /* AppleEventIntegration.bundle */, 46 | ); 47 | name = Products; 48 | sourceTree = ""; 49 | }; 50 | E08E02F7236392A300A4B1BE /* AppleEventIntegration */ = { 51 | isa = PBXGroup; 52 | children = ( 53 | E08E02F8236392A300A4B1BE /* Info.plist */, 54 | E08E02FE236392D000A4B1BE /* main.mm */, 55 | ); 56 | path = AppleEventIntegration; 57 | sourceTree = ""; 58 | }; 59 | E08E03002363933B00A4B1BE /* Frameworks */ = { 60 | isa = PBXGroup; 61 | children = ( 62 | E08E03012363933B00A4B1BE /* AppKit.framework */, 63 | ); 64 | name = Frameworks; 65 | sourceTree = ""; 66 | }; 67 | /* End PBXGroup section */ 68 | 69 | /* Begin PBXNativeTarget section */ 70 | E08E02F4236392A300A4B1BE /* AppleEventIntegration */ = { 71 | isa = PBXNativeTarget; 72 | buildConfigurationList = E08E02FB236392A300A4B1BE /* Build configuration list for PBXNativeTarget "AppleEventIntegration" */; 73 | buildPhases = ( 74 | E08E02F1236392A300A4B1BE /* Sources */, 75 | E08E02F2236392A300A4B1BE /* Frameworks */, 76 | E08E02F3236392A300A4B1BE /* Resources */, 77 | ); 78 | buildRules = ( 79 | ); 80 | dependencies = ( 81 | ); 82 | name = AppleEventIntegration; 83 | productName = AppleEventIntegration; 84 | productReference = E08E02F5236392A300A4B1BE /* AppleEventIntegration.bundle */; 85 | productType = "com.apple.product-type.bundle"; 86 | }; 87 | /* End PBXNativeTarget section */ 88 | 89 | /* Begin PBXProject section */ 90 | E08E02ED236392A300A4B1BE /* Project object */ = { 91 | isa = PBXProject; 92 | attributes = { 93 | LastUpgradeCheck = 1200; 94 | ORGANIZATIONNAME = Unity; 95 | TargetAttributes = { 96 | E08E02F4236392A300A4B1BE = { 97 | CreatedOnToolsVersion = 11.1; 98 | }; 99 | }; 100 | }; 101 | buildConfigurationList = E08E02F0236392A300A4B1BE /* Build configuration list for PBXProject "AppleEventIntegration" */; 102 | compatibilityVersion = "Xcode 9.3"; 103 | developmentRegion = en; 104 | hasScannedForEncodings = 0; 105 | knownRegions = ( 106 | en, 107 | Base, 108 | ); 109 | mainGroup = E08E02EC236392A300A4B1BE; 110 | productRefGroup = E08E02F6236392A300A4B1BE /* Products */; 111 | projectDirPath = ""; 112 | projectRoot = ""; 113 | targets = ( 114 | E08E02F4236392A300A4B1BE /* AppleEventIntegration */, 115 | ); 116 | }; 117 | /* End PBXProject section */ 118 | 119 | /* Begin PBXResourcesBuildPhase section */ 120 | E08E02F3236392A300A4B1BE /* Resources */ = { 121 | isa = PBXResourcesBuildPhase; 122 | buildActionMask = 2147483647; 123 | files = ( 124 | ); 125 | runOnlyForDeploymentPostprocessing = 0; 126 | }; 127 | /* End PBXResourcesBuildPhase section */ 128 | 129 | /* Begin PBXSourcesBuildPhase section */ 130 | E08E02F1236392A300A4B1BE /* Sources */ = { 131 | isa = PBXSourcesBuildPhase; 132 | buildActionMask = 2147483647; 133 | files = ( 134 | E08E02FF236392D000A4B1BE /* main.mm in Sources */, 135 | ); 136 | runOnlyForDeploymentPostprocessing = 0; 137 | }; 138 | /* End PBXSourcesBuildPhase section */ 139 | 140 | /* Begin XCBuildConfiguration section */ 141 | E08E02F9236392A300A4B1BE /* Debug */ = { 142 | isa = XCBuildConfiguration; 143 | buildSettings = { 144 | ALWAYS_SEARCH_USER_PATHS = NO; 145 | CLANG_ANALYZER_NONNULL = YES; 146 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 147 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 148 | CLANG_CXX_LIBRARY = "libc++"; 149 | CLANG_ENABLE_MODULES = YES; 150 | CLANG_ENABLE_OBJC_ARC = YES; 151 | CLANG_ENABLE_OBJC_WEAK = YES; 152 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 153 | CLANG_WARN_BOOL_CONVERSION = YES; 154 | CLANG_WARN_COMMA = YES; 155 | CLANG_WARN_CONSTANT_CONVERSION = YES; 156 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 157 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 158 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 159 | CLANG_WARN_EMPTY_BODY = YES; 160 | CLANG_WARN_ENUM_CONVERSION = YES; 161 | CLANG_WARN_INFINITE_RECURSION = YES; 162 | CLANG_WARN_INT_CONVERSION = YES; 163 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 164 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 165 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 166 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 167 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 168 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 169 | CLANG_WARN_STRICT_PROTOTYPES = YES; 170 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 171 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 172 | CLANG_WARN_UNREACHABLE_CODE = YES; 173 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 174 | COPY_PHASE_STRIP = NO; 175 | DEBUG_INFORMATION_FORMAT = dwarf; 176 | ENABLE_STRICT_OBJC_MSGSEND = YES; 177 | ENABLE_TESTABILITY = YES; 178 | GCC_C_LANGUAGE_STANDARD = gnu11; 179 | GCC_DYNAMIC_NO_PIC = NO; 180 | GCC_NO_COMMON_BLOCKS = YES; 181 | GCC_OPTIMIZATION_LEVEL = 0; 182 | GCC_PREPROCESSOR_DEFINITIONS = ( 183 | "DEBUG=1", 184 | "$(inherited)", 185 | ); 186 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 187 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 188 | GCC_WARN_UNDECLARED_SELECTOR = YES; 189 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 190 | GCC_WARN_UNUSED_FUNCTION = YES; 191 | GCC_WARN_UNUSED_VARIABLE = YES; 192 | MACOSX_DEPLOYMENT_TARGET = 10.13; 193 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 194 | MTL_FAST_MATH = YES; 195 | ONLY_ACTIVE_ARCH = YES; 196 | SDKROOT = macosx; 197 | }; 198 | name = Debug; 199 | }; 200 | E08E02FA236392A300A4B1BE /* Release */ = { 201 | isa = XCBuildConfiguration; 202 | buildSettings = { 203 | ALWAYS_SEARCH_USER_PATHS = NO; 204 | CLANG_ANALYZER_NONNULL = YES; 205 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 206 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 207 | CLANG_CXX_LIBRARY = "libc++"; 208 | CLANG_ENABLE_MODULES = YES; 209 | CLANG_ENABLE_OBJC_ARC = YES; 210 | CLANG_ENABLE_OBJC_WEAK = YES; 211 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 212 | CLANG_WARN_BOOL_CONVERSION = YES; 213 | CLANG_WARN_COMMA = YES; 214 | CLANG_WARN_CONSTANT_CONVERSION = YES; 215 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 216 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 217 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 218 | CLANG_WARN_EMPTY_BODY = YES; 219 | CLANG_WARN_ENUM_CONVERSION = YES; 220 | CLANG_WARN_INFINITE_RECURSION = YES; 221 | CLANG_WARN_INT_CONVERSION = YES; 222 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 223 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 224 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 225 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 226 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 227 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 228 | CLANG_WARN_STRICT_PROTOTYPES = YES; 229 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 230 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 231 | CLANG_WARN_UNREACHABLE_CODE = YES; 232 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 233 | COPY_PHASE_STRIP = NO; 234 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 235 | ENABLE_NS_ASSERTIONS = NO; 236 | ENABLE_STRICT_OBJC_MSGSEND = YES; 237 | GCC_C_LANGUAGE_STANDARD = gnu11; 238 | GCC_NO_COMMON_BLOCKS = YES; 239 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 240 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 241 | GCC_WARN_UNDECLARED_SELECTOR = YES; 242 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 243 | GCC_WARN_UNUSED_FUNCTION = YES; 244 | GCC_WARN_UNUSED_VARIABLE = YES; 245 | MACOSX_DEPLOYMENT_TARGET = 10.13; 246 | MTL_ENABLE_DEBUG_INFO = NO; 247 | MTL_FAST_MATH = YES; 248 | SDKROOT = macosx; 249 | }; 250 | name = Release; 251 | }; 252 | E08E02FC236392A300A4B1BE /* Debug */ = { 253 | isa = XCBuildConfiguration; 254 | buildSettings = { 255 | CODE_SIGN_STYLE = Automatic; 256 | COMBINE_HIDPI_IMAGES = YES; 257 | DEVELOPMENT_TEAM = ""; 258 | INFOPLIST_FILE = AppleEventIntegration/Info.plist; 259 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles"; 260 | MACOSX_DEPLOYMENT_TARGET = 10.13; 261 | PRODUCT_BUNDLE_IDENTIFIER = com.unity.visualstudio.AppleEventIntegration; 262 | PRODUCT_NAME = "$(TARGET_NAME)"; 263 | SKIP_INSTALL = YES; 264 | WRAPPER_EXTENSION = bundle; 265 | }; 266 | name = Debug; 267 | }; 268 | E08E02FD236392A300A4B1BE /* Release */ = { 269 | isa = XCBuildConfiguration; 270 | buildSettings = { 271 | CODE_SIGN_STYLE = Automatic; 272 | COMBINE_HIDPI_IMAGES = YES; 273 | DEVELOPMENT_TEAM = ""; 274 | INFOPLIST_FILE = AppleEventIntegration/Info.plist; 275 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles"; 276 | MACOSX_DEPLOYMENT_TARGET = 10.13; 277 | PRODUCT_BUNDLE_IDENTIFIER = com.unity.visualstudio.AppleEventIntegration; 278 | PRODUCT_NAME = "$(TARGET_NAME)"; 279 | SKIP_INSTALL = YES; 280 | WRAPPER_EXTENSION = bundle; 281 | }; 282 | name = Release; 283 | }; 284 | /* End XCBuildConfiguration section */ 285 | 286 | /* Begin XCConfigurationList section */ 287 | E08E02F0236392A300A4B1BE /* Build configuration list for PBXProject "AppleEventIntegration" */ = { 288 | isa = XCConfigurationList; 289 | buildConfigurations = ( 290 | E08E02F9236392A300A4B1BE /* Debug */, 291 | E08E02FA236392A300A4B1BE /* Release */, 292 | ); 293 | defaultConfigurationIsVisible = 0; 294 | defaultConfigurationName = Release; 295 | }; 296 | E08E02FB236392A300A4B1BE /* Build configuration list for PBXNativeTarget "AppleEventIntegration" */ = { 297 | isa = XCConfigurationList; 298 | buildConfigurations = ( 299 | E08E02FC236392A300A4B1BE /* Debug */, 300 | E08E02FD236392A300A4B1BE /* Release */, 301 | ); 302 | defaultConfigurationIsVisible = 0; 303 | defaultConfigurationName = Release; 304 | }; 305 | /* End XCConfigurationList section */ 306 | }; 307 | rootObject = E08E02ED236392A300A4B1BE /* Project object */; 308 | } 309 | -------------------------------------------------------------------------------- /Editor/AppleEventIntegration~/AppleEventIntegration/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | NSHumanReadableCopyright 22 | Copyright © 2019 Unity. All rights reserved. 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Editor/AppleEventIntegration~/AppleEventIntegration/main.mm: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Unity Technologies. 3 | * Copyright (c) Microsoft Corporation. All rights reserved. 4 | * Licensed under the MIT License. See License.txt in the project root for license information. 5 | *--------------------------------------------------------------------------------------------*/ 6 | 7 | #import 8 | #import 9 | 10 | // 'FSnd' FourCC 11 | #define keyFileSender 1179872868 12 | 13 | // 16 bit aligned legacy struct - this should total 20 bytes 14 | typedef struct _SelectionRange 15 | { 16 | int16_t unused1; // 0 (not used) 17 | int16_t lineNum; // line to select (<0 to specify range) 18 | int32_t startRange; // start of selection range (if line < 0) 19 | int32_t endRange; // end of selection range (if line < 0) 20 | int32_t unused2; // 0 (not used) 21 | int32_t theDate; // modification date/time 22 | } __attribute__((packed)) SelectionRange; 23 | 24 | static NSString* MakeNSString(const char* str) 25 | { 26 | if (!str) 27 | return NULL; 28 | 29 | NSString* ret = [NSString stringWithUTF8String: str]; 30 | return ret; 31 | } 32 | 33 | static UInt32 GetCreatorOfThisApp() 34 | { 35 | static UInt32 creator = 0; 36 | if (creator == 0) 37 | { 38 | UInt32 type; 39 | CFBundleGetPackageInfo(CFBundleGetMainBundle(), &type, &creator); 40 | } 41 | return creator; 42 | } 43 | 44 | static BOOL OpenFileAtLineWithAppleEvent(NSRunningApplication *runningApp, NSString* path, int line) 45 | { 46 | if (!runningApp) 47 | return NO; 48 | 49 | NSURL *pathUrl = [NSURL fileURLWithPath: path]; 50 | 51 | NSAppleEventDescriptor* targetDescriptor = [NSAppleEventDescriptor 52 | descriptorWithProcessIdentifier: runningApp.processIdentifier]; 53 | 54 | NSAppleEventDescriptor* appleEvent = [NSAppleEventDescriptor 55 | appleEventWithEventClass: kCoreEventClass 56 | eventID: kAEOpenDocuments 57 | targetDescriptor: targetDescriptor 58 | returnID: kAutoGenerateReturnID 59 | transactionID: kAnyTransactionID]; 60 | 61 | [appleEvent 62 | setParamDescriptor: [NSAppleEventDescriptor 63 | descriptorWithDescriptorType: typeFileURL 64 | data: [[pathUrl absoluteString] dataUsingEncoding: NSUTF8StringEncoding]] 65 | forKeyword: keyDirectObject]; 66 | 67 | UInt32 packageCreator = GetCreatorOfThisApp(); 68 | if (packageCreator == kUnknownType) { 69 | [appleEvent 70 | setParamDescriptor: [NSAppleEventDescriptor 71 | descriptorWithDescriptorType: typeApplicationBundleID 72 | data: [[[NSBundle mainBundle] bundleIdentifier] dataUsingEncoding: NSUTF8StringEncoding]] 73 | forKeyword: keyFileSender]; 74 | } else { 75 | [appleEvent 76 | setParamDescriptor: [NSAppleEventDescriptor descriptorWithTypeCode: packageCreator] 77 | forKeyword: keyFileSender]; 78 | } 79 | 80 | if (line != -1) { 81 | // Add selection range to event 82 | SelectionRange range; 83 | range.unused1 = 0; 84 | range.lineNum = line - 1; 85 | range.startRange = -1; 86 | range.endRange = -1; 87 | range.unused2 = 0; 88 | range.theDate = -1; 89 | 90 | [appleEvent 91 | setParamDescriptor: [NSAppleEventDescriptor 92 | descriptorWithDescriptorType: typeChar 93 | bytes: &range 94 | length: sizeof(SelectionRange)] 95 | forKeyword: keyAEPosition]; 96 | } 97 | 98 | AEDesc reply = { typeNull, NULL }; 99 | OSErr err = AESendMessage( 100 | [appleEvent aeDesc], 101 | &reply, 102 | kAENoReply + kAENeverInteract, 103 | kAEDefaultTimeout); 104 | 105 | return err == noErr; 106 | } 107 | 108 | static BOOL ApplicationSupportsQueryOpenedSolution(NSString* appPath) 109 | { 110 | NSURL* appUrl = [NSURL fileURLWithPath: appPath]; 111 | NSBundle* bundle = [NSBundle bundleWithURL: appUrl]; 112 | 113 | if (!bundle) 114 | return NO; 115 | 116 | id versionValue = [bundle objectForInfoDictionaryKey: @"CFBundleVersion"]; 117 | if (!versionValue || ![versionValue isKindOfClass: [NSString class]]) 118 | return NO; 119 | 120 | NSString* version = (NSString*)versionValue; 121 | return [version compare:@"8.6" options:NSNumericSearch] != NSOrderedAscending; 122 | } 123 | 124 | static NSArray* QueryRunningInstances(NSString *appPath) 125 | { 126 | NSMutableArray* instances = [[NSMutableArray alloc] init]; 127 | NSURL *appUrl = [NSURL fileURLWithPath: appPath]; 128 | 129 | for (NSRunningApplication *runningApp in NSWorkspace.sharedWorkspace.runningApplications) { 130 | if (![runningApp isTerminated] && [runningApp.bundleURL isEqual: appUrl]) { 131 | [instances addObject: runningApp]; 132 | } 133 | } 134 | 135 | return instances; 136 | } 137 | 138 | enum { 139 | kWorkspaceEventClass = 1448302419, /* 'VSWS' FourCC */ 140 | kCurrentSelectedSolutionPathEventID = 1129534288 /* 'CSSP' FourCC */ 141 | }; 142 | 143 | static BOOL TryQueryCurrentSolutionPath(NSRunningApplication* runningApp, NSString** solutionPath) 144 | { 145 | NSAppleEventDescriptor* targetDescriptor = [NSAppleEventDescriptor 146 | descriptorWithProcessIdentifier: runningApp.processIdentifier]; 147 | 148 | NSAppleEventDescriptor* appleEvent = [NSAppleEventDescriptor 149 | appleEventWithEventClass: kWorkspaceEventClass 150 | eventID: kCurrentSelectedSolutionPathEventID 151 | targetDescriptor: targetDescriptor 152 | returnID: kAutoGenerateReturnID 153 | transactionID: kAnyTransactionID]; 154 | 155 | AEDesc aeReply = { 0, }; 156 | 157 | OSErr sendResult = AESendMessage( 158 | [appleEvent aeDesc], 159 | &aeReply, 160 | kAEWaitReply | kAENeverInteract, 161 | kAEDefaultTimeout); 162 | 163 | if (sendResult != noErr) { 164 | return NO; 165 | } 166 | 167 | NSAppleEventDescriptor *reply = [[NSAppleEventDescriptor alloc] initWithAEDescNoCopy: &aeReply]; 168 | *solutionPath = [[reply descriptorForKeyword: keyDirectObject] stringValue]; 169 | 170 | return *solutionPath != NULL; 171 | } 172 | 173 | static NSRunningApplication* QueryRunningApplicationOpenedOnSolution(NSString* appPath, NSString* solutionPath) 174 | { 175 | BOOL supportsQueryOpenedSolution = ApplicationSupportsQueryOpenedSolution(appPath); 176 | 177 | for (NSRunningApplication *runningApp in QueryRunningInstances(appPath)) { 178 | // If the currently selected external editor does not support the opened solution apple event 179 | // then fallback to the previous behavior: take the first opened VSM and open the solution 180 | if (!supportsQueryOpenedSolution) { 181 | OpenFileAtLineWithAppleEvent(runningApp, solutionPath, -1); 182 | return runningApp; 183 | } 184 | 185 | NSString* currentSolutionPath; 186 | if (TryQueryCurrentSolutionPath(runningApp, ¤tSolutionPath)) { 187 | if ([solutionPath isEqual:currentSolutionPath]) { 188 | return runningApp; 189 | } 190 | } else { 191 | // If VSM doesn't respond to the query opened solution event 192 | // we fallback to the previous behavior too 193 | OpenFileAtLineWithAppleEvent(runningApp, solutionPath, -1); 194 | return runningApp; 195 | } 196 | } 197 | 198 | return NULL; 199 | } 200 | 201 | static NSRunningApplication* LaunchApplicationOnSolution(NSString* appPath, NSString* solutionPath) 202 | { 203 | return [[NSWorkspace sharedWorkspace] 204 | launchApplicationAtURL: [NSURL fileURLWithPath: appPath] 205 | options: NSWorkspaceLaunchDefault | NSWorkspaceLaunchNewInstance 206 | configuration: @{ 207 | NSWorkspaceLaunchConfigurationArguments: @[ solutionPath ], 208 | } 209 | error: nil]; 210 | } 211 | 212 | static NSRunningApplication* QueryOrLaunchApplication(NSString* appPath, NSString* solutionPath) 213 | { 214 | NSRunningApplication* runningApp = QueryRunningApplicationOpenedOnSolution(appPath, solutionPath); 215 | 216 | if (!runningApp) 217 | runningApp = LaunchApplicationOnSolution(appPath, solutionPath); 218 | 219 | if (runningApp) 220 | [runningApp activateWithOptions: 0]; 221 | 222 | return runningApp; 223 | } 224 | 225 | BOOL LaunchOrReuseApp(NSString* appPath, NSString* solutionPath, NSRunningApplication** outApp) 226 | { 227 | NSRunningApplication* app = QueryOrLaunchApplication(appPath, solutionPath); 228 | 229 | if (outApp) 230 | *outApp = app; 231 | 232 | return app != NULL; 233 | } 234 | 235 | BOOL MonoDevelopOpenFile(NSString* appPath, NSString* solutionPath, NSString* filePath, int line) 236 | { 237 | NSRunningApplication* runningApp; 238 | if (!LaunchOrReuseApp(appPath, solutionPath, &runningApp)) { 239 | return FALSE; 240 | } 241 | 242 | if (filePath) { 243 | return OpenFileAtLineWithAppleEvent(runningApp, filePath, line); 244 | } 245 | 246 | return YES; 247 | } 248 | 249 | #if BUILD_APP 250 | 251 | int main(int argc, const char** argv) 252 | { 253 | if (argc != 5) { 254 | printf("Usage: AppleEventIntegration appPath solutionPath filePath lineNumber\n"); 255 | return 1; 256 | } 257 | 258 | const char* appPath = argv[1]; 259 | const char* solutionPath = argv[2]; 260 | const char* filePath = argv[3]; 261 | const int lineNumber = atoi(argv[4]); 262 | 263 | @autoreleasepool 264 | { 265 | MonoDevelopOpenFile(MakeNSString(appPath), MakeNSString(solutionPath), MakeNSString(filePath), lineNumber); 266 | } 267 | 268 | return 0; 269 | } 270 | 271 | #else 272 | 273 | extern "C" 274 | { 275 | BOOL OpenVisualStudio(const char* appPath, const char* solutionPath, const char* filePath, int line) 276 | { 277 | return MonoDevelopOpenFile(MakeNSString(appPath), MakeNSString(solutionPath), MakeNSString(filePath), line); 278 | } 279 | } 280 | 281 | #endif 282 | -------------------------------------------------------------------------------- /Editor/AppleEventIntegration~/howtobuild.txt: -------------------------------------------------------------------------------- 1 | Bundle style (release) 2 | xcodebuild -configuration Release 3 | 4 | Standalone style (test) 5 | clang++ -D BUILD_APP -framework Foundation -framework AppKit main.mm 6 | -------------------------------------------------------------------------------- /Editor/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly:InternalsVisibleTo("Unity.VisualStudio.EditorTests")] 4 | [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] 5 | -------------------------------------------------------------------------------- /Editor/AssemblyInfo.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e742243860d1a8f409a85e49176adc10 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/AsyncOperation.cs: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Unity Technologies. 3 | * Copyright (c) Microsoft Corporation. All rights reserved. 4 | * Licensed under the MIT License. See License.txt in the project root for license information. 5 | *--------------------------------------------------------------------------------------------*/ 6 | using System; 7 | using System.Threading; 8 | 9 | namespace Microsoft.Unity.VisualStudio.Editor 10 | { 11 | internal class AsyncOperation 12 | { 13 | private readonly Func _producer; 14 | private readonly Func _exceptionHandler; 15 | private readonly Action _finalHandler; 16 | private readonly ManualResetEventSlim _resetEvent; 17 | 18 | private T _result; 19 | private Exception _exception; 20 | 21 | private AsyncOperation(Func producer, Func exceptionHandler, Action finalHandler) 22 | { 23 | _producer = producer; 24 | _exceptionHandler = exceptionHandler; 25 | _finalHandler = finalHandler; 26 | _resetEvent = new ManualResetEventSlim(initialState: false); 27 | } 28 | 29 | public static AsyncOperation Run(Func producer, Func exceptionHandler = null, Action finalHandler = null) 30 | { 31 | var task = new AsyncOperation(producer, exceptionHandler, finalHandler); 32 | task.Run(); 33 | return task; 34 | } 35 | 36 | private void Run() 37 | { 38 | ThreadPool.QueueUserWorkItem(_ => 39 | { 40 | try 41 | { 42 | _result = _producer(); 43 | } 44 | catch (Exception e) 45 | { 46 | _exception = e; 47 | 48 | if (_exceptionHandler != null) 49 | { 50 | _result = _exceptionHandler(e); 51 | } 52 | } 53 | finally 54 | { 55 | _finalHandler?.Invoke(); 56 | _resetEvent.Set(); 57 | } 58 | }); 59 | } 60 | 61 | private void CheckCompletion() 62 | { 63 | if (!_resetEvent.IsSet) 64 | _resetEvent.Wait(); 65 | } 66 | 67 | 68 | public T Result 69 | { 70 | get 71 | { 72 | CheckCompletion(); 73 | return _result; 74 | } 75 | } 76 | 77 | public Exception Exception 78 | { 79 | get 80 | { 81 | CheckCompletion(); 82 | return _exception; 83 | } 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /Editor/AsyncOperation.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 4feb2e7db71d34d40a7e4c0cf33765aa 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/COMIntegration.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 79136730e61b87043bff73e5d2be7151 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Editor/COMIntegration/COMIntegration~/BStrHolder.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | struct BStrHolder 5 | { 6 | BStrHolder() : 7 | m_Str(NULL) 8 | { 9 | } 10 | 11 | BStrHolder(const wchar_t* str) : 12 | m_Str(SysAllocString(str)) 13 | { 14 | } 15 | 16 | ~BStrHolder() 17 | { 18 | if (m_Str != NULL) 19 | SysFreeString(m_Str); 20 | } 21 | 22 | operator BSTR() const 23 | { 24 | return m_Str; 25 | } 26 | 27 | BSTR* operator&() 28 | { 29 | if (m_Str != NULL) 30 | { 31 | SysFreeString(m_Str); 32 | m_Str = NULL; 33 | } 34 | 35 | return &m_Str; 36 | } 37 | 38 | private: 39 | BSTR m_Str; 40 | }; 41 | -------------------------------------------------------------------------------- /Editor/COMIntegration/COMIntegration~/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | 3 | project(com) 4 | set(SOURCES 5 | COMIntegration.cpp 6 | BStrHolder.h 7 | ComPtr.h 8 | dte80a.tlh 9 | ) 10 | set(CMAKE_CXX_STANDARD 17) 11 | set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3 -Wall") 12 | add_executable(COMIntegration ${SOURCES}) 13 | set_property(TARGET COMIntegration PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded") 14 | target_link_libraries(COMIntegration Shlwapi.lib) 15 | -------------------------------------------------------------------------------- /Editor/COMIntegration/COMIntegration~/COMIntegration.cpp: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Unity Technologies. 3 | * Copyright (c) Microsoft Corporation. All rights reserved. 4 | * Licensed under the MIT License. See License.txt in the project root for license information. 5 | *--------------------------------------------------------------------------------------------*/ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | 17 | #include "BStrHolder.h" 18 | #include "ComPtr.h" 19 | #include "dte80a.tlh" 20 | 21 | constexpr int RETRY_INTERVAL_MS = 150; 22 | constexpr int TIMEOUT_MS = 10000; 23 | 24 | // Often a DTE call made to Visual Studio can fail after Visual Studio has just started. Usually the 25 | // return value will be RPC_E_CALL_REJECTED, meaning that Visual Studio is probably busy on another 26 | // thread. This types filter the RPC messages and retries to send the message until VS accepts it. 27 | class CRetryMessageFilter : public IMessageFilter 28 | { 29 | private: 30 | static bool ShouldRetryCall(DWORD dwTickCount, DWORD dwRejectType) 31 | { 32 | if (dwRejectType == SERVERCALL_RETRYLATER || dwRejectType == SERVERCALL_REJECTED) { 33 | return dwTickCount < TIMEOUT_MS; 34 | } 35 | 36 | return false; 37 | } 38 | 39 | win::ComPtr currentFilter; 40 | 41 | public: 42 | CRetryMessageFilter() 43 | { 44 | HRESULT hr = CoRegisterMessageFilter(this, ¤tFilter); 45 | _ASSERT(SUCCEEDED(hr)); 46 | } 47 | 48 | ~CRetryMessageFilter() 49 | { 50 | win::ComPtr messageFilter; 51 | HRESULT hr = CoRegisterMessageFilter(currentFilter, &messageFilter); 52 | _ASSERT(SUCCEEDED(hr)); 53 | } 54 | 55 | // IUnknown methods 56 | IFACEMETHODIMP QueryInterface(REFIID riid, void** ppv) 57 | { 58 | static const QITAB qit[] = 59 | { 60 | QITABENT(CRetryMessageFilter, IMessageFilter), 61 | { 0 }, 62 | }; 63 | return QISearch(this, qit, riid, ppv); 64 | } 65 | 66 | IFACEMETHODIMP_(ULONG) AddRef() 67 | { 68 | return 0; 69 | } 70 | 71 | IFACEMETHODIMP_(ULONG) Release() 72 | { 73 | return 0; 74 | } 75 | 76 | DWORD STDMETHODCALLTYPE HandleInComingCall(DWORD dwCallType, HTASK htaskCaller, DWORD dwTickCount, LPINTERFACEINFO lpInterfaceInfo) 77 | { 78 | if (currentFilter) 79 | return currentFilter->HandleInComingCall(dwCallType, htaskCaller, dwTickCount, lpInterfaceInfo); 80 | 81 | return SERVERCALL_ISHANDLED; 82 | } 83 | 84 | DWORD STDMETHODCALLTYPE RetryRejectedCall(HTASK htaskCallee, DWORD dwTickCount, DWORD dwRejectType) 85 | { 86 | if (ShouldRetryCall(dwTickCount, dwRejectType)) 87 | return RETRY_INTERVAL_MS; 88 | 89 | if (currentFilter) 90 | return currentFilter->RetryRejectedCall(htaskCallee, dwTickCount, dwRejectType); 91 | 92 | return (DWORD)-1; 93 | } 94 | 95 | DWORD STDMETHODCALLTYPE MessagePending(HTASK htaskCallee, DWORD dwTickCount, DWORD dwPendingType) 96 | { 97 | if (currentFilter) 98 | return currentFilter->MessagePending(htaskCallee, dwTickCount, dwPendingType); 99 | 100 | return PENDINGMSG_WAITDEFPROCESS; 101 | } 102 | }; 103 | 104 | static void DisplayProgressbar() { 105 | std::wcout << "displayProgressBar" << std::endl; 106 | } 107 | 108 | static void ClearProgressbar() { 109 | std::wcout << "clearprogressbar" << std::endl; 110 | } 111 | 112 | inline const std::wstring QuoteString(const std::wstring& str) 113 | { 114 | return L"\"" + str + L"\""; 115 | } 116 | 117 | static std::wstring ErrorCodeToMsg(DWORD code) 118 | { 119 | LPWSTR msgBuf = nullptr; 120 | if (!FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 121 | nullptr, code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR)&msgBuf, 0, nullptr)) 122 | { 123 | return L"Unknown error"; 124 | } 125 | else 126 | { 127 | return msgBuf; 128 | } 129 | } 130 | 131 | // Get an environment variable 132 | static std::wstring GetEnvironmentVariableValue(const std::wstring& variableName) { 133 | DWORD currentBufferSize = MAX_PATH; 134 | std::wstring variableValue; 135 | variableValue.resize(currentBufferSize); 136 | 137 | DWORD requiredBufferSize = GetEnvironmentVariableW(variableName.c_str(), variableValue.data(), currentBufferSize); 138 | if (requiredBufferSize == 0) { 139 | // Environment variable probably does not exist. 140 | return std::wstring(); 141 | } 142 | 143 | if (currentBufferSize < requiredBufferSize) { 144 | variableValue.resize(requiredBufferSize); 145 | if (GetEnvironmentVariableW(variableName.c_str(), variableValue.data(), currentBufferSize) == 0) 146 | return std::wstring(); 147 | } 148 | 149 | variableValue.resize(requiredBufferSize); 150 | return variableValue; 151 | } 152 | 153 | static bool StartVisualStudioProcess( 154 | const std::filesystem::path &visualStudioExecutablePath, 155 | const std::filesystem::path &solutionPath, 156 | DWORD *dwProcessId) { 157 | 158 | STARTUPINFOW si; 159 | PROCESS_INFORMATION pi; 160 | BOOL result; 161 | 162 | ZeroMemory(&si, sizeof(si)); 163 | si.cb = sizeof(si); 164 | ZeroMemory(&pi, sizeof(pi)); 165 | 166 | std::wstring startingDirectory = visualStudioExecutablePath.parent_path(); 167 | 168 | // Build the command line that is passed as the argv of the VS process 169 | // argv[0] must be the quoted full path to the VS exe 170 | std::wstringstream commandLineStream; 171 | commandLineStream << QuoteString(visualStudioExecutablePath) << L" "; 172 | 173 | std::wstring vsArgsWide = GetEnvironmentVariableValue(L"UNITY_VS_ARGS"); 174 | if (!vsArgsWide.empty()) 175 | commandLineStream << vsArgsWide << L" "; 176 | 177 | commandLineStream << QuoteString(solutionPath); 178 | 179 | std::wstring commandLine = commandLineStream.str(); 180 | 181 | std::wcout << "Starting Visual Studio process with: " << commandLine << std::endl; 182 | 183 | result = CreateProcessW( 184 | visualStudioExecutablePath.c_str(), // Full path to VS, must not be quoted 185 | commandLine.data(), // Command line, as passed as argv, separate arguments must be quoted if they contain spaces 186 | nullptr, // Process handle not inheritable 187 | nullptr, // Thread handle not inheritable 188 | false, // Set handle inheritance to FALSE 189 | 0, // No creation flags 190 | nullptr, // Use parent's environment block 191 | startingDirectory.c_str(), // starting directory set to the VS directory 192 | &si, 193 | &pi); 194 | 195 | if (!result) { 196 | DWORD error = GetLastError(); 197 | std::wcout << "Starting Visual Studio process failed: " << ErrorCodeToMsg(error) << std::endl; 198 | return false; 199 | } 200 | 201 | *dwProcessId = pi.dwProcessId; 202 | CloseHandle(pi.hProcess); 203 | CloseHandle(pi.hThread); 204 | 205 | return true; 206 | } 207 | 208 | static bool 209 | MonikerIsVisualStudioProcess(const win::ComPtr &moniker, const win::ComPtr &bindCtx, const DWORD dwProcessId = 0) { 210 | LPOLESTR oleMonikerName; 211 | if (FAILED(moniker->GetDisplayName(bindCtx, nullptr, &oleMonikerName))) 212 | return false; 213 | 214 | std::wstring monikerName(oleMonikerName); 215 | 216 | // VisualStudio Moniker is "!VisualStudio.DTE.$Version:$PID" 217 | // Example "!VisualStudio.DTE.14.0:1234" 218 | 219 | if (monikerName.find(L"!VisualStudio.DTE") != 0) 220 | return false; 221 | 222 | if (dwProcessId == 0) 223 | return true; 224 | 225 | std::wstringstream suffixStream; 226 | suffixStream << ":"; 227 | suffixStream << dwProcessId; 228 | 229 | std::wstring suffix(suffixStream.str()); 230 | 231 | return monikerName.length() - suffix.length() == monikerName.find(suffix); 232 | } 233 | 234 | static win::ComPtr FindRunningVisualStudioWithSolution( 235 | const std::filesystem::path &visualStudioExecutablePath, 236 | const std::filesystem::path &solutionPath) 237 | { 238 | win::ComPtr punk = nullptr; 239 | win::ComPtr dte = nullptr; 240 | 241 | CRetryMessageFilter retryMessageFilter; 242 | 243 | // Search through the Running Object Table for an instance of Visual Studio 244 | // to use that either has the correct solution already open or does not have 245 | // any solution open. 246 | win::ComPtr ROT; 247 | if (FAILED(GetRunningObjectTable(0, &ROT))) 248 | return nullptr; 249 | 250 | win::ComPtr bindCtx; 251 | if (FAILED(CreateBindCtx(0, &bindCtx))) 252 | return nullptr; 253 | 254 | win::ComPtr enumMoniker; 255 | if (FAILED(ROT->EnumRunning(&enumMoniker))) 256 | return nullptr; 257 | 258 | win::ComPtr moniker; 259 | ULONG monikersFetched = 0; 260 | while (SUCCEEDED(enumMoniker->Next(1, &moniker, &monikersFetched)) && monikersFetched) { 261 | if (!MonikerIsVisualStudioProcess(moniker, bindCtx)) 262 | continue; 263 | 264 | if (FAILED(ROT->GetObject(moniker, &punk))) 265 | continue; 266 | 267 | punk.As(&dte); 268 | if (!dte) 269 | continue; 270 | 271 | // Okay, so we found an actual running instance of Visual Studio. 272 | 273 | // Get the executable path of this running instance. 274 | BStrHolder visualStudioFullName; 275 | if (FAILED(dte->get_FullName(&visualStudioFullName))) 276 | continue; 277 | 278 | std::filesystem::path currentVisualStudioExecutablePath = std::wstring(visualStudioFullName); 279 | 280 | // Ask for its current solution. 281 | win::ComPtr solution; 282 | if (FAILED(dte->get_Solution(&solution))) 283 | continue; 284 | 285 | // Get the name of that solution. 286 | BStrHolder solutionFullName; 287 | if (FAILED(solution->get_FullName(&solutionFullName))) 288 | continue; 289 | 290 | std::filesystem::path currentSolutionPath = std::wstring(solutionFullName); 291 | if (currentSolutionPath.empty()) 292 | continue; 293 | 294 | std::wcout << "Visual Studio opened on " << currentSolutionPath.wstring() << std::endl; 295 | 296 | // If the name matches the solution we want to open and we have a Visual Studio installation path to use and this one matches that path, then use it. 297 | // If we don't have a Visual Studio installation path to use, just use this solution. 298 | if (std::filesystem::equivalent(currentSolutionPath, solutionPath)) { 299 | std::wcout << "We found a running Visual Studio session with the solution open." << std::endl; 300 | if (!visualStudioExecutablePath.empty()) { 301 | if (std::filesystem::equivalent(currentVisualStudioExecutablePath, visualStudioExecutablePath)) { 302 | return dte; 303 | } 304 | else { 305 | std::wcout << "This running Visual Studio session does not seem to be the version requested in the user preferences. We will keep looking." << std::endl; 306 | } 307 | } 308 | else { 309 | std::wcout << "We're not sure which version of Visual Studio was requested in the user preferences. We will use this running session." << std::endl; 310 | return dte; 311 | } 312 | } 313 | } 314 | return nullptr; 315 | } 316 | 317 | static win::ComPtr FindRunningVisualStudioWithPID(const DWORD dwProcessId) { 318 | win::ComPtr punk = nullptr; 319 | win::ComPtr dte = nullptr; 320 | 321 | // Search through the Running Object Table for a Visual Studio 322 | // process with the process ID specified 323 | win::ComPtr ROT; 324 | if (FAILED(GetRunningObjectTable(0, &ROT))) 325 | return nullptr; 326 | 327 | win::ComPtr bindCtx; 328 | if (FAILED(CreateBindCtx(0, &bindCtx))) 329 | return nullptr; 330 | 331 | win::ComPtr enumMoniker; 332 | if (FAILED(ROT->EnumRunning(&enumMoniker))) 333 | return nullptr; 334 | 335 | win::ComPtr moniker; 336 | ULONG monikersFetched = 0; 337 | while (SUCCEEDED(enumMoniker->Next(1, &moniker, &monikersFetched)) && monikersFetched) { 338 | if (!MonikerIsVisualStudioProcess(moniker, bindCtx, dwProcessId)) 339 | continue; 340 | 341 | if (FAILED(ROT->GetObject(moniker, &punk))) 342 | continue; 343 | 344 | punk.As(&dte); 345 | if (dte) 346 | return dte; 347 | } 348 | 349 | return nullptr; 350 | } 351 | 352 | static bool HaveRunningVisualStudioOpenFile(const win::ComPtr &dte, const std::filesystem::path &filename, int line) { 353 | BStrHolder bstrFileName(filename.c_str()); 354 | BStrHolder bstrKind(L"{00000000-0000-0000-0000-000000000000}"); // EnvDTE::vsViewKindPrimary 355 | win::ComPtr window = nullptr; 356 | 357 | CRetryMessageFilter retryMessageFilter; 358 | 359 | if (!filename.empty()) { 360 | std::wcout << "Getting operations API from the Visual Studio session." << std::endl; 361 | 362 | win::ComPtr item_ops; 363 | if (FAILED(dte->get_ItemOperations(&item_ops))) 364 | return false; 365 | 366 | std::wcout << "Waiting for the Visual Studio session to open the file: " << filename.wstring() << "." << std::endl; 367 | 368 | if (FAILED(item_ops->OpenFile(bstrFileName, bstrKind, &window))) 369 | return false; 370 | 371 | if (line > 0) { 372 | win::ComPtr selection_dispatch; 373 | if (window && SUCCEEDED(window->get_Selection(&selection_dispatch))) { 374 | win::ComPtr selection; 375 | if (selection_dispatch && 376 | SUCCEEDED(selection_dispatch->QueryInterface(__uuidof(EnvDTE::TextSelection), &selection)) && 377 | selection) { 378 | selection->GotoLine(line, false); 379 | selection->EndOfLine(false); 380 | } 381 | } 382 | } 383 | } 384 | 385 | window = nullptr; 386 | if (SUCCEEDED(dte->get_MainWindow(&window))) { 387 | // Allow the DTE to make its main window the foreground 388 | HWND hWnd; 389 | window->get_HWnd((LONG *)&hWnd); 390 | 391 | DWORD processID; 392 | if (SUCCEEDED(GetWindowThreadProcessId(hWnd, &processID))) 393 | AllowSetForegroundWindow(processID); 394 | 395 | // Activate() set the window to visible and active (blinks in taskbar) 396 | window->Activate(); 397 | } 398 | 399 | return true; 400 | } 401 | 402 | static bool VisualStudioOpenFile( 403 | const std::filesystem::path &visualStudioExecutablePath, 404 | const std::filesystem::path &solutionPath, 405 | const std::filesystem::path &filename, 406 | int line) 407 | { 408 | win::ComPtr dte = nullptr; 409 | 410 | std::wcout << "Looking for a running Visual Studio session." << std::endl; 411 | 412 | // TODO: If path does not exist pass empty, which will just try to match all windows with solution 413 | dte = FindRunningVisualStudioWithSolution(visualStudioExecutablePath, solutionPath); 414 | 415 | if (!dte) { 416 | std::wcout << "No appropriate running Visual Studio session not found, creating a new one." << std::endl; 417 | 418 | DisplayProgressbar(); 419 | 420 | DWORD dwProcessId; 421 | if (!StartVisualStudioProcess(visualStudioExecutablePath, solutionPath, &dwProcessId)) { 422 | ClearProgressbar(); 423 | return false; 424 | } 425 | 426 | int timeWaited = 0; 427 | 428 | while (timeWaited < TIMEOUT_MS) { 429 | dte = FindRunningVisualStudioWithPID(dwProcessId); 430 | 431 | if (dte) 432 | break; 433 | 434 | std::wcout << "Retrying to acquire DTE" << std::endl; 435 | 436 | Sleep(RETRY_INTERVAL_MS); 437 | timeWaited += RETRY_INTERVAL_MS; 438 | } 439 | 440 | ClearProgressbar(); 441 | 442 | if (!dte) 443 | return false; 444 | } 445 | else { 446 | std::wcout << "Using the existing Visual Studio session." << std::endl; 447 | } 448 | 449 | return HaveRunningVisualStudioOpenFile(dte, filename, line); 450 | } 451 | 452 | int wmain(int argc, wchar_t* argv[]) { 453 | 454 | // We need this to properly display UTF16 text on the console 455 | _setmode(_fileno(stdout), _O_U16TEXT); 456 | 457 | if (argc != 3 && argc != 5) { 458 | std::wcerr << argc << ": wrong number of arguments\n" << "Usage: com.exe installationPath solutionPath [fileName lineNumber]" << std::endl; 459 | for (int i = 0; i < argc; i++) { 460 | std::wcerr << argv[i] << std::endl; 461 | } 462 | return EXIT_FAILURE; 463 | } 464 | 465 | if (FAILED(CoInitialize(nullptr))) { 466 | std::wcerr << "CoInitialize failed." << std::endl; 467 | return EXIT_FAILURE; 468 | } 469 | 470 | std::filesystem::path visualStudioExecutablePath = std::filesystem::absolute(argv[1]); 471 | std::filesystem::path solutionPath = std::filesystem::absolute(argv[2]); 472 | 473 | if (argc == 3) { 474 | VisualStudioOpenFile(visualStudioExecutablePath, solutionPath, L"", -1); 475 | return EXIT_SUCCESS; 476 | } 477 | 478 | std::filesystem::path fileName = std::filesystem::absolute(argv[3]); 479 | int lineNumber = std::stoi(argv[4]); 480 | 481 | VisualStudioOpenFile(visualStudioExecutablePath, solutionPath, fileName, lineNumber); 482 | return EXIT_SUCCESS; 483 | } 484 | -------------------------------------------------------------------------------- /Editor/COMIntegration/COMIntegration~/ComPtr.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace win 4 | { 5 | template 6 | class ComPtr; 7 | 8 | template 9 | class ComPtrRef 10 | { 11 | private: 12 | ComPtr& m_ComPtr; 13 | 14 | ComPtrRef(ComPtr& comPtr) : 15 | m_ComPtr(comPtr) 16 | { 17 | } 18 | 19 | friend class ComPtr; 20 | 21 | public: 22 | inline operator T**() 23 | { 24 | return m_ComPtr.ReleaseAndGetAddressOf(); 25 | } 26 | 27 | inline operator void**() 28 | { 29 | return reinterpret_cast(m_ComPtr.ReleaseAndGetAddressOf()); 30 | } 31 | 32 | inline T* operator*() throw () 33 | { 34 | return m_ComPtr; 35 | } 36 | 37 | }; 38 | 39 | template 40 | class ComPtr 41 | { 42 | private: 43 | T *ptr; 44 | 45 | public: 46 | inline ComPtr(void) : ptr(NULL) {} 47 | inline ~ComPtr(void) { this->Free(); } 48 | 49 | ComPtr(T *ptr) 50 | { 51 | if (NULL != (this->ptr = ptr)) 52 | { 53 | this->ptr->AddRef(); 54 | } 55 | } 56 | 57 | ComPtr(const ComPtr &ptr) 58 | { 59 | if (NULL != (this->ptr = ptr.ptr)) 60 | { 61 | this->ptr->AddRef(); 62 | } 63 | } 64 | 65 | inline bool operator!() const 66 | { 67 | return (NULL == this->ptr); 68 | } 69 | 70 | inline operator T*() const { return this->ptr; } 71 | 72 | inline T *operator->() const 73 | { 74 | //_assert(NULL != this->ptr); 75 | return this->ptr; 76 | } 77 | 78 | inline T &operator*() 79 | { 80 | //_assert(NULL != this->ptr); 81 | return *this->ptr; 82 | } 83 | 84 | inline ComPtrRef operator&() 85 | { 86 | return ComPtrRef(*this); 87 | } 88 | 89 | const ComPtr &operator=(T *ptr) 90 | { 91 | if (this->ptr != ptr) 92 | { 93 | this->Free(); 94 | 95 | if (NULL != (this->ptr = ptr)) 96 | { 97 | this->ptr->AddRef(); 98 | } 99 | } 100 | 101 | return *this; 102 | } 103 | 104 | const ComPtr &operator=(const ComPtr &ptr) 105 | { 106 | if (this->ptr != ptr.ptr) 107 | { 108 | this->Free(); 109 | 110 | if (NULL != (this->ptr = ptr.ptr)) 111 | { 112 | this->ptr->AddRef(); 113 | } 114 | } 115 | 116 | return *this; 117 | } 118 | 119 | void Free(void) 120 | { 121 | if (NULL != this->ptr) 122 | { 123 | this->ptr->Release(); 124 | this->ptr = NULL; 125 | } 126 | } 127 | 128 | inline T** ReleaseAndGetAddressOf() 129 | { 130 | Free(); 131 | return &ptr; 132 | } 133 | 134 | template 135 | inline HRESULT As(ComPtrRef p) const throw () 136 | { 137 | return ptr->QueryInterface(__uuidof(U), p); 138 | } 139 | 140 | inline bool operator==(std::nullptr_t) const 141 | { 142 | return this->ptr == nullptr; 143 | } 144 | 145 | template 146 | inline bool operator==(U* other) 147 | { 148 | if (ptr == nullptr || other == nullptr) 149 | return ptr == other; 150 | 151 | ComPtr meUnknown; 152 | ComPtr otherUnknown; 153 | 154 | if (FAILED(this->ptr->QueryInterface(__uuidof(IUnknown), &meUnknown))) 155 | return false; 156 | 157 | if (FAILED(other->QueryInterface(__uuidof(IUnknown), &otherUnknown))) 158 | return false; 159 | 160 | return static_cast(meUnknown) == static_cast(otherUnknown); 161 | } 162 | 163 | template 164 | inline bool operator==(ComPtr& other) 165 | { 166 | return *this == static_cast(other); 167 | } 168 | 169 | inline bool operator!=(std::nullptr_t) const 170 | { 171 | return this->ptr != nullptr; 172 | } 173 | 174 | template 175 | inline bool operator!=(U* other) 176 | { 177 | return !(*this == other); 178 | } 179 | 180 | template 181 | inline bool operator!=(ComPtr& other) 182 | { 183 | return *this != static_cast(other); 184 | } 185 | }; 186 | } 187 | -------------------------------------------------------------------------------- /Editor/COMIntegration/COMIntegration~/howtobuild.txt: -------------------------------------------------------------------------------- 1 | Direct style: 2 | cl /EHsc /std:c++17 COMIntegration.cpp /link Shlwapi.lib /out:"..\Release\COMIntegration.exe" 3 | 4 | For a debug build with PDB: 5 | cl /EHsc /std:c++17 /Z7 /DEBUG:FULL COMIntegration.cpp /link Shlwapi.lib /out:"..\Release\COMIntegration.exe" 6 | 7 | CMake style: 8 | cmake ../COMIntegration~ -B ./build 9 | cmake --build ./build --config=release -- /p:OutDir=.. 10 | -------------------------------------------------------------------------------- /Editor/COMIntegration/Release.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a92aed9328d17a74d97b976bd0ba80da 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Editor/COMIntegration/Release/COMIntegration.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liyingsong99/com.unity.ide.cursor-et/2ff42a4c32bdbe24a12d24762bf43da7cdccc76a/Editor/COMIntegration/Release/COMIntegration.exe -------------------------------------------------------------------------------- /Editor/COMIntegration/Release/COMIntegration.exe.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 2d8ba14fe0a703a46a0112cb50c1f802 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Editor/Cli.cs: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | using System; 6 | using System.Linq; 7 | using Unity.CodeEditor; 8 | 9 | namespace Microsoft.Unity.VisualStudio.Editor 10 | { 11 | internal static class Cli 12 | { 13 | internal static void Log(string message) 14 | { 15 | // Use writeline here, instead of UnityEngine.Debug.Log to not include the stacktrace in the editor.log 16 | Console.WriteLine($"[VisualStudio.Editor.{nameof(Cli)}] {message}"); 17 | } 18 | 19 | internal static string GetInstallationDetails(IVisualStudioInstallation installation) 20 | { 21 | return $"{installation.ToCodeEditorInstallation().Name} Path:{installation.Path}, LanguageVersionSupport:{installation.LatestLanguageVersionSupported} AnalyzersSupport:{installation.SupportsAnalyzers}"; 22 | } 23 | 24 | internal static void GenerateSolutionWith(VisualStudioEditor vse, string installationPath) 25 | { 26 | if (vse != null && vse.TryGetVisualStudioInstallationForPath(installationPath, lookupDiscoveredInstallations: true, out var vsi)) 27 | { 28 | Log($"Using {GetInstallationDetails(vsi)}"); 29 | vse.SyncAll(); 30 | } 31 | else 32 | { 33 | Log($"No Visual Studio installation found in ${installationPath}!"); 34 | } 35 | } 36 | 37 | internal static void GenerateSolution() 38 | { 39 | if (CodeEditor.CurrentEditor is VisualStudioEditor vse) 40 | { 41 | Log($"Using default editor settings for Visual Studio installation"); 42 | GenerateSolutionWith(vse, CodeEditor.CurrentEditorInstallation); 43 | } 44 | else 45 | { 46 | Log($"Visual Studio is not set as your default editor, looking for installations"); 47 | try 48 | { 49 | var installations = Discovery 50 | .GetVisualStudioInstallations() 51 | .Cast() 52 | .OrderByDescending(vsi => !vsi.IsPrerelease) 53 | .ThenBy(vsi => vsi.Version) 54 | .ToArray(); 55 | 56 | foreach(var vsi in installations) 57 | { 58 | Log($"Detected {GetInstallationDetails(vsi)}"); 59 | } 60 | 61 | var installation = installations 62 | .FirstOrDefault(); 63 | 64 | if (installation != null) 65 | { 66 | var current = CodeEditor.CurrentEditorInstallation; 67 | try 68 | { 69 | CodeEditor.SetExternalScriptEditor(installation.Path); 70 | GenerateSolutionWith(CodeEditor.CurrentEditor as VisualStudioEditor, installation.Path); 71 | } 72 | finally 73 | { 74 | CodeEditor.SetExternalScriptEditor(current); 75 | } 76 | } else 77 | { 78 | Log($"No Visual Studio installation found!"); 79 | } 80 | } 81 | catch (Exception ex) 82 | { 83 | Log($"Error detecting Visual Studio installations: {ex}"); 84 | } 85 | } 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Editor/Cli.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 119670827e2bdfb4c8bab8ae43a91bf6 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/Discovery.cs: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Unity Technologies. 3 | * Copyright (c) Microsoft Corporation. All rights reserved. 4 | * Licensed under the MIT License. See License.txt in the project root for license information. 5 | *--------------------------------------------------------------------------------------------*/ 6 | 7 | using System.Collections.Generic; 8 | using System.IO; 9 | 10 | namespace Microsoft.Unity.VisualStudio.Editor 11 | { 12 | internal static class Discovery 13 | { 14 | public static IEnumerable GetVisualStudioInstallations() 15 | { 16 | foreach (var installation in VisualStudioCursorInstallation.GetVisualStudioInstallations()) 17 | yield return installation; 18 | foreach (var installation in VisualStudioCodiumInstallation.GetVisualStudioInstallations()) 19 | yield return installation; 20 | } 21 | 22 | public static bool TryDiscoverInstallation(string editorPath, out IVisualStudioInstallation installation) 23 | { 24 | try 25 | { 26 | if (VisualStudioCursorInstallation.TryDiscoverInstallation(editorPath, out installation)) 27 | return true; 28 | if (VisualStudioCodiumInstallation.TryDiscoverInstallation(editorPath, out installation)) 29 | return true; 30 | } 31 | catch (IOException) 32 | { 33 | installation = null; 34 | } 35 | 36 | return false; 37 | } 38 | 39 | public static void Initialize() 40 | { 41 | VisualStudioCursorInstallation.Initialize(); 42 | VisualStudioCodiumInstallation.Initialize(); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Editor/Discovery.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b9e6525873b60214bac98a2611001065 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/FileUtility.cs: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Unity Technologies. 3 | * Copyright (c) Microsoft Corporation. All rights reserved. 4 | * Licensed under the MIT License. See License.txt in the project root for license information. 5 | *--------------------------------------------------------------------------------------------*/ 6 | using System; 7 | using System.IO; 8 | using UnityEngine; 9 | 10 | namespace Microsoft.Unity.VisualStudio.Editor 11 | { 12 | internal static class FileUtility 13 | { 14 | public const char WinSeparator = '\\'; 15 | public const char UnixSeparator = '/'; 16 | 17 | public static string GetPackageAssetFullPath(params string[] components) 18 | { 19 | // Unity has special IO handling of Packages and will resolve those path to the right package location 20 | return Path.GetFullPath(Path.Combine("Packages", "com.unity.ide.visualstudio", Path.Combine(components))); 21 | } 22 | 23 | public static string GetAssetFullPath(string asset) 24 | { 25 | var basePath = Path.GetFullPath(Path.Combine(Application.dataPath, "..")); 26 | return Path.GetFullPath(Path.Combine(basePath, NormalizePathSeparators(asset))); 27 | } 28 | 29 | public static string NormalizePathSeparators(this string path) 30 | { 31 | if (string.IsNullOrEmpty(path)) 32 | return path; 33 | 34 | if (Path.DirectorySeparatorChar == WinSeparator) 35 | path = path.Replace(UnixSeparator, WinSeparator); 36 | if (Path.DirectorySeparatorChar == UnixSeparator) 37 | path = path.Replace(WinSeparator, UnixSeparator); 38 | 39 | return path.Replace(string.Concat(WinSeparator, WinSeparator), WinSeparator.ToString()); 40 | } 41 | 42 | public static string NormalizeWindowsToUnix(this string path) 43 | { 44 | if (string.IsNullOrEmpty(path)) 45 | return path; 46 | 47 | return path.Replace(WinSeparator, UnixSeparator); 48 | } 49 | 50 | internal static bool IsFileInProjectRootDirectory(string fileName) 51 | { 52 | var relative = MakeRelativeToProjectPath(fileName); 53 | if (string.IsNullOrEmpty(relative)) 54 | return false; 55 | 56 | return relative == Path.GetFileName(relative); 57 | } 58 | 59 | public static string MakeAbsolutePath(this string path) 60 | { 61 | if (string.IsNullOrEmpty(path)) { return string.Empty; } 62 | return Path.IsPathRooted(path) ? path : Path.GetFullPath(path); 63 | } 64 | 65 | // returns null if outside of the project scope 66 | internal static string MakeRelativeToProjectPath(string fileName) 67 | { 68 | var basePath = Path.GetFullPath(Path.Combine(Application.dataPath, "..")); 69 | fileName = NormalizePathSeparators(fileName); 70 | 71 | if (!Path.IsPathRooted(fileName)) 72 | fileName = Path.Combine(basePath, fileName); 73 | 74 | if (!fileName.StartsWith(basePath, StringComparison.OrdinalIgnoreCase)) 75 | return null; 76 | 77 | return fileName 78 | .Substring(basePath.Length) 79 | .Trim(Path.DirectorySeparatorChar); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Editor/FileUtility.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: fa3cfc2997f49b943b10f66c511ab9b7 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/Image.cs: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | using System; 6 | using System.IO; 7 | 8 | namespace Microsoft.Unity.VisualStudio.Editor 9 | { 10 | public sealed class Image : IDisposable 11 | { 12 | 13 | long position; 14 | Stream stream; 15 | 16 | Image(Stream stream) 17 | { 18 | this.stream = stream; 19 | this.position = stream.Position; 20 | this.stream.Position = 0; 21 | } 22 | 23 | bool Advance(int length) 24 | { 25 | if (stream.Position + length >= stream.Length) 26 | return false; 27 | 28 | stream.Seek(length, SeekOrigin.Current); 29 | return true; 30 | } 31 | 32 | bool MoveTo(uint position) 33 | { 34 | if (position >= stream.Length) 35 | return false; 36 | 37 | stream.Position = position; 38 | return true; 39 | } 40 | 41 | void IDisposable.Dispose() 42 | { 43 | stream.Position = position; 44 | } 45 | 46 | ushort ReadUInt16() 47 | { 48 | return (ushort)(stream.ReadByte() 49 | | (stream.ReadByte() << 8)); 50 | } 51 | 52 | uint ReadUInt32() 53 | { 54 | return (uint)(stream.ReadByte() 55 | | (stream.ReadByte() << 8) 56 | | (stream.ReadByte() << 16) 57 | | (stream.ReadByte() << 24)); 58 | } 59 | 60 | bool IsManagedAssembly() 61 | { 62 | if (stream.Length < 318) 63 | return false; 64 | if (ReadUInt16() != 0x5a4d) 65 | return false; 66 | if (!Advance(58)) 67 | return false; 68 | if (!MoveTo(ReadUInt32())) 69 | return false; 70 | if (ReadUInt32() != 0x00004550) 71 | return false; 72 | if (!Advance(20)) 73 | return false; 74 | if (!Advance(ReadUInt16() == 0x20b ? 222 : 206)) 75 | return false; 76 | 77 | return ReadUInt32() != 0; 78 | } 79 | 80 | public static bool IsAssembly(string file) 81 | { 82 | if (file == null) 83 | throw new ArgumentNullException("file"); 84 | 85 | using (var stream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read)) 86 | return IsAssembly(stream); 87 | } 88 | 89 | public static bool IsAssembly(Stream stream) 90 | { 91 | if (stream == null) 92 | throw new ArgumentNullException(nameof(stream)); 93 | if (!stream.CanRead) 94 | throw new ArgumentException(nameof(stream)); 95 | if (!stream.CanSeek) 96 | throw new ArgumentException(nameof(stream)); 97 | 98 | using (var image = new Image(stream)) 99 | return image.IsManagedAssembly(); 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /Editor/Image.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 8c2650c3659765147bb1ecb95c04f931 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/KnownAssemblies.cs: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | namespace Microsoft.Unity.VisualStudio.Editor 7 | { 8 | internal static class KnownAssemblies 9 | { 10 | public const string Bridge = "SyntaxTree.VisualStudio.Unity.Bridge"; 11 | public const string Messaging = "SyntaxTree.VisualStudio.Unity.Messaging"; 12 | public const string UnityVS = "UnityVS.VersionSpecific"; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Editor/KnownAssemblies.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 876a40e26248b4d49bb090978f2428d8 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/Messaging.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: edf1e631e73499647a7b28951b9910ef 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Editor/Messaging/Deserializer.cs: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | using System.IO; 6 | using System.Text; 7 | 8 | namespace Microsoft.Unity.VisualStudio.Editor.Messaging 9 | { 10 | internal class Deserializer 11 | { 12 | private readonly BinaryReader _reader; 13 | 14 | public Deserializer(byte[] buffer) 15 | { 16 | _reader = new BinaryReader(new MemoryStream(buffer)); 17 | } 18 | 19 | public int ReadInt32() 20 | { 21 | return _reader.ReadInt32(); 22 | } 23 | 24 | public string ReadString() 25 | { 26 | var length = ReadInt32(); 27 | return length > 0 28 | ? Encoding.UTF8.GetString(_reader.ReadBytes(length)) 29 | : ""; 30 | } 31 | 32 | public bool CanReadMore() 33 | { 34 | return _reader.BaseStream.Position < _reader.BaseStream.Length; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Editor/Messaging/Deserializer.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 4158afe0f3ba24543b5573829095c090 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/Messaging/ExceptionEventArgs.cs: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | using System; 6 | 7 | namespace Microsoft.Unity.VisualStudio.Editor.Messaging 8 | { 9 | internal class ExceptionEventArgs 10 | { 11 | public Exception Exception { get; } 12 | 13 | public ExceptionEventArgs(Exception exception) 14 | { 15 | Exception = exception; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Editor/Messaging/ExceptionEventArgs.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a60ca3635cca04b42a8b5b83471bbb2a 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/Messaging/Message.cs: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | using System.Globalization; 6 | using System.Net; 7 | 8 | namespace Microsoft.Unity.VisualStudio.Editor.Messaging 9 | { 10 | internal class Message 11 | { 12 | public MessageType Type { get; set; } 13 | 14 | public string Value { get; set; } 15 | 16 | public IPEndPoint Origin { get; set; } 17 | 18 | public override string ToString() 19 | { 20 | return string.Format(CultureInfo.InvariantCulture, "", Type, Value); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Editor/Messaging/Message.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b76af5429092a58459912042d3474e74 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/Messaging/MessageEventArgs.cs: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | namespace Microsoft.Unity.VisualStudio.Editor.Messaging 6 | { 7 | internal class MessageEventArgs 8 | { 9 | public Message Message 10 | { 11 | get; 12 | } 13 | 14 | public MessageEventArgs(Message message) 15 | { 16 | Message = message; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Editor/Messaging/MessageEventArgs.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 53f72b24585bb564ebdf15944b1a9a78 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/Messaging/MessageType.cs: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | namespace Microsoft.Unity.VisualStudio.Editor.Messaging 6 | { 7 | internal enum MessageType 8 | { 9 | None = 0, 10 | 11 | Ping, 12 | Pong, 13 | 14 | Play, 15 | Stop, 16 | Pause, 17 | Unpause, 18 | 19 | Build, 20 | Refresh, 21 | 22 | Info, 23 | Error, 24 | Warning, 25 | 26 | Open, 27 | Opened, 28 | 29 | Version, 30 | UpdatePackage, 31 | 32 | ProjectPath, 33 | 34 | // This message is a technical one for big messages, not intended to be used directly 35 | Tcp, 36 | 37 | RunStarted, 38 | RunFinished, 39 | TestStarted, 40 | TestFinished, 41 | TestListRetrieved, 42 | 43 | RetrieveTestList, 44 | ExecuteTests, 45 | 46 | ShowUsage 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Editor/Messaging/MessageType.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: bca6d6cb5c976544d83d550e80d0ab10 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/Messaging/Messenger.cs: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | using System; 6 | using System.Net; 7 | using System.Net.Sockets; 8 | 9 | namespace Microsoft.Unity.VisualStudio.Editor.Messaging 10 | { 11 | internal class Messager : IDisposable 12 | { 13 | public event EventHandler ReceiveMessage; 14 | public event EventHandler MessagerException; 15 | 16 | private readonly UdpSocket _socket; 17 | private readonly object _disposeLock = new object(); 18 | private bool _disposed; 19 | 20 | #if UNITY_EDITOR_WIN 21 | [System.Runtime.InteropServices.DllImport("kernel32.dll", SetLastError = true)] 22 | private static extern bool SetHandleInformation(IntPtr hObject, HandleFlags dwMask, HandleFlags dwFlags); 23 | 24 | [Flags] 25 | private enum HandleFlags: uint 26 | { 27 | None = 0, 28 | Inherit = 1, 29 | ProtectFromClose = 2 30 | } 31 | #endif 32 | 33 | protected Messager(int port) 34 | { 35 | _socket = new UdpSocket(); 36 | _socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ExclusiveAddressUse, false); 37 | _socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); 38 | 39 | #if UNITY_EDITOR_WIN 40 | // Explicitely disable inheritance for our UDP socket handle 41 | // We found that Unity is creating a fork when importing new assets that can clone our socket 42 | SetHandleInformation(_socket.Handle, HandleFlags.Inherit, HandleFlags.None); 43 | #endif 44 | 45 | _socket.Bind(IPAddress.Any, port); 46 | 47 | BeginReceiveMessage(); 48 | } 49 | 50 | private void BeginReceiveMessage() 51 | { 52 | var buffer = new byte[UdpSocket.BufferSize]; 53 | var any = UdpSocket.Any(); 54 | 55 | try 56 | { 57 | lock (_disposeLock) 58 | { 59 | if (_disposed) 60 | return; 61 | 62 | _socket.BeginReceiveFrom(buffer, 0, buffer.Length, SocketFlags.None, ref any, ReceiveMessageCallback, buffer); 63 | } 64 | } 65 | catch (SocketException se) 66 | { 67 | MessagerException?.Invoke(this, new ExceptionEventArgs(se)); 68 | 69 | BeginReceiveMessage(); 70 | } 71 | catch (ObjectDisposedException) 72 | { 73 | } 74 | } 75 | 76 | private void ReceiveMessageCallback(IAsyncResult result) 77 | { 78 | try 79 | { 80 | var endPoint = UdpSocket.Any(); 81 | 82 | lock (_disposeLock) 83 | { 84 | if (_disposed) 85 | return; 86 | 87 | _socket.EndReceiveFrom(result, ref endPoint); 88 | } 89 | 90 | var message = DeserializeMessage(UdpSocket.BufferFor(result)); 91 | if (message != null) 92 | { 93 | message.Origin = (IPEndPoint)endPoint; 94 | 95 | if (IsValidTcpMessage(message, out var port, out var bufferSize)) 96 | { 97 | // switch to TCP mode to handle big messages 98 | TcpClient.Queue(message.Origin.Address, port, bufferSize, buffer => 99 | { 100 | var originalMessage = DeserializeMessage(buffer); 101 | originalMessage.Origin = message.Origin; 102 | ReceiveMessage?.Invoke(this, new MessageEventArgs(originalMessage)); 103 | }); 104 | } 105 | else 106 | { 107 | ReceiveMessage?.Invoke(this, new MessageEventArgs(message)); 108 | } 109 | } 110 | } 111 | catch (ObjectDisposedException) 112 | { 113 | return; 114 | } 115 | catch (Exception e) 116 | { 117 | RaiseMessagerException(e); 118 | } 119 | 120 | BeginReceiveMessage(); 121 | } 122 | 123 | private static bool IsValidTcpMessage(Message message, out int port, out int bufferSize) 124 | { 125 | port = 0; 126 | bufferSize = 0; 127 | if (message.Value == null) 128 | return false; 129 | if (message.Type != MessageType.Tcp) 130 | return false; 131 | var parts = message.Value.Split(':'); 132 | if (parts.Length != 2) 133 | return false; 134 | if (!int.TryParse(parts[0], out port)) 135 | return false; 136 | return int.TryParse(parts[1], out bufferSize); 137 | } 138 | 139 | private void RaiseMessagerException(Exception e) 140 | { 141 | MessagerException?.Invoke(this, new ExceptionEventArgs(e)); 142 | } 143 | 144 | private static Message MessageFor(MessageType type, string value) 145 | { 146 | return new Message { Type = type, Value = value }; 147 | } 148 | 149 | public void SendMessage(IPEndPoint target, MessageType type, string value = "") 150 | { 151 | var message = MessageFor(type, value); 152 | var buffer = SerializeMessage(message); 153 | 154 | try 155 | { 156 | lock (_disposeLock) 157 | { 158 | if (_disposed) 159 | return; 160 | 161 | if (buffer.Length >= UdpSocket.BufferSize) 162 | { 163 | // switch to TCP mode to handle big messages 164 | var port = TcpListener.Queue(buffer); 165 | if (port > 0) 166 | { 167 | // success, replace original message with "switch to tcp" marker + port information + buffer length 168 | message = MessageFor(MessageType.Tcp, string.Concat(port, ':', buffer.Length)); 169 | buffer = SerializeMessage(message); 170 | } 171 | } 172 | 173 | _socket.BeginSendTo(buffer, 0, Math.Min(buffer.Length, UdpSocket.BufferSize), SocketFlags.None, target, SendMessageCallback, null); 174 | } 175 | } 176 | catch (SocketException se) 177 | { 178 | MessagerException?.Invoke(this, new ExceptionEventArgs(se)); 179 | } 180 | } 181 | 182 | private void SendMessageCallback(IAsyncResult result) 183 | { 184 | try 185 | { 186 | lock (_disposeLock) 187 | { 188 | if (_disposed) 189 | return; 190 | 191 | _socket.EndSendTo(result); 192 | } 193 | } 194 | catch (SocketException se) 195 | { 196 | MessagerException?.Invoke(this, new ExceptionEventArgs(se)); 197 | } 198 | catch (ObjectDisposedException) 199 | { 200 | } 201 | } 202 | 203 | private static byte[] SerializeMessage(Message message) 204 | { 205 | var serializer = new Serializer(); 206 | serializer.WriteInt32((int)message.Type); 207 | serializer.WriteString(message.Value); 208 | 209 | return serializer.Buffer(); 210 | } 211 | 212 | private static Message DeserializeMessage(byte[] buffer) 213 | { 214 | if (buffer.Length < 4) 215 | return null; 216 | 217 | var deserializer = new Deserializer(buffer); 218 | var type = (MessageType)deserializer.ReadInt32(); 219 | var value = deserializer.ReadString(); 220 | 221 | return new Message { Type = type, Value = value }; 222 | } 223 | 224 | public static Messager BindTo(int port) 225 | { 226 | return new Messager(port); 227 | } 228 | 229 | public void Dispose() 230 | { 231 | lock (_disposeLock) 232 | { 233 | _disposed = true; 234 | _socket.Close(); 235 | } 236 | } 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /Editor/Messaging/Messenger.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 66e2b0fc0f2800148be21743edd56c07 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/Messaging/Serializer.cs: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | using System.IO; 6 | using System.Text; 7 | 8 | namespace Microsoft.Unity.VisualStudio.Editor.Messaging 9 | { 10 | internal class Serializer 11 | { 12 | private readonly MemoryStream _stream; 13 | private readonly BinaryWriter _writer; 14 | 15 | public Serializer() 16 | { 17 | _stream = new MemoryStream(); 18 | _writer = new BinaryWriter(_stream); 19 | } 20 | 21 | public void WriteInt32(int i) 22 | { 23 | _writer.Write(i); 24 | } 25 | 26 | public void WriteString(string s) 27 | { 28 | var bytes = Encoding.UTF8.GetBytes(s ?? ""); 29 | if (bytes.Length > 0) 30 | { 31 | _writer.Write(bytes.Length); 32 | _writer.Write(bytes); 33 | } 34 | else 35 | _writer.Write(0); 36 | } 37 | 38 | public byte[] Buffer() 39 | { 40 | return _stream.ToArray(); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Editor/Messaging/Serializer.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 1903487170519b940925e4d60121d63b 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/Messaging/TcpClient.cs: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | using System; 6 | using System.Net; 7 | using System.Net.Sockets; 8 | using System.Threading; 9 | 10 | namespace Microsoft.Unity.VisualStudio.Editor.Messaging 11 | { 12 | internal class TcpClient 13 | { 14 | private const int ConnectOrReadTimeoutMilliseconds = 5000; 15 | 16 | private class State 17 | { 18 | public System.Net.Sockets.TcpClient TcpClient; 19 | public NetworkStream NetworkStream; 20 | public byte[] Buffer; 21 | public Action OnBufferAvailable; 22 | } 23 | 24 | public static void Queue(IPAddress address, int port, int bufferSize, Action onBufferAvailable) 25 | { 26 | var client = new System.Net.Sockets.TcpClient(); 27 | var state = new State {OnBufferAvailable = onBufferAvailable, TcpClient = client, Buffer = new byte[bufferSize]}; 28 | 29 | try 30 | { 31 | ThreadPool.QueueUserWorkItem(_ => 32 | { 33 | var handle = client.BeginConnect(address, port, OnClientConnected, state); 34 | if (!handle.AsyncWaitHandle.WaitOne(ConnectOrReadTimeoutMilliseconds)) 35 | Cleanup(state); 36 | }); 37 | } 38 | catch (Exception) 39 | { 40 | Cleanup(state); 41 | } 42 | } 43 | 44 | private static void OnClientConnected(IAsyncResult result) 45 | { 46 | var state = (State)result.AsyncState; 47 | 48 | try 49 | { 50 | state.TcpClient.EndConnect(result); 51 | state.NetworkStream = state.TcpClient.GetStream(); 52 | var handle = state.NetworkStream.BeginRead(state.Buffer, 0, state.Buffer.Length, OnEndRead, state); 53 | if (!handle.AsyncWaitHandle.WaitOne(ConnectOrReadTimeoutMilliseconds)) 54 | Cleanup(state); 55 | } 56 | catch (Exception) 57 | { 58 | Cleanup(state); 59 | } 60 | } 61 | 62 | private static void OnEndRead(IAsyncResult result) 63 | { 64 | var state = (State)result.AsyncState; 65 | 66 | try 67 | { 68 | var count = state.NetworkStream.EndRead(result); 69 | if (count == state.Buffer.Length) 70 | state.OnBufferAvailable?.Invoke(state.Buffer); 71 | } 72 | catch (Exception) 73 | { 74 | // Ignore and cleanup 75 | } 76 | finally 77 | { 78 | Cleanup(state); 79 | } 80 | } 81 | 82 | private static void Cleanup(State state) 83 | { 84 | state.NetworkStream?.Dispose(); 85 | state.TcpClient?.Close(); 86 | 87 | state.NetworkStream = null; 88 | state.TcpClient = null; 89 | state.Buffer = null; 90 | state.OnBufferAvailable = null; 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /Editor/Messaging/TcpClient.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e1eb5180dca1e6e4c80f16fe1e89ab5f 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/Messaging/TcpListener.cs: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | using System; 6 | using System.Net; 7 | using System.Threading; 8 | 9 | namespace Microsoft.Unity.VisualStudio.Editor.Messaging 10 | { 11 | internal class TcpListener 12 | { 13 | private const int ListenTimeoutMilliseconds = 5000; 14 | 15 | private class State 16 | { 17 | public System.Net.Sockets.TcpListener TcpListener; 18 | public byte[] Buffer; 19 | } 20 | 21 | public static int Queue(byte[] buffer) 22 | { 23 | var tcpListener = new System.Net.Sockets.TcpListener(IPAddress.Any, 0); 24 | var state = new State {Buffer = buffer, TcpListener = tcpListener}; 25 | 26 | try 27 | { 28 | tcpListener.Start(); 29 | 30 | int port = ((IPEndPoint)tcpListener.LocalEndpoint).Port; 31 | 32 | ThreadPool.QueueUserWorkItem(_ => 33 | { 34 | bool listening = true; 35 | 36 | while (listening) 37 | { 38 | var handle = tcpListener.BeginAcceptTcpClient(OnIncomingConnection, state); 39 | listening = handle.AsyncWaitHandle.WaitOne(ListenTimeoutMilliseconds); 40 | } 41 | 42 | Cleanup(state); 43 | }); 44 | 45 | return port; 46 | } 47 | catch (Exception) 48 | { 49 | Cleanup(state); 50 | return -1; 51 | } 52 | } 53 | 54 | private static void OnIncomingConnection(IAsyncResult result) 55 | { 56 | var state = (State)result.AsyncState; 57 | 58 | try 59 | { 60 | using (var client = state.TcpListener.EndAcceptTcpClient(result)) 61 | { 62 | using (var stream = client.GetStream()) 63 | { 64 | stream.Write(state.Buffer, 0, state.Buffer.Length); 65 | } 66 | } 67 | } 68 | catch (Exception) 69 | { 70 | // Ignore and cleanup 71 | } 72 | } 73 | 74 | private static void Cleanup(State state) 75 | { 76 | state.TcpListener?.Stop(); 77 | 78 | state.TcpListener = null; 79 | state.Buffer = null; 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Editor/Messaging/TcpListener.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 4aac9ec2e87e1df429254b46ea93ee54 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/Messaging/UdpSocket.cs: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | using System; 6 | using System.Net; 7 | using System.Net.Sockets; 8 | 9 | namespace Microsoft.Unity.VisualStudio.Editor.Messaging 10 | { 11 | internal class UdpSocket : Socket 12 | { 13 | // Maximum UDP payload is 65507 bytes. 14 | // TCP mode will be used when the payload is bigger than this BufferSize 15 | public const int BufferSize = 1024 * 8; 16 | 17 | internal UdpSocket() 18 | : base(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp) 19 | { 20 | SetIOControl(); 21 | } 22 | 23 | public void Bind(IPAddress address, int port = 0) 24 | { 25 | Bind(new IPEndPoint(address ?? IPAddress.Any, port)); 26 | } 27 | 28 | private void SetIOControl() 29 | { 30 | #if UNITY_EDITOR_WIN 31 | try 32 | { 33 | const int SIO_UDP_CONNRESET = -1744830452; 34 | 35 | IOControl(SIO_UDP_CONNRESET, new byte[] { 0 }, new byte[0]); 36 | } 37 | catch 38 | { 39 | // fallback 40 | } 41 | #endif 42 | } 43 | 44 | public static byte[] BufferFor(IAsyncResult result) 45 | { 46 | return (byte[])result.AsyncState; 47 | } 48 | 49 | public static EndPoint Any() 50 | { 51 | return new IPEndPoint(IPAddress.Any, 0); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Editor/Messaging/UdpSocket.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 982947b2e8aa7aa439af7f60a14251b8 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/Plugins.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 2267e5a8629ec3e4f954d2e5112e3294 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Editor/Plugins/AppleEventIntegration.bundle.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: acefa48680f354de1bfab8daa3d26916 3 | folderAsset: yes 4 | PluginImporter: 5 | externalObjects: {} 6 | serializedVersion: 2 7 | iconMap: {} 8 | executionOrder: {} 9 | defineConstraints: [] 10 | isPreloaded: 0 11 | isOverridable: 0 12 | isExplicitlyReferenced: 0 13 | validateReferences: 1 14 | platformData: 15 | - first: 16 | Any: 17 | second: 18 | enabled: 0 19 | settings: {} 20 | - first: 21 | Editor: Editor 22 | second: 23 | enabled: 1 24 | settings: 25 | DefaultValueInitialized: true 26 | - first: 27 | Standalone: OSXUniversal 28 | second: 29 | enabled: 1 30 | settings: 31 | CPU: AnyCPU 32 | userData: 33 | assetBundleName: 34 | assetBundleVariant: 35 | -------------------------------------------------------------------------------- /Editor/Plugins/AppleEventIntegration.bundle/Contents.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 79aba80d0afc348ce868931a58e6b8f6 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Editor/Plugins/AppleEventIntegration.bundle/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildMachineOSBuild 6 | 19G2021 7 | CFBundleDevelopmentRegion 8 | en 9 | CFBundleExecutable 10 | AppleEventIntegration 11 | CFBundleIdentifier 12 | com.unity.visualstudio.AppleEventIntegration 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | AppleEventIntegration 17 | CFBundlePackageType 18 | BNDL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSupportedPlatforms 22 | 23 | MacOSX 24 | 25 | CFBundleVersion 26 | 1 27 | DTCompiler 28 | com.apple.compilers.llvm.clang.1_0 29 | DTPlatformBuild 30 | 12B45b 31 | DTPlatformName 32 | macosx 33 | DTPlatformVersion 34 | 11.0 35 | DTSDKBuild 36 | 20A2408 37 | DTSDKName 38 | macosx11.0 39 | DTXcode 40 | 1220 41 | DTXcodeBuild 42 | 12B45b 43 | LSMinimumSystemVersion 44 | 10.13 45 | NSHumanReadableCopyright 46 | Copyright © 2019 Unity. All rights reserved. 47 | 48 | 49 | -------------------------------------------------------------------------------- /Editor/Plugins/AppleEventIntegration.bundle/Contents/Info.plist.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e62045032126d4c01b7e654924b1225b 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Editor/Plugins/AppleEventIntegration.bundle/Contents/MacOS.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 6093efd10ea204d6eaa82bac5462c5c1 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Editor/Plugins/AppleEventIntegration.bundle/Contents/MacOS/AppleEventIntegration: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liyingsong99/com.unity.ide.cursor-et/2ff42a4c32bdbe24a12d24762bf43da7cdccc76a/Editor/Plugins/AppleEventIntegration.bundle/Contents/MacOS/AppleEventIntegration -------------------------------------------------------------------------------- /Editor/Plugins/AppleEventIntegration.bundle/Contents/MacOS/AppleEventIntegration.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0bdddcfce7aa54eeb95d4b1710a978a7 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Editor/Plugins/AppleEventIntegration.bundle/Contents/_CodeSignature.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 67a21ddb5bbef4b868123014886593ee 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Editor/Plugins/AppleEventIntegration.bundle/Contents/_CodeSignature/CodeResources: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | files 6 | 7 | files2 8 | 9 | rules 10 | 11 | ^Resources/ 12 | 13 | ^Resources/.*\.lproj/ 14 | 15 | optional 16 | 17 | weight 18 | 1000 19 | 20 | ^Resources/.*\.lproj/locversion.plist$ 21 | 22 | omit 23 | 24 | weight 25 | 1100 26 | 27 | ^Resources/Base\.lproj/ 28 | 29 | weight 30 | 1010 31 | 32 | ^version.plist$ 33 | 34 | 35 | rules2 36 | 37 | .*\.dSYM($|/) 38 | 39 | weight 40 | 11 41 | 42 | ^(.*/)?\.DS_Store$ 43 | 44 | omit 45 | 46 | weight 47 | 2000 48 | 49 | ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/ 50 | 51 | nested 52 | 53 | weight 54 | 10 55 | 56 | ^.* 57 | 58 | ^Info\.plist$ 59 | 60 | omit 61 | 62 | weight 63 | 20 64 | 65 | ^PkgInfo$ 66 | 67 | omit 68 | 69 | weight 70 | 20 71 | 72 | ^Resources/ 73 | 74 | weight 75 | 20 76 | 77 | ^Resources/.*\.lproj/ 78 | 79 | optional 80 | 81 | weight 82 | 1000 83 | 84 | ^Resources/.*\.lproj/locversion.plist$ 85 | 86 | omit 87 | 88 | weight 89 | 1100 90 | 91 | ^Resources/Base\.lproj/ 92 | 93 | weight 94 | 1010 95 | 96 | ^[^/]+$ 97 | 98 | nested 99 | 100 | weight 101 | 10 102 | 103 | ^embedded\.provisionprofile$ 104 | 105 | weight 106 | 20 107 | 108 | ^version\.plist$ 109 | 110 | weight 111 | 20 112 | 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /Editor/Plugins/AppleEventIntegration.bundle/Contents/_CodeSignature/CodeResources.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ab18804e7a0584c00a8a9134901a725a 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Editor/ProcessRunner.cs: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | using System; 7 | using System.Diagnostics; 8 | using System.Text; 9 | using System.Threading; 10 | using System.Threading.Tasks; 11 | using System.Runtime.InteropServices; 12 | using System.ComponentModel; 13 | using Debug = UnityEngine.Debug; 14 | using System.IO; 15 | using SimpleJSON; 16 | using System.Collections.Generic; 17 | using System.Linq; 18 | 19 | namespace Microsoft.Unity.VisualStudio.Editor 20 | { 21 | internal class ProcessRunnerResult 22 | { 23 | public bool Success { get; set; } 24 | public string Output { get; set; } 25 | public string Error { get; set; } 26 | } 27 | 28 | internal static class ProcessRunner 29 | { 30 | public const int DefaultTimeoutInMilliseconds = 300000; 31 | 32 | public static ProcessStartInfo ProcessStartInfoFor(string filename, string arguments, bool redirect = true, bool shell = false) 33 | { 34 | return new ProcessStartInfo 35 | { 36 | UseShellExecute = shell, 37 | CreateNoWindow = true, 38 | RedirectStandardOutput = redirect, 39 | RedirectStandardError = redirect, 40 | FileName = filename, 41 | Arguments = arguments 42 | }; 43 | } 44 | 45 | public static void Start(string filename, string arguments) 46 | { 47 | Start(ProcessStartInfoFor(filename, arguments, false)); 48 | } 49 | 50 | public static void Start(ProcessStartInfo processStartInfo) 51 | { 52 | var process = new Process { StartInfo = processStartInfo }; 53 | 54 | using (process) 55 | { 56 | process.Start(); 57 | } 58 | } 59 | 60 | public static ProcessRunnerResult StartAndWaitForExit(string filename, string arguments, int timeoutms = DefaultTimeoutInMilliseconds, Action onOutputReceived = null) 61 | { 62 | return StartAndWaitForExit(ProcessStartInfoFor(filename, arguments), timeoutms, onOutputReceived); 63 | } 64 | 65 | public static ProcessRunnerResult StartAndWaitForExit(ProcessStartInfo processStartInfo, int timeoutms = DefaultTimeoutInMilliseconds, Action onOutputReceived = null) 66 | { 67 | var process = new Process { StartInfo = processStartInfo }; 68 | 69 | using (process) 70 | { 71 | var sbOutput = new StringBuilder(); 72 | var sbError = new StringBuilder(); 73 | 74 | var outputSource = new TaskCompletionSource(); 75 | var errorSource = new TaskCompletionSource(); 76 | 77 | process.OutputDataReceived += (_, e) => 78 | { 79 | Append(sbOutput, e.Data, outputSource); 80 | if (onOutputReceived != null && e.Data != null) 81 | onOutputReceived(e.Data); 82 | }; 83 | process.ErrorDataReceived += (_, e) => Append(sbError, e.Data, errorSource); 84 | 85 | process.Start(); 86 | process.BeginOutputReadLine(); 87 | process.BeginErrorReadLine(); 88 | 89 | var run = Task.Run(() => process.WaitForExit(timeoutms)); 90 | var processTask = Task.WhenAll(run, outputSource.Task, errorSource.Task); 91 | 92 | if (Task.WhenAny(Task.Delay(timeoutms), processTask).Result == processTask && run.Result) 93 | return new ProcessRunnerResult {Success = true, Error = sbError.ToString(), Output = sbOutput.ToString()}; 94 | 95 | try 96 | { 97 | process.Kill(); 98 | } 99 | catch 100 | { 101 | /* ignore */ 102 | } 103 | 104 | return new ProcessRunnerResult {Success = false, Error = sbError.ToString(), Output = sbOutput.ToString()}; 105 | } 106 | } 107 | 108 | private static void Append(StringBuilder sb, string data, TaskCompletionSource taskSource) 109 | { 110 | if (data == null) 111 | { 112 | taskSource.SetResult(true); 113 | return; 114 | } 115 | 116 | sb?.Append(data); 117 | } 118 | 119 | public static string[] GetProcessWorkspaces(Process process) 120 | { 121 | if (process == null) 122 | return null; 123 | 124 | try 125 | { 126 | var workspaces = new List(); 127 | var userProfile = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); 128 | var cursorStoragePath = Path.Combine(userProfile, "AppData", "Roaming", "cursor", "User", "workspaceStorage"); 129 | 130 | if (Directory.Exists(cursorStoragePath)) 131 | { 132 | foreach (var workspaceDir in Directory.GetDirectories(cursorStoragePath)) 133 | { 134 | try 135 | { 136 | var workspaceStatePath = Path.Combine(workspaceDir, "workspace.json"); 137 | if (File.Exists(workspaceStatePath)) 138 | { 139 | var content = File.ReadAllText(workspaceStatePath); 140 | if (!string.IsNullOrEmpty(content)) 141 | { 142 | var workspace = JSONNode.Parse(content); 143 | if (workspace != null) 144 | { 145 | var folder = workspace["folder"]; 146 | if (folder != null && !string.IsNullOrEmpty(folder.Value)) 147 | { 148 | var workspacePath = folder.Value; 149 | if (workspacePath.StartsWith("file:///")) 150 | { 151 | workspacePath = Uri.UnescapeDataString(workspacePath.Substring(8)); 152 | workspaces.Add(workspacePath); 153 | } 154 | } 155 | } 156 | } 157 | } 158 | 159 | var windowStatePath = Path.Combine(workspaceDir, "window.json"); 160 | if (File.Exists(windowStatePath)) 161 | { 162 | var content = File.ReadAllText(windowStatePath); 163 | if (!string.IsNullOrEmpty(content)) 164 | { 165 | var windowState = JSONNode.Parse(content); 166 | if (windowState != null) 167 | { 168 | var workspace = windowState["workspace"]; 169 | if (workspace != null && !string.IsNullOrEmpty(workspace.Value)) 170 | { 171 | var workspacePath = workspace.Value; 172 | if (workspacePath.StartsWith("file:///")) 173 | { 174 | workspacePath = Uri.UnescapeDataString(workspacePath.Substring(8)); 175 | workspaces.Add(workspacePath); 176 | } 177 | } 178 | } 179 | } 180 | } 181 | } 182 | catch (Exception ex) 183 | { 184 | Debug.LogWarning($"[Cursor] Error reading workspace state file: {ex.Message}"); 185 | continue; 186 | } 187 | } 188 | } 189 | 190 | return workspaces.Distinct().ToArray(); 191 | } 192 | catch (Exception ex) 193 | { 194 | Debug.LogError($"[Cursor] Error getting workspace directory: {ex.Message}"); 195 | return null; 196 | } 197 | } 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /Editor/ProcessRunner.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 35f2ac7e588f7f0488890c2eb8eb713b 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/ProjectGeneration.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 39f6c5735f4896746a1bcfa95716cdfb 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Editor/ProjectGeneration/AssemblyNameProvider.cs: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Unity Technologies. 3 | * Copyright (c) Microsoft Corporation. All rights reserved. 4 | * Licensed under the MIT License. See License.txt in the project root for license information. 5 | *--------------------------------------------------------------------------------------------*/ 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using UnityEditor; 10 | using UnityEditor.Compilation; 11 | using UnityEditor.PackageManager; 12 | 13 | namespace Microsoft.Unity.VisualStudio.Editor 14 | { 15 | public interface IAssemblyNameProvider 16 | { 17 | string[] ProjectSupportedExtensions { get; } 18 | string ProjectGenerationRootNamespace { get; } 19 | ProjectGenerationFlag ProjectGenerationFlag { get; } 20 | 21 | string GetAssemblyNameFromScriptPath(string path); 22 | string GetAssemblyName(string assemblyOutputPath, string assemblyName); 23 | bool IsInternalizedPackagePath(string path); 24 | IEnumerable GetAssemblies(Func shouldFileBePartOfSolution); 25 | IEnumerable GetAllAssetPaths(); 26 | UnityEditor.PackageManager.PackageInfo FindForAssetPath(string assetPath); 27 | ResponseFileData ParseResponseFile(string responseFilePath, string projectDirectory, string[] systemReferenceDirectories); 28 | void ToggleProjectGeneration(ProjectGenerationFlag preference); 29 | } 30 | 31 | public class AssemblyNameProvider : IAssemblyNameProvider 32 | { 33 | private readonly Dictionary m_PackageInfoCache = new Dictionary(); 34 | 35 | ProjectGenerationFlag m_ProjectGenerationFlag = (ProjectGenerationFlag)EditorPrefs.GetInt( 36 | "unity_project_generation_flag", 37 | (int)(ProjectGenerationFlag.Local | ProjectGenerationFlag.Embedded)); 38 | 39 | public string[] ProjectSupportedExtensions => EditorSettings.projectGenerationUserExtensions; 40 | 41 | public string ProjectGenerationRootNamespace => EditorSettings.projectGenerationRootNamespace; 42 | 43 | public ProjectGenerationFlag ProjectGenerationFlag 44 | { 45 | get { return ProjectGenerationFlagImpl; } 46 | private set { ProjectGenerationFlagImpl = value;} 47 | } 48 | 49 | internal virtual ProjectGenerationFlag ProjectGenerationFlagImpl 50 | { 51 | get => m_ProjectGenerationFlag; 52 | private set 53 | { 54 | EditorPrefs.SetInt("unity_project_generation_flag", (int)value); 55 | m_ProjectGenerationFlag = value; 56 | } 57 | } 58 | 59 | public string GetAssemblyNameFromScriptPath(string path) 60 | { 61 | return CompilationPipeline.GetAssemblyNameFromScriptPath(path); 62 | } 63 | 64 | internal static readonly string AssemblyOutput = @"Temp\bin\Debug\".NormalizePathSeparators(); 65 | internal static readonly string PlayerAssemblyOutput = @"Temp\bin\Debug\Player\".NormalizePathSeparators(); 66 | 67 | public IEnumerable GetAssemblies(Func shouldFileBePartOfSolution) 68 | { 69 | IEnumerable assemblies = GetAssembliesByType(AssembliesType.Editor, shouldFileBePartOfSolution, AssemblyOutput); 70 | 71 | if (!ProjectGenerationFlag.HasFlag(ProjectGenerationFlag.PlayerAssemblies)) 72 | { 73 | return assemblies; 74 | } 75 | var playerAssemblies = GetAssembliesByType(AssembliesType.Player, shouldFileBePartOfSolution, PlayerAssemblyOutput); 76 | return assemblies.Concat(playerAssemblies); 77 | } 78 | 79 | private static IEnumerable GetAssembliesByType(AssembliesType type, Func shouldFileBePartOfSolution, string outputPath) 80 | { 81 | foreach (var assembly in CompilationPipeline.GetAssemblies(type)) 82 | { 83 | if (assembly.sourceFiles.Any(shouldFileBePartOfSolution)) 84 | { 85 | yield return new Assembly( 86 | assembly.name, 87 | outputPath, 88 | assembly.sourceFiles, 89 | assembly.defines, 90 | assembly.assemblyReferences, 91 | assembly.compiledAssemblyReferences, 92 | assembly.flags, 93 | assembly.compilerOptions 94 | #if UNITY_2020_2_OR_NEWER 95 | , assembly.rootNamespace 96 | #endif 97 | ); 98 | } 99 | } 100 | } 101 | 102 | public string GetCompileOutputPath(string assemblyName) 103 | { 104 | // We need to keep this one for API surface check (AssemblyNameProvider is public), but not used anymore 105 | throw new NotImplementedException(); 106 | } 107 | 108 | public IEnumerable GetAllAssetPaths() 109 | { 110 | return AssetDatabase.GetAllAssetPaths(); 111 | } 112 | 113 | private static string ResolvePotentialParentPackageAssetPath(string assetPath) 114 | { 115 | const string packagesPrefix = "packages/"; 116 | if (!assetPath.StartsWith(packagesPrefix, StringComparison.OrdinalIgnoreCase)) 117 | { 118 | return null; 119 | } 120 | 121 | var followupSeparator = assetPath.IndexOf('/', packagesPrefix.Length); 122 | if (followupSeparator == -1) 123 | { 124 | return assetPath.ToLowerInvariant(); 125 | } 126 | 127 | return assetPath.Substring(0, followupSeparator).ToLowerInvariant(); 128 | } 129 | 130 | public UnityEditor.PackageManager.PackageInfo FindForAssetPath(string assetPath) 131 | { 132 | var parentPackageAssetPath = ResolvePotentialParentPackageAssetPath(assetPath); 133 | if (parentPackageAssetPath == null) 134 | { 135 | return null; 136 | } 137 | 138 | if (m_PackageInfoCache.TryGetValue(parentPackageAssetPath, out var cachedPackageInfo)) 139 | { 140 | return cachedPackageInfo; 141 | } 142 | 143 | var result = UnityEditor.PackageManager.PackageInfo.FindForAssetPath(parentPackageAssetPath); 144 | m_PackageInfoCache[parentPackageAssetPath] = result; 145 | return result; 146 | } 147 | 148 | public bool IsInternalizedPackagePath(string path) 149 | { 150 | if (string.IsNullOrEmpty(path.Trim())) 151 | { 152 | return false; 153 | } 154 | var packageInfo = FindForAssetPath(path); 155 | if (packageInfo == null) 156 | { 157 | return false; 158 | } 159 | var packageSource = packageInfo.source; 160 | switch (packageSource) 161 | { 162 | case PackageSource.Embedded: 163 | return !ProjectGenerationFlag.HasFlag(ProjectGenerationFlag.Embedded); 164 | case PackageSource.Registry: 165 | return !ProjectGenerationFlag.HasFlag(ProjectGenerationFlag.Registry); 166 | case PackageSource.BuiltIn: 167 | return !ProjectGenerationFlag.HasFlag(ProjectGenerationFlag.BuiltIn); 168 | case PackageSource.Unknown: 169 | return !ProjectGenerationFlag.HasFlag(ProjectGenerationFlag.Unknown); 170 | case PackageSource.Local: 171 | return !ProjectGenerationFlag.HasFlag(ProjectGenerationFlag.Local); 172 | case PackageSource.Git: 173 | return !ProjectGenerationFlag.HasFlag(ProjectGenerationFlag.Git); 174 | case PackageSource.LocalTarball: 175 | return !ProjectGenerationFlag.HasFlag(ProjectGenerationFlag.LocalTarBall); 176 | } 177 | 178 | return false; 179 | } 180 | 181 | public ResponseFileData ParseResponseFile(string responseFilePath, string projectDirectory, string[] systemReferenceDirectories) 182 | { 183 | return CompilationPipeline.ParseResponseFile( 184 | responseFilePath, 185 | projectDirectory, 186 | systemReferenceDirectories 187 | ); 188 | } 189 | 190 | public void ToggleProjectGeneration(ProjectGenerationFlag preference) 191 | { 192 | if (ProjectGenerationFlag.HasFlag(preference)) 193 | { 194 | ProjectGenerationFlag ^= preference; 195 | } 196 | else 197 | { 198 | ProjectGenerationFlag |= preference; 199 | } 200 | } 201 | 202 | internal void ResetPackageInfoCache() 203 | { 204 | m_PackageInfoCache.Clear(); 205 | } 206 | 207 | public void ResetProjectGenerationFlag() 208 | { 209 | ProjectGenerationFlag = ProjectGenerationFlag.None; 210 | } 211 | 212 | public string GetAssemblyName(string assemblyOutputPath, string assemblyName) 213 | { 214 | if (assemblyOutputPath == PlayerAssemblyOutput) 215 | return assemblyName + ".Player"; 216 | 217 | return assemblyName; 218 | } 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /Editor/ProjectGeneration/AssemblyNameProvider.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7c09dc04e9f01884b939d6c1087f95ed 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/ProjectGeneration/FileIOProvider.cs: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Unity Technologies. 3 | * Copyright (c) Microsoft Corporation. All rights reserved. 4 | * Licensed under the MIT License. See License.txt in the project root for license information. 5 | *--------------------------------------------------------------------------------------------*/ 6 | using System.IO; 7 | using System.Text; 8 | 9 | namespace Microsoft.Unity.VisualStudio.Editor 10 | { 11 | public interface IFileIO 12 | { 13 | bool Exists(string fileName); 14 | 15 | string ReadAllText(string fileName); 16 | void WriteAllText(string fileName, string content); 17 | } 18 | 19 | class FileIOProvider : IFileIO 20 | { 21 | public bool Exists(string fileName) 22 | { 23 | return File.Exists(fileName); 24 | } 25 | 26 | public string ReadAllText(string fileName) 27 | { 28 | return File.ReadAllText(fileName); 29 | } 30 | 31 | public void WriteAllText(string fileName, string content) 32 | { 33 | File.WriteAllText(fileName, content, Encoding.UTF8); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Editor/ProjectGeneration/FileIOProvider.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a78bf80420cfff243be0a089839e1214 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/ProjectGeneration/GUIDProvider.cs: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Unity Technologies. 3 | * Copyright (c) Microsoft Corporation. All rights reserved. 4 | * Licensed under the MIT License. See License.txt in the project root for license information. 5 | *--------------------------------------------------------------------------------------------*/ 6 | namespace Microsoft.Unity.VisualStudio.Editor 7 | { 8 | public interface IGUIDGenerator 9 | { 10 | string ProjectGuid(string projectName, string assemblyName); 11 | string SolutionGuid(string projectName, ScriptingLanguage scriptingLanguage); 12 | } 13 | 14 | class GUIDProvider : IGUIDGenerator 15 | { 16 | public string ProjectGuid(string projectName, string assemblyName) 17 | { 18 | return SolutionGuidGenerator.GuidForProject(projectName + assemblyName); 19 | } 20 | 21 | public string SolutionGuid(string projectName, ScriptingLanguage scriptingLanguage) 22 | { 23 | return SolutionGuidGenerator.GuidForSolution(projectName, scriptingLanguage); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Editor/ProjectGeneration/GUIDProvider.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 10c92ccf16d421947abfe28eb5247869 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/ProjectGeneration/LegacyStyleProjectGeneration.cs: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Unity Technologies. 3 | * Copyright (c) Microsoft Corporation. All rights reserved. 4 | * Licensed under the MIT License. See License.txt in the project root for license information. 5 | *--------------------------------------------------------------------------------------------*/ 6 | 7 | using System.Text; 8 | using UnityEditor.Compilation; 9 | 10 | namespace Microsoft.Unity.VisualStudio.Editor 11 | { 12 | 13 | internal class LegacyStyleProjectGeneration : ProjectGeneration 14 | { 15 | internal override string StyleName => "Legacy"; 16 | 17 | public LegacyStyleProjectGeneration(string tempDirectory, IAssemblyNameProvider assemblyNameProvider, IFileIO fileIoProvider, IGUIDGenerator guidGenerator) : base(tempDirectory, assemblyNameProvider, fileIoProvider, guidGenerator) 18 | { 19 | } 20 | 21 | public LegacyStyleProjectGeneration(string tempDirectory) : base(tempDirectory) 22 | { 23 | } 24 | 25 | public LegacyStyleProjectGeneration() 26 | { 27 | } 28 | 29 | internal override void GetProjectHeader(ProjectProperties properties, out StringBuilder headerBuilder) 30 | { 31 | headerBuilder = new StringBuilder(); 32 | 33 | //Header 34 | headerBuilder.Append(@"").Append(k_WindowsNewline); 35 | headerBuilder.Append($@"").Append(k_WindowsNewline); 36 | headerBuilder.Append(@" ").Append(k_WindowsNewline); 37 | headerBuilder.Append(@" ").Append(k_WindowsNewline); 38 | headerBuilder.Append(@" ").Append(properties.LangVersion).Append(@"").Append(k_WindowsNewline); 39 | headerBuilder.Append(@" ").Append(k_WindowsNewline); 40 | headerBuilder.Append(@" ").Append(k_WindowsNewline); 41 | headerBuilder.Append(@" Debug").Append(k_WindowsNewline); 42 | headerBuilder.Append(@" AnyCPU").Append(k_WindowsNewline); 43 | headerBuilder.Append(@" 10.0.20506").Append(k_WindowsNewline); 44 | headerBuilder.Append(@" 2.0").Append(k_WindowsNewline); 45 | headerBuilder.Append(@" ").Append(properties.RootNamespace).Append(@"").Append(k_WindowsNewline); 46 | headerBuilder.Append(@" {").Append(properties.ProjectGuid).Append(@"}").Append(k_WindowsNewline); 47 | headerBuilder.Append(@" Library").Append(k_WindowsNewline); 48 | headerBuilder.Append(@" Properties").Append(k_WindowsNewline); 49 | headerBuilder.Append(@" ").Append(properties.AssemblyName).Append(@"").Append(k_WindowsNewline); 50 | // In the end, given we use NoConfig/NoStdLib (see below), hardcoding the target framework version with the legacy format will have no impact, even when targeting netstandard/net48 from Unity. 51 | // And VSTU/Unity Game workload has a dependency towards net471 reference assemblies, so IDE will not complain that this specific SDK is not available. 52 | // Unity already selected proper API surface through referenced DLLs for us. 53 | headerBuilder.Append(@" v4.7.1").Append(k_WindowsNewline); 54 | headerBuilder.Append(@" 512").Append(k_WindowsNewline); 55 | headerBuilder.Append(@" .").Append(k_WindowsNewline); 56 | headerBuilder.Append(@" ").Append(k_WindowsNewline); 57 | 58 | GetProjectHeaderConfigurations(properties, headerBuilder); 59 | 60 | // Explicit references 61 | headerBuilder.Append(@" ").Append(k_WindowsNewline); 62 | headerBuilder.Append(@" true").Append(k_WindowsNewline); 63 | headerBuilder.Append(@" true").Append(k_WindowsNewline); 64 | headerBuilder.Append(@" false").Append(k_WindowsNewline); 65 | headerBuilder.Append(@" false").Append(k_WindowsNewline); 66 | headerBuilder.Append(@" false").Append(k_WindowsNewline); 67 | headerBuilder.Append(@" ").Append(k_WindowsNewline); 68 | 69 | GetProjectHeaderVstuFlavoring(properties, headerBuilder); 70 | GetProjectHeaderAnalyzers(properties, headerBuilder); 71 | } 72 | 73 | internal override void AppendProjectReference(Assembly assembly, Assembly reference, StringBuilder projectBuilder) 74 | { 75 | // If the current assembly is a Player project, we want to project-reference the corresponding Player project 76 | var referenceName = m_AssemblyNameProvider.GetAssemblyName(assembly.outputPath, reference.name); 77 | 78 | projectBuilder.Append(@" ").Append(k_WindowsNewline); 79 | projectBuilder.Append(" {").Append(ProjectGuid(referenceName)).Append("}").Append(k_WindowsNewline); 80 | projectBuilder.Append(" ").Append(referenceName).Append("").Append(k_WindowsNewline); 81 | projectBuilder.Append(" ").Append(k_WindowsNewline); 82 | } 83 | 84 | internal override void GetProjectFooter(StringBuilder footerBuilder) 85 | { 86 | footerBuilder.Append(string.Join(k_WindowsNewline, 87 | $" ", 88 | @" ", 89 | @" ", 96 | @"", 97 | @"")); 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /Editor/ProjectGeneration/LegacyStyleProjectGeneration.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d5201e27b238df341abb6ede328238d8 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/ProjectGeneration/ProjectGeneration.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e43e4f00a4b35e741b8d658b792c38e7 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/ProjectGeneration/ProjectGenerationFlag.cs: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Unity Technologies. 3 | * Copyright (c) Microsoft Corporation. All rights reserved. 4 | * Licensed under the MIT License. See License.txt in the project root for license information. 5 | *--------------------------------------------------------------------------------------------*/ 6 | using System; 7 | 8 | namespace Microsoft.Unity.VisualStudio.Editor 9 | { 10 | [Flags] 11 | public enum ProjectGenerationFlag 12 | { 13 | None = 0, 14 | Embedded = 1, 15 | Local = 2, 16 | Registry = 4, 17 | Git = 8, 18 | BuiltIn = 16, 19 | Unknown = 32, 20 | PlayerAssemblies = 64, 21 | LocalTarBall = 128, 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Editor/ProjectGeneration/ProjectGenerationFlag.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 56544889635e6154d8f3b295bdfeca38 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/ProjectGeneration/ProjectProperties.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Microsoft.Unity.VisualStudio.Editor 4 | { 5 | internal class ProjectProperties 6 | { 7 | public string ProjectGuid { get; set; } = string.Empty; 8 | public string LangVersion { get; set; } = "latest"; 9 | public string AssemblyName { get; set; } = string.Empty; 10 | public string RootNamespace { get; set; } = string.Empty; 11 | public string OutputPath { get; set; } = string.Empty; 12 | 13 | // Analyzers 14 | public string[] Analyzers { get; set; } = Array.Empty(); 15 | public string RulesetPath { get; set; } = string.Empty; 16 | public string AnalyzerConfigPath { get; set; } = string.Empty; 17 | // Source generators 18 | public string[] AdditionalFilePaths { get; set; } = Array.Empty(); 19 | 20 | // RSP alterable 21 | public string[] Defines { get; set; } = Array.Empty(); 22 | public bool Unsafe { get; set; } = false; 23 | 24 | // VSTU Flavouring 25 | public string FlavoringProjectType { get; set; } = string.Empty; 26 | public string FlavoringBuildTarget { get; set; } = string.Empty; 27 | public string FlavoringUnityVersion { get; set; } = string.Empty; 28 | public string FlavoringPackageVersion { get; set; } = string.Empty; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Editor/ProjectGeneration/ProjectProperties.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 124f613b45b45af49bc986c5cf48f93e 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/ProjectGeneration/SdkStyleProjectGeneration.cs: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Unity Technologies. 3 | * Copyright (c) Microsoft Corporation. All rights reserved. 4 | * Licensed under the MIT License. See License.txt in the project root for license information. 5 | *--------------------------------------------------------------------------------------------*/ 6 | 7 | using System; 8 | using System.IO; 9 | using System.Text; 10 | using UnityEditor.Compilation; 11 | using UnityEngine; 12 | 13 | namespace Microsoft.Unity.VisualStudio.Editor 14 | { 15 | internal class SdkStyleProjectGeneration : ProjectGeneration 16 | { 17 | internal override string StyleName => "SDK"; 18 | 19 | internal class SdkStyleAssemblyNameProvider : AssemblyNameProvider 20 | { 21 | // disable PlayerGeneration with SdkStyle projects 22 | internal override ProjectGenerationFlag ProjectGenerationFlagImpl => base.ProjectGenerationFlagImpl & ~ProjectGenerationFlag.PlayerAssemblies; 23 | } 24 | 25 | public SdkStyleProjectGeneration() : base( 26 | Directory.GetParent(Application.dataPath)?.FullName, 27 | new SdkStyleAssemblyNameProvider(), 28 | new FileIOProvider(), 29 | new GUIDProvider()) 30 | { 31 | } 32 | 33 | internal static readonly string[] SupportedCapabilities = new string[] 34 | { 35 | "Unity", 36 | }; 37 | 38 | internal static readonly string[] UnsupportedCapabilities = new string[] 39 | { 40 | "LaunchProfiles", 41 | "SharedProjectReferences", 42 | "ReferenceManagerSharedProjects", 43 | "ProjectReferences", 44 | "ReferenceManagerProjects", 45 | "COMReferences", 46 | "ReferenceManagerCOM", 47 | "AssemblyReferences", 48 | "ReferenceManagerAssemblies", 49 | }; 50 | 51 | internal override void GetProjectHeader(ProjectProperties properties, out StringBuilder headerBuilder) 52 | { 53 | headerBuilder = new StringBuilder(); 54 | 55 | headerBuilder.Append(@"").Append(k_WindowsNewline); 56 | headerBuilder.Append(@" ").Append(k_WindowsNewline); 57 | 58 | // Prevent circular dependency issues see https://github.com/microsoft/vscode-dotnettools/issues/401 59 | // We need a dedicated subfolder for each project in obj, else depending on the build order, nuget cache files could be overwritten 60 | // We need to do this before common.props, else we'll have a MSB3539 The value of the property "BaseIntermediateOutputPath" was modified after it was used by MSBuild 61 | headerBuilder.Append(@" ").Append(k_WindowsNewline); 62 | headerBuilder.Append($" {@"Temp\obj\$(Configuration)\$(MSBuildProjectName)".NormalizePathSeparators()}").Append(k_WindowsNewline); 63 | headerBuilder.Append(@" $(BaseIntermediateOutputPath)").Append(k_WindowsNewline); 64 | headerBuilder.Append(@" ").Append(k_WindowsNewline); 65 | 66 | // Supported capabilities 67 | GetCapabilityBlock(headerBuilder, "Sdk.props", "Include", SupportedCapabilities); 68 | 69 | headerBuilder.Append(@" ").Append(k_WindowsNewline); 70 | headerBuilder.Append(@" false").Append(k_WindowsNewline); 71 | headerBuilder.Append(@" false").Append(k_WindowsNewline); 72 | headerBuilder.Append(@" false").Append(k_WindowsNewline); 73 | headerBuilder.Append(@" ").Append(properties.LangVersion).Append(@"").Append(k_WindowsNewline); 74 | headerBuilder.Append(@" Debug;Release").Append(k_WindowsNewline); 75 | headerBuilder.Append(@" Debug").Append(k_WindowsNewline); 76 | headerBuilder.Append(@" AnyCPU").Append(k_WindowsNewline); 77 | headerBuilder.Append(@" ").Append(properties.RootNamespace).Append(@"").Append(k_WindowsNewline); 78 | headerBuilder.Append(@" Library").Append(k_WindowsNewline); 79 | headerBuilder.Append(@" Properties").Append(k_WindowsNewline); 80 | headerBuilder.Append(@" ").Append(properties.AssemblyName).Append(@"").Append(k_WindowsNewline); 81 | // In the end, given we use NoConfig/NoStdLib (see below), hardcoding the target framework version will have no impact, even when targeting netstandard/net48 from Unity. 82 | // But with SDK style we use netstandard2.1 (net471 for legacy), so 3rd party tools will not fail to work when .NETFW reference assemblies are not installed. 83 | // Unity already selected proper API surface through referenced DLLs for us. 84 | headerBuilder.Append(@" netstandard2.1").Append(k_WindowsNewline); 85 | headerBuilder.Append(@" .").Append(k_WindowsNewline); 86 | headerBuilder.Append(@" ").Append(k_WindowsNewline); 87 | 88 | GetProjectHeaderConfigurations(properties, headerBuilder); 89 | 90 | // Explicit references 91 | headerBuilder.Append(@" ").Append(k_WindowsNewline); 92 | headerBuilder.Append(@" true").Append(k_WindowsNewline); 93 | headerBuilder.Append(@" true").Append(k_WindowsNewline); 94 | headerBuilder.Append(@" true").Append(k_WindowsNewline); 95 | headerBuilder.Append(@" true").Append(k_WindowsNewline); 96 | headerBuilder.Append(@" MSB3277").Append(k_WindowsNewline); 97 | headerBuilder.Append(@" ").Append(k_WindowsNewline); 98 | 99 | GetProjectHeaderVstuFlavoring(properties, headerBuilder, false); 100 | GetProjectHeaderAnalyzers(properties, headerBuilder); 101 | } 102 | 103 | internal override void AppendProjectReference(Assembly assembly, Assembly reference, StringBuilder projectBuilder) 104 | { 105 | // If the current assembly is a Player project, we want to project-reference the corresponding Player project 106 | var referenceName = m_AssemblyNameProvider.GetAssemblyName(assembly.outputPath, reference.name); 107 | projectBuilder.Append(@" ").Append(k_WindowsNewline); 108 | } 109 | 110 | internal override void GetProjectFooter(StringBuilder footerBuilder) 111 | { 112 | // Unsupported capabilities 113 | GetCapabilityBlock(footerBuilder, "Sdk.targets", "Remove", UnsupportedCapabilities); 114 | 115 | footerBuilder.Append("").Append(k_WindowsNewline); 116 | } 117 | 118 | internal static void GetCapabilityBlock(StringBuilder footerBuilder, string import, string attribute, string[] capabilities) 119 | { 120 | footerBuilder.Append($@" ").Append(k_WindowsNewline); 121 | footerBuilder.Append(@" ").Append(k_WindowsNewline); 122 | foreach (var capability in capabilities) 123 | { 124 | footerBuilder.Append($@" ").Append(k_WindowsNewline); 125 | } 126 | footerBuilder.Append(@" ").Append(k_WindowsNewline); 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /Editor/ProjectGeneration/SdkStyleProjectGeneration.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c449403aa776a1d4daca10ff808d30de 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/SimpleJSON.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f14a73997ecfc3a49a8b5e46a49c4f4f 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/Solution.cs: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | namespace Microsoft.Unity.VisualStudio.Editor 6 | { 7 | internal class Solution 8 | { 9 | public SolutionProjectEntry[] Projects { get; set; } 10 | public SolutionProperties[] Properties { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Editor/Solution.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 27bc8a57b00ccec44945a7db9acf9b3b 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/SolutionParser.cs: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | using System.Collections.Generic; 6 | using System.Text.RegularExpressions; 7 | 8 | namespace Microsoft.Unity.VisualStudio.Editor 9 | { 10 | internal static class SolutionParser 11 | { 12 | // Compared to the bridge implementation, we are not returning "{" "}" from Guids 13 | private static readonly Regex ProjectDeclaration = new Regex(@"Project\(\""{(?.*?)}\""\)\s+=\s+\""(?.*?)\"",\s+\""(?.*?)\"",\s+\""{(?.*?)}\""(?.*?)\bEndProject\b", RegexOptions.Singleline | RegexOptions.ExplicitCapture | RegexOptions.Compiled); 14 | private static readonly Regex PropertiesDeclaration = new Regex(@"GlobalSection\((?([\w]+Properties|NestedProjects))\)\s+=\s+(?(?:post|pre)Solution)(?.*?)EndGlobalSection", RegexOptions.Singleline | RegexOptions.ExplicitCapture | RegexOptions.Compiled); 15 | private static readonly Regex PropertiesEntryDeclaration = new Regex(@"^\s*(?.*?)=(?.*?)$", RegexOptions.Multiline | RegexOptions.ExplicitCapture | RegexOptions.Compiled); 16 | 17 | public static Solution ParseSolutionFile(string filename, IFileIO fileIO) 18 | { 19 | return ParseSolutionContent(fileIO.ReadAllText(filename)); 20 | } 21 | 22 | public static Solution ParseSolutionContent(string content) 23 | { 24 | return new Solution 25 | { 26 | Projects = ParseSolutionProjects(content), 27 | Properties = ParseSolutionProperties(content) 28 | }; 29 | } 30 | 31 | private static SolutionProjectEntry[] ParseSolutionProjects(string content) 32 | { 33 | var projects = new List(); 34 | var mc = ProjectDeclaration.Matches(content); 35 | 36 | foreach (Match match in mc) 37 | { 38 | projects.Add(new SolutionProjectEntry 39 | { 40 | ProjectFactoryGuid = match.Groups["projectFactoryGuid"].Value, 41 | Name = match.Groups["name"].Value, 42 | FileName = match.Groups["fileName"].Value, 43 | ProjectGuid = match.Groups["projectGuid"].Value, 44 | Metadata = match.Groups["metadata"].Value 45 | }); 46 | } 47 | 48 | return projects.ToArray(); 49 | } 50 | 51 | private static SolutionProperties[] ParseSolutionProperties(string content) 52 | { 53 | var properties = new List(); 54 | var mc = PropertiesDeclaration.Matches(content); 55 | 56 | foreach (Match match in mc) 57 | { 58 | var sp = new SolutionProperties 59 | { 60 | Entries = new List>(), 61 | Name = match.Groups["name"].Value, 62 | Type = match.Groups["type"].Value 63 | }; 64 | 65 | var entries = match.Groups["entries"].Value; 66 | var mec = PropertiesEntryDeclaration.Matches(entries); 67 | foreach (Match entry in mec) 68 | { 69 | var key = entry.Groups["key"].Value.Trim(); 70 | var value = entry.Groups["value"].Value.Trim(); 71 | sp.Entries.Add(new KeyValuePair(key, value)); 72 | } 73 | 74 | properties.Add(sp); 75 | } 76 | 77 | return properties.ToArray(); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Editor/SolutionParser.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 2f5aa66aee329a04d9fc7805f908ae6a 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/SolutionProjectEntry.cs: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | using System; 6 | 7 | namespace Microsoft.Unity.VisualStudio.Editor 8 | { 9 | internal class SolutionProjectEntry 10 | { 11 | public string ProjectFactoryGuid { get; set; } 12 | public string Name { get; set; } 13 | public string FileName { get; set; } 14 | public string ProjectGuid { get; set; } 15 | public string Metadata { get; set; } 16 | 17 | public bool IsSolutionFolderProjectFactory() 18 | { 19 | return ProjectFactoryGuid != null && ProjectFactoryGuid.Equals("2150E333-8FDC-42A3-9474-1A3956D46DE8", StringComparison.OrdinalIgnoreCase); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Editor/SolutionProjectEntry.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 34cd2d7b80a45a344b72e4c60d1c22ba 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/SolutionProperties.cs: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | using System.Collections.Generic; 6 | 7 | namespace Microsoft.Unity.VisualStudio.Editor 8 | { 9 | internal class SolutionProperties 10 | { 11 | public string Name { get; set; } 12 | public IList> Entries { get; set; } 13 | public string Type { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Editor/SolutionProperties.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 53157d8310cfca940bb22bf101b2c275 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/Symbols.cs: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | using System; 6 | using System.IO; 7 | 8 | namespace Microsoft.Unity.VisualStudio.Editor 9 | { 10 | internal static class Symbols 11 | { 12 | public static bool IsPortableSymbolFile(string pdbFile) 13 | { 14 | try 15 | { 16 | using (var stream = File.OpenRead(pdbFile)) 17 | { 18 | return stream.ReadByte() == 'B' 19 | && stream.ReadByte() == 'S' 20 | && stream.ReadByte() == 'J' 21 | && stream.ReadByte() == 'B'; 22 | } 23 | } 24 | catch (Exception) 25 | { 26 | return false; 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Editor/Symbols.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 4f11f75003f55a8408825edd475b9204 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/Testing.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d83b86f0bc6c70b4ba02a9c9f24c865e 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Editor/Testing/TestAdaptor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | using UnityEditor.TestTools.TestRunner.Api; 4 | 5 | namespace Microsoft.Unity.VisualStudio.Editor.Testing 6 | { 7 | [Serializable] 8 | internal class TestAdaptorContainer 9 | { 10 | public TestAdaptor[] TestAdaptors; 11 | } 12 | 13 | [Serializable] 14 | internal class TestAdaptor 15 | { 16 | public string Id; 17 | public string Name; 18 | public string FullName; 19 | 20 | public string Type; 21 | public string Method; 22 | public string Assembly; 23 | 24 | public int Parent; 25 | 26 | public TestAdaptor(ITestAdaptor testAdaptor, int parent) 27 | { 28 | Id = testAdaptor.Id; 29 | Name = testAdaptor.Name; 30 | FullName = testAdaptor.FullName; 31 | 32 | Type = testAdaptor.TypeInfo?.FullName; 33 | Method = testAdaptor.Method?.Name; 34 | Assembly = testAdaptor.TypeInfo?.Assembly?.Location; 35 | 36 | Parent = parent; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Editor/Testing/TestAdaptor.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 606e2ff16916e884c904be431b4311f3 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/Testing/TestResultAdaptor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | using UnityEditor.TestTools.TestRunner.Api; 4 | 5 | namespace Microsoft.Unity.VisualStudio.Editor.Testing 6 | { 7 | [Serializable] 8 | internal class TestResultAdaptorContainer 9 | { 10 | public TestResultAdaptor[] TestResultAdaptors; 11 | } 12 | 13 | [Serializable] 14 | internal class TestResultAdaptor 15 | { 16 | public string Name; 17 | public string FullName; 18 | 19 | public int PassCount; 20 | public int FailCount; 21 | public int InconclusiveCount; 22 | public int SkipCount; 23 | 24 | public string ResultState; 25 | public string StackTrace; 26 | 27 | public TestStatusAdaptor TestStatus; 28 | 29 | public int Parent; 30 | 31 | public TestResultAdaptor(ITestResultAdaptor testResultAdaptor, int parent) 32 | { 33 | Name = testResultAdaptor.Name; 34 | FullName = testResultAdaptor.FullName; 35 | 36 | PassCount = testResultAdaptor.PassCount; 37 | FailCount = testResultAdaptor.FailCount; 38 | InconclusiveCount = testResultAdaptor.InconclusiveCount; 39 | SkipCount = testResultAdaptor.SkipCount; 40 | 41 | switch (testResultAdaptor.TestStatus) 42 | { 43 | case UnityEditor.TestTools.TestRunner.Api.TestStatus.Passed: 44 | TestStatus = TestStatusAdaptor.Passed; 45 | break; 46 | case UnityEditor.TestTools.TestRunner.Api.TestStatus.Skipped: 47 | TestStatus = TestStatusAdaptor.Skipped; 48 | break; 49 | case UnityEditor.TestTools.TestRunner.Api.TestStatus.Inconclusive: 50 | TestStatus = TestStatusAdaptor.Inconclusive; 51 | break; 52 | case UnityEditor.TestTools.TestRunner.Api.TestStatus.Failed: 53 | TestStatus = TestStatusAdaptor.Failed; 54 | break; 55 | } 56 | 57 | Parent = parent; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Editor/Testing/TestResultAdaptor.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: cd85aa8281e57264ebb6fc4ec8abae25 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/Testing/TestRunnerApiListener.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEditor; 3 | using UnityEditor.TestTools.TestRunner.Api; 4 | using UnityEngine; 5 | 6 | namespace Microsoft.Unity.VisualStudio.Editor.Testing 7 | { 8 | [InitializeOnLoad] 9 | internal class TestRunnerApiListener 10 | { 11 | private static readonly TestRunnerApi _testRunnerApi; 12 | private static readonly TestRunnerCallbacks _testRunnerCallbacks; 13 | 14 | static TestRunnerApiListener() 15 | { 16 | if (!VisualStudioEditor.IsEnabled) 17 | return; 18 | 19 | _testRunnerApi = ScriptableObject.CreateInstance(); 20 | _testRunnerCallbacks = new TestRunnerCallbacks(); 21 | 22 | _testRunnerApi.RegisterCallbacks(_testRunnerCallbacks); 23 | } 24 | 25 | public static void RetrieveTestList(string mode) 26 | { 27 | RetrieveTestList((TestMode) Enum.Parse(typeof(TestMode), mode)); 28 | } 29 | 30 | private static void RetrieveTestList(TestMode mode) 31 | { 32 | _testRunnerApi?.RetrieveTestList(mode, ta => _testRunnerCallbacks.TestListRetrieved(mode, ta)); 33 | } 34 | 35 | public static void ExecuteTests(string command) 36 | { 37 | // ExecuteTests format: 38 | // TestMode:FullName 39 | 40 | var index = command.IndexOf(':'); 41 | if (index < 0) 42 | return; 43 | 44 | var testMode = (TestMode)Enum.Parse(typeof(TestMode), command.Substring(0, index)); 45 | var filter = command.Substring(index + 1); 46 | 47 | ExecuteTests(new Filter { testMode = testMode, testNames = new [] { filter } }); 48 | } 49 | 50 | private static void ExecuteTests(Filter filter) 51 | { 52 | _testRunnerApi?.Execute(new ExecutionSettings(filter)); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Editor/Testing/TestRunnerApiListener.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a5f5f778f126f0542a5a9c3eb6ce9be4 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/Testing/TestRunnerCallbacks.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using UnityEditor.TestTools.TestRunner.Api; 4 | using UnityEngine; 5 | 6 | namespace Microsoft.Unity.VisualStudio.Editor.Testing 7 | { 8 | internal class TestRunnerCallbacks : ICallbacks 9 | { 10 | private string Serialize( 11 | TSource source, 12 | Func createAdaptor, 13 | Func> children, 14 | Func container) 15 | { 16 | var adaptors = new List(); 17 | 18 | void AddAdaptor(TSource item, int parentIndex) 19 | { 20 | var index = adaptors.Count; 21 | adaptors.Add(createAdaptor(item, parentIndex)); 22 | foreach (var child in children(item)) 23 | AddAdaptor(child, index); 24 | } 25 | 26 | AddAdaptor(source, -1); 27 | 28 | return JsonUtility.ToJson(container(adaptors.ToArray())); 29 | } 30 | 31 | private string Serialize(ITestAdaptor testAdaptor) 32 | { 33 | return Serialize( 34 | testAdaptor, 35 | (a, parentIndex) => new TestAdaptor(a, parentIndex), 36 | (a) => a.Children, 37 | (r) => new TestAdaptorContainer { TestAdaptors = r }); 38 | } 39 | 40 | private string Serialize(ITestResultAdaptor testResultAdaptor) 41 | { 42 | return Serialize( 43 | testResultAdaptor, 44 | (a, parentIndex) => new TestResultAdaptor(a, parentIndex), 45 | (a) => a.Children, 46 | (r) => new TestResultAdaptorContainer { TestResultAdaptors = r }); 47 | } 48 | 49 | public void RunFinished(ITestResultAdaptor testResultAdaptor) 50 | { 51 | VisualStudioIntegration.BroadcastMessage(Messaging.MessageType.RunFinished, Serialize(testResultAdaptor)); 52 | } 53 | 54 | public void RunStarted(ITestAdaptor testAdaptor) 55 | { 56 | VisualStudioIntegration.BroadcastMessage(Messaging.MessageType.RunStarted, Serialize(testAdaptor)); 57 | } 58 | 59 | public void TestFinished(ITestResultAdaptor testResultAdaptor) 60 | { 61 | VisualStudioIntegration.BroadcastMessage(Messaging.MessageType.TestFinished, Serialize(testResultAdaptor)); 62 | } 63 | 64 | public void TestStarted(ITestAdaptor testAdaptor) 65 | { 66 | VisualStudioIntegration.BroadcastMessage(Messaging.MessageType.TestStarted, Serialize(testAdaptor)); 67 | } 68 | 69 | private static string TestModeName(TestMode testMode) 70 | { 71 | switch (testMode) 72 | { 73 | case TestMode.EditMode: return "EditMode"; 74 | case TestMode.PlayMode: return "PlayMode"; 75 | } 76 | 77 | throw new ArgumentOutOfRangeException(); 78 | } 79 | 80 | 81 | internal void TestListRetrieved(TestMode testMode, ITestAdaptor testAdaptor) 82 | { 83 | // TestListRetrieved format: 84 | // TestMode:Json 85 | 86 | var value = TestModeName(testMode) + ":" + Serialize(testAdaptor); 87 | VisualStudioIntegration.BroadcastMessage(Messaging.MessageType.TestListRetrieved, value); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /Editor/Testing/TestRunnerCallbacks.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a2d33327b38d0dd45a27a726a560ab0f 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/Testing/TestStatusAdaptor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Microsoft.Unity.VisualStudio.Editor.Testing 4 | { 5 | [Serializable] 6 | internal enum TestStatusAdaptor 7 | { 8 | Passed, 9 | Skipped, 10 | Inconclusive, 11 | Failed, 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Editor/Testing/TestStatusAdaptor.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 14de30b850ab54040b195d012eef71fc 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/UnityInstallation.cs: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | using System; 7 | using UnityEditor; 8 | using UnityEditor.Compilation; 9 | 10 | namespace Microsoft.Unity.VisualStudio.Editor 11 | { 12 | internal static class UnityInstallation 13 | { 14 | public static bool IsMainUnityEditorProcess 15 | { 16 | get 17 | { 18 | #if UNITY_2020_2_OR_NEWER 19 | if (UnityEditor.AssetDatabase.IsAssetImportWorkerProcess()) 20 | return false; 21 | #elif UNITY_2019_3_OR_NEWER 22 | if (UnityEditor.Experimental.AssetDatabaseExperimental.IsAssetImportWorkerProcess()) 23 | return false; 24 | #endif 25 | 26 | #if UNITY_2021_1_OR_NEWER 27 | if (UnityEditor.MPE.ProcessService.level == UnityEditor.MPE.ProcessLevel.Secondary) 28 | return false; 29 | #elif UNITY_2020_2_OR_NEWER 30 | if (UnityEditor.MPE.ProcessService.level == UnityEditor.MPE.ProcessLevel.Slave) 31 | return false; 32 | #elif UNITY_2020_1_OR_NEWER 33 | if (global::Unity.MPE.ProcessService.level == global::Unity.MPE.ProcessLevel.UMP_SLAVE) 34 | return false; 35 | #endif 36 | 37 | return true; 38 | } 39 | } 40 | 41 | private static readonly Lazy _lazyIsInSafeMode = new Lazy(() => 42 | { 43 | // internal static extern bool isInSafeMode { get {} } 44 | var ieu = typeof(EditorUtility); 45 | var pinfo = ieu.GetProperty("isInSafeMode", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic); 46 | if (pinfo == null) 47 | return false; 48 | 49 | return Convert.ToBoolean(pinfo.GetValue(null)); 50 | }); 51 | public static bool IsInSafeMode => _lazyIsInSafeMode.Value; 52 | public static Version LatestLanguageVersionSupported(Assembly assembly) 53 | { 54 | #if UNITY_2020_2_OR_NEWER 55 | if (assembly?.compilerOptions != null && Version.TryParse(assembly.compilerOptions.LanguageVersion, out var result)) 56 | return result; 57 | 58 | // if parsing fails, we know at least we have support for 8.0 59 | return new Version(8, 0); 60 | #else 61 | return new Version(7, 3); 62 | #endif 63 | } 64 | 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Editor/UnityInstallation.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 795c5c262a77f1849970ef8abb2aea55 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/UsageUtility.cs: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | using System; 6 | using System.IO; 7 | using System.Linq; 8 | 9 | using UnityEngine; 10 | using UnityEditor; 11 | using UnityEditor.SceneManagement; 12 | using UnityEngine.SceneManagement; 13 | 14 | namespace Microsoft.Unity.VisualStudio.Editor 15 | { 16 | [Serializable] 17 | internal class FileUsage 18 | { 19 | public string Path; 20 | public string[] GameObjectPath; 21 | } 22 | 23 | internal static class UsageUtility 24 | { 25 | internal static void ShowUsage(string json) 26 | { 27 | try 28 | { 29 | var usage = JsonUtility.FromJson(json); 30 | ShowUsage(usage.Path, usage.GameObjectPath); 31 | } 32 | catch (Exception) 33 | { 34 | // ignore malformed request 35 | } 36 | } 37 | 38 | internal static void ShowUsage(string path, string[] gameObjectPath) 39 | { 40 | path = FileUtility.MakeRelativeToProjectPath(path); 41 | if (path == null) 42 | return; 43 | 44 | path = FileUtility.NormalizeWindowsToUnix(path); 45 | var extension = Path.GetExtension(path).ToLower(); 46 | 47 | EditorUtility.FocusProjectWindow(); 48 | 49 | switch (extension) 50 | { 51 | case ".unity": 52 | ShowSceneUsage(path, gameObjectPath); 53 | break; 54 | default: 55 | var asset = AssetDatabase.LoadMainAssetAtPath(path); 56 | Selection.activeObject = asset; 57 | EditorGUIUtility.PingObject(asset); 58 | break; 59 | } 60 | } 61 | 62 | private static void ShowSceneUsage(string scenePath, string[] gameObjectPath) 63 | { 64 | var scene = SceneManager.GetSceneByPath(scenePath.Replace(Path.DirectorySeparatorChar, '/')); 65 | if (!scene.isLoaded) 66 | { 67 | var result = EditorUtility.DisplayDialogComplex("Show Usage", 68 | $"Do you want to open \"{Path.GetFileName(scenePath)}\"?", 69 | "Open Scene", 70 | "Cancel", 71 | "Open Scene (additive)"); 72 | 73 | switch (result) 74 | { 75 | case 0: 76 | EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo(); 77 | scene = EditorSceneManager.OpenScene(scenePath, OpenSceneMode.Single); 78 | break; 79 | case 1: 80 | return; 81 | case 2: 82 | scene = EditorSceneManager.OpenScene(scenePath, OpenSceneMode.Additive); 83 | break; 84 | } 85 | } 86 | 87 | ShowSceneUsage(scene, gameObjectPath); 88 | } 89 | 90 | private static void ShowSceneUsage(Scene scene, string[] gameObjectPath) 91 | { 92 | if (gameObjectPath == null || gameObjectPath.Length == 0) 93 | return; 94 | 95 | var go = scene.GetRootGameObjects().FirstOrDefault(g => g.name == gameObjectPath[0]); 96 | if (go == null) 97 | return; 98 | 99 | for (var ni = 1; ni < gameObjectPath.Length; ni++) 100 | { 101 | var transform = go.transform; 102 | for (var i = 0; i < transform.childCount; i++) 103 | { 104 | var child = transform.GetChild(i); 105 | var childgo = child.gameObject; 106 | if (childgo.name == gameObjectPath[ni]) 107 | { 108 | go = childgo; 109 | break; 110 | } 111 | } 112 | } 113 | 114 | Selection.activeObject = go; 115 | EditorGUIUtility.PingObject(go); 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /Editor/UsageUtility.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 2b920fd559dbf4a4ba9d960411e3414e 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/VSWhere.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ab378124d52ce0f498e46271d915f588 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Editor/VSWhere/vswhere.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liyingsong99/com.unity.ide.cursor-et/2ff42a4c32bdbe24a12d24762bf43da7cdccc76a/Editor/VSWhere/vswhere.exe -------------------------------------------------------------------------------- /Editor/VSWhere/vswhere.exe.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0cc03a84149fbf24987d62822ee55a04 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Editor/VersionPair.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Microsoft.Unity.VisualStudio.Editor 4 | { 5 | internal struct VersionPair 6 | { 7 | public Version IdeVersion; 8 | public Version LanguageVersion; 9 | 10 | public VersionPair(int idemajor, int ideminor, int languageMajor, int languageMinor) 11 | { 12 | IdeVersion = new Version(idemajor, ideminor); 13 | LanguageVersion = new Version(languageMajor, languageMinor); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Editor/VersionPair.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 75ebc31926260bd4ab99ffc2dc72b327 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/VisualStudioCodiumInstallation.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 67fb09553bf91c34cb2e7b383a9907ba 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/VisualStudioCursorInstallation.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d83ff2bd8039e2f41a5147034ed5d28e 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/VisualStudioEditor.cs: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Unity Technologies. 3 | * Copyright (c) Microsoft Corporation. All rights reserved. 4 | * Licensed under the MIT License. See License.txt in the project root for license information. 5 | *--------------------------------------------------------------------------------------------*/ 6 | using System; 7 | using System.Collections.Generic; 8 | using System.IO; 9 | using System.Linq; 10 | using System.Runtime.CompilerServices; 11 | using UnityEditor; 12 | using UnityEngine; 13 | using Unity.CodeEditor; 14 | 15 | [assembly: InternalsVisibleTo("Unity.VisualStudio.EditorTests")] 16 | [assembly: InternalsVisibleTo("Unity.VisualStudio.Standalone.EditorTests")] 17 | [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] 18 | 19 | namespace Microsoft.Unity.VisualStudio.Editor 20 | { 21 | [InitializeOnLoad] 22 | public class VisualStudioEditor : IExternalCodeEditor 23 | { 24 | CodeEditor.Installation[] IExternalCodeEditor.Installations => _discoverInstallations 25 | .Result 26 | .Values 27 | .Select(v => v.ToCodeEditorInstallation()) 28 | .ToArray(); 29 | 30 | private static readonly AsyncOperation> _discoverInstallations; 31 | 32 | static VisualStudioEditor() 33 | { 34 | if (!UnityInstallation.IsMainUnityEditorProcess) 35 | return; 36 | 37 | Discovery.Initialize(); 38 | CodeEditor.Register(new VisualStudioEditor()); 39 | 40 | _discoverInstallations = AsyncOperation>.Run(DiscoverInstallations); 41 | } 42 | 43 | #if UNITY_2019_4_OR_NEWER && !UNITY_2020 44 | [InitializeOnLoadMethod] 45 | static void LegacyVisualStudioCodePackageDisabler() 46 | { 47 | // disable legacy Visual Studio Code packages 48 | var editor = CodeEditor.Editor.GetCodeEditorForPath("code.cmd"); 49 | if (editor == null) 50 | return; 51 | 52 | if (editor is VisualStudioEditor) 53 | return; 54 | 55 | // only disable the com.unity.ide.vscode package 56 | var assembly = editor.GetType().Assembly; 57 | var assemblyName = assembly.GetName().Name; 58 | if (assemblyName != "Unity.VSCode.Editor") 59 | return; 60 | 61 | CodeEditor.Unregister(editor); 62 | } 63 | #endif 64 | 65 | private static Dictionary DiscoverInstallations() 66 | { 67 | try 68 | { 69 | return Discovery 70 | .GetVisualStudioInstallations() 71 | .ToDictionary(i => Path.GetFullPath(i.Path), i => i); 72 | } 73 | catch (Exception ex) 74 | { 75 | Debug.LogError($"Error detecting Visual Studio installations: {ex}"); 76 | return new Dictionary(); 77 | } 78 | } 79 | 80 | internal static bool IsEnabled => CodeEditor.CurrentEditor is VisualStudioEditor && UnityInstallation.IsMainUnityEditorProcess; 81 | 82 | // this one seems legacy and not used anymore 83 | // keeping it for now given it is public, so we need a major bump to remove it 84 | public void CreateIfDoesntExist() 85 | { 86 | if (!TryGetVisualStudioInstallationForPath(CodeEditor.CurrentEditorInstallation, true, out var installation)) 87 | return; 88 | 89 | var generator = installation.ProjectGenerator; 90 | if (!generator.HasSolutionBeenGenerated()) 91 | generator.Sync(); 92 | } 93 | 94 | public void Initialize(string editorInstallationPath) 95 | { 96 | } 97 | 98 | internal virtual bool TryGetVisualStudioInstallationForPath(string editorPath, bool lookupDiscoveredInstallations, out IVisualStudioInstallation installation) 99 | { 100 | editorPath = Path.GetFullPath(editorPath); 101 | 102 | // lookup for well known installations 103 | if (lookupDiscoveredInstallations && _discoverInstallations.Result.TryGetValue(editorPath, out installation)) 104 | return true; 105 | 106 | return Discovery.TryDiscoverInstallation(editorPath, out installation); 107 | } 108 | 109 | public virtual bool TryGetInstallationForPath(string editorPath, out CodeEditor.Installation installation) 110 | { 111 | var result = TryGetVisualStudioInstallationForPath(editorPath, lookupDiscoveredInstallations: false, out var vsi); 112 | installation = vsi?.ToCodeEditorInstallation() ?? default; 113 | return result; 114 | } 115 | 116 | public void OnGUI() 117 | { 118 | GUILayout.BeginHorizontal(); 119 | GUILayout.FlexibleSpace(); 120 | 121 | if (!TryGetVisualStudioInstallationForPath(CodeEditor.CurrentEditorInstallation, true, out var installation)) 122 | return; 123 | 124 | var package = UnityEditor.PackageManager.PackageInfo.FindForAssembly(GetType().Assembly); 125 | 126 | var style = new GUIStyle 127 | { 128 | richText = true, 129 | margin = new RectOffset(0, 4, 0, 0) 130 | }; 131 | 132 | GUILayout.Label($"{package.displayName} v{package.version} enabled", style); 133 | GUILayout.EndHorizontal(); 134 | 135 | EditorGUILayout.LabelField("Generate .csproj files for:"); 136 | EditorGUI.indentLevel++; 137 | SettingsButton(ProjectGenerationFlag.Embedded, "Embedded packages", "", installation); 138 | SettingsButton(ProjectGenerationFlag.Local, "Local packages", "", installation); 139 | SettingsButton(ProjectGenerationFlag.Registry, "Registry packages", "", installation); 140 | SettingsButton(ProjectGenerationFlag.Git, "Git packages", "", installation); 141 | SettingsButton(ProjectGenerationFlag.BuiltIn, "Built-in packages", "", installation); 142 | SettingsButton(ProjectGenerationFlag.LocalTarBall, "Local tarball", "", installation); 143 | SettingsButton(ProjectGenerationFlag.Unknown, "Packages from unknown sources", "", installation); 144 | SettingsButton(ProjectGenerationFlag.PlayerAssemblies, "Player projects", "For each player project generate an additional csproj with the name 'project-player.csproj'", installation); 145 | RegenerateProjectFiles(installation); 146 | EditorGUI.indentLevel--; 147 | } 148 | 149 | private static void RegenerateProjectFiles(IVisualStudioInstallation installation) 150 | { 151 | var rect = EditorGUI.IndentedRect(EditorGUILayout.GetControlRect()); 152 | rect.width = 252; 153 | if (GUI.Button(rect, "Regenerate project files")) 154 | { 155 | installation.ProjectGenerator.Sync(); 156 | } 157 | } 158 | 159 | private static void SettingsButton(ProjectGenerationFlag preference, string guiMessage, string toolTip, IVisualStudioInstallation installation) 160 | { 161 | var generator = installation.ProjectGenerator; 162 | var prevValue = generator.AssemblyNameProvider.ProjectGenerationFlag.HasFlag(preference); 163 | 164 | var newValue = EditorGUILayout.Toggle(new GUIContent(guiMessage, toolTip), prevValue); 165 | if (newValue != prevValue) 166 | generator.AssemblyNameProvider.ToggleProjectGeneration(preference); 167 | } 168 | 169 | public void SyncIfNeeded(string[] addedFiles, string[] deletedFiles, string[] movedFiles, string[] movedFromFiles, string[] importedFiles) 170 | { 171 | if (TryGetVisualStudioInstallationForPath(CodeEditor.CurrentEditorInstallation, true, out var installation)) 172 | { 173 | installation.ProjectGenerator.SyncIfNeeded(addedFiles.Union(deletedFiles).Union(movedFiles).Union(movedFromFiles), importedFiles); 174 | } 175 | 176 | foreach (var file in importedFiles.Where(a => Path.GetExtension(a) == ".pdb")) 177 | { 178 | var pdbFile = FileUtility.GetAssetFullPath(file); 179 | 180 | // skip Unity packages like com.unity.ext.nunit 181 | if (pdbFile.IndexOf($"{Path.DirectorySeparatorChar}com.unity.", StringComparison.OrdinalIgnoreCase) > 0) 182 | continue; 183 | 184 | var asmFile = Path.ChangeExtension(pdbFile, ".dll"); 185 | if (!File.Exists(asmFile) || !Image.IsAssembly(asmFile)) 186 | continue; 187 | 188 | if (Symbols.IsPortableSymbolFile(pdbFile)) 189 | continue; 190 | 191 | Debug.LogWarning($"Unity is only able to load mdb or portable-pdb symbols. {file} is using a legacy pdb format."); 192 | } 193 | } 194 | 195 | public void SyncAll() 196 | { 197 | if (TryGetVisualStudioInstallationForPath(CodeEditor.CurrentEditorInstallation, true, out var installation)) 198 | { 199 | installation.ProjectGenerator.Sync(); 200 | } 201 | } 202 | 203 | private static bool IsSupportedPath(string path, IGenerator generator) 204 | { 205 | // Path is empty with "Open C# Project", as we only want to open the solution without specific files 206 | if (string.IsNullOrEmpty(path)) 207 | return true; 208 | 209 | // cs, uxml, uss, shader, compute, cginc, hlsl, glslinc, template are part of Unity builtin extensions 210 | // txt, xml, fnt, cd are -often- par of Unity user extensions 211 | // asdmdef is mandatory included 212 | return generator.IsSupportedFile(path); 213 | } 214 | 215 | public bool OpenProject(string path, int line, int column) 216 | { 217 | var editorPath = CodeEditor.CurrentEditorInstallation; 218 | 219 | if (!Discovery.TryDiscoverInstallation(editorPath, out var installation)) { 220 | Debug.LogWarning($"Visual Studio executable {editorPath} is not found. Please change your settings in Edit > Preferences > External Tools."); 221 | return false; 222 | } 223 | 224 | var generator = installation.ProjectGenerator; 225 | if (!IsSupportedPath(path, generator)) 226 | return false; 227 | 228 | if (!IsProjectGeneratedFor(path, generator, out var missingFlag)) 229 | Debug.LogWarning($"You are trying to open {path} outside a generated project. This might cause problems with IntelliSense and debugging. To avoid this, you can change your .csproj preferences in Edit > Preferences > External Tools and enable {GetProjectGenerationFlagDescription(missingFlag)} generation."); 230 | 231 | var solution = GetOrGenerateSolutionFile(generator); 232 | return installation.Open(path, line, column, solution); 233 | } 234 | 235 | private static string GetProjectGenerationFlagDescription(ProjectGenerationFlag flag) 236 | { 237 | switch (flag) 238 | { 239 | case ProjectGenerationFlag.BuiltIn: 240 | return "Built-in packages"; 241 | case ProjectGenerationFlag.Embedded: 242 | return "Embedded packages"; 243 | case ProjectGenerationFlag.Git: 244 | return "Git packages"; 245 | case ProjectGenerationFlag.Local: 246 | return "Local packages"; 247 | case ProjectGenerationFlag.LocalTarBall: 248 | return "Local tarball"; 249 | case ProjectGenerationFlag.PlayerAssemblies: 250 | return "Player projects"; 251 | case ProjectGenerationFlag.Registry: 252 | return "Registry packages"; 253 | case ProjectGenerationFlag.Unknown: 254 | return "Packages from unknown sources"; 255 | default: 256 | return string.Empty; 257 | } 258 | } 259 | 260 | private static bool IsProjectGeneratedFor(string path, IGenerator generator, out ProjectGenerationFlag missingFlag) 261 | { 262 | missingFlag = ProjectGenerationFlag.None; 263 | 264 | // No need to check when opening the whole solution 265 | if (string.IsNullOrEmpty(path)) 266 | return true; 267 | 268 | // We only want to check for cs scripts 269 | if (ProjectGeneration.ScriptingLanguageForFile(path) != ScriptingLanguage.CSharp) 270 | return true; 271 | 272 | // Even on windows, the package manager requires relative path + unix style separators for queries 273 | var basePath = generator.ProjectDirectory; 274 | var relativePath = path 275 | .NormalizeWindowsToUnix() 276 | .Replace(basePath, string.Empty) 277 | .Trim(FileUtility.UnixSeparator); 278 | 279 | var packageInfo = UnityEditor.PackageManager.PackageInfo.FindForAssetPath(relativePath); 280 | if (packageInfo == null) 281 | return true; 282 | 283 | var source = packageInfo.source; 284 | if (!Enum.TryParse(source.ToString(), out var flag)) 285 | return true; 286 | 287 | if (generator.AssemblyNameProvider.ProjectGenerationFlag.HasFlag(flag)) 288 | return true; 289 | 290 | // Return false if we found a source not flagged for generation 291 | missingFlag = flag; 292 | return false; 293 | } 294 | 295 | private static string GetOrGenerateSolutionFile(IGenerator generator) 296 | { 297 | generator.Sync(); 298 | return generator.SolutionFile(); 299 | } 300 | } 301 | } 302 | -------------------------------------------------------------------------------- /Editor/VisualStudioEditor.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ddfe5c11838c29047931abace68bf11e 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/VisualStudioInstallation.cs: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | using System; 6 | using System.IO; 7 | using Unity.CodeEditor; 8 | using IOPath = System.IO.Path; 9 | 10 | namespace Microsoft.Unity.VisualStudio.Editor 11 | { 12 | internal interface IVisualStudioInstallation 13 | { 14 | string Path { get; } 15 | bool SupportsAnalyzers { get; } 16 | Version LatestLanguageVersionSupported { get; } 17 | string[] GetAnalyzers(); 18 | CodeEditor.Installation ToCodeEditorInstallation(); 19 | bool Open(string path, int line, int column, string solutionPath); 20 | IGenerator ProjectGenerator { get; } 21 | void CreateExtraFiles(string projectDirectory); 22 | } 23 | 24 | internal abstract class VisualStudioInstallation : IVisualStudioInstallation 25 | { 26 | public string Name { get; set; } 27 | public string Path { get; set; } 28 | public Version Version { get; set; } 29 | public bool IsPrerelease { get; set; } 30 | 31 | public abstract bool SupportsAnalyzers { get; } 32 | public abstract Version LatestLanguageVersionSupported { get; } 33 | public abstract string[] GetAnalyzers(); 34 | public abstract IGenerator ProjectGenerator { get; } 35 | public abstract void CreateExtraFiles(string projectDirectory); 36 | public abstract bool Open(string path, int line, int column, string solutionPath); 37 | 38 | protected Version GetLatestLanguageVersionSupported(VersionPair[] versions) 39 | { 40 | if (versions != null) 41 | { 42 | foreach (var entry in versions) 43 | { 44 | if (Version >= entry.IdeVersion) 45 | return entry.LanguageVersion; 46 | } 47 | } 48 | 49 | // default to 7.0 50 | return new Version(7, 0); 51 | } 52 | 53 | protected static string[] GetAnalyzers(string path) 54 | { 55 | var analyzersDirectory = IOPath.GetFullPath(IOPath.Combine(path, "Analyzers")); 56 | 57 | if (Directory.Exists(analyzersDirectory)) 58 | return Directory.GetFiles(analyzersDirectory, "*Analyzers.dll", SearchOption.AllDirectories); 59 | 60 | return Array.Empty(); 61 | } 62 | 63 | public CodeEditor.Installation ToCodeEditorInstallation() 64 | { 65 | return new CodeEditor.Installation() { Name = Name, Path = Path }; 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Editor/VisualStudioInstallation.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c452f2dfc99f91b4c9883719d71b2d8f 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/VisualStudioIntegration.cs: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | using System; 6 | using System.Collections.Generic; 7 | using System.IO; 8 | using System.Linq; 9 | using System.Net; 10 | using System.Net.Sockets; 11 | using Microsoft.Unity.VisualStudio.Editor.Messaging; 12 | using Microsoft.Unity.VisualStudio.Editor.Testing; 13 | using UnityEditor; 14 | using UnityEngine; 15 | using MessageType = Microsoft.Unity.VisualStudio.Editor.Messaging.MessageType; 16 | 17 | namespace Microsoft.Unity.VisualStudio.Editor 18 | { 19 | [InitializeOnLoad] 20 | internal class VisualStudioIntegration 21 | { 22 | class Client 23 | { 24 | public IPEndPoint EndPoint { get; set; } 25 | public double LastMessage { get; set; } 26 | } 27 | 28 | private static Messager _messager; 29 | 30 | private static readonly Queue _incoming = new Queue(); 31 | private static readonly Dictionary _clients = new Dictionary(); 32 | private static readonly object _incomingLock = new object(); 33 | private static readonly object _clientsLock = new object(); 34 | 35 | static VisualStudioIntegration() 36 | { 37 | if (!VisualStudioEditor.IsEnabled) 38 | return; 39 | 40 | RunOnceOnUpdate(() => 41 | { 42 | // Despite using ReuseAddress|!ExclusiveAddressUse, we can fail here: 43 | // - if another application is using this port with exclusive access 44 | // - or if the firewall is not properly configured 45 | var messagingPort = MessagingPort(); 46 | 47 | try 48 | { 49 | _messager = Messager.BindTo(messagingPort); 50 | _messager.ReceiveMessage += ReceiveMessage; 51 | } 52 | catch (SocketException) 53 | { 54 | // We'll have a chance to try to rebind on next domain reload 55 | Debug.LogWarning($"Unable to use UDP port {messagingPort} for VS/Unity messaging. You should check if another process is already bound to this port or if your firewall settings are compatible."); 56 | } 57 | 58 | RunOnShutdown(Shutdown); 59 | }); 60 | 61 | EditorApplication.update += OnUpdate; 62 | 63 | CheckLegacyAssemblies(); 64 | } 65 | 66 | private static void CheckLegacyAssemblies() 67 | { 68 | var checkList = new HashSet(new[] { KnownAssemblies.UnityVS, KnownAssemblies.Messaging, KnownAssemblies.Bridge }); 69 | 70 | try 71 | { 72 | var assemblies = AppDomain 73 | .CurrentDomain 74 | .GetAssemblies() 75 | .Where(a => checkList.Contains(a.GetName().Name)); 76 | 77 | foreach (var assembly in assemblies) 78 | { 79 | // for now we only want to warn against local assemblies, do not check externals. 80 | var relativePath = FileUtility.MakeRelativeToProjectPath(assembly.Location); 81 | if (relativePath == null) 82 | continue; 83 | 84 | Debug.LogWarning($"Project contains legacy assembly that could interfere with the Visual Studio Package. You should delete {relativePath}"); 85 | } 86 | } 87 | catch (Exception) 88 | { 89 | // abandon legacy check 90 | } 91 | } 92 | 93 | private static void RunOnceOnUpdate(Action action) 94 | { 95 | var callback = null as EditorApplication.CallbackFunction; 96 | 97 | callback = () => 98 | { 99 | EditorApplication.update -= callback; 100 | action(); 101 | }; 102 | 103 | EditorApplication.update += callback; 104 | } 105 | 106 | private static void RunOnShutdown(Action action) 107 | { 108 | // Mono on OSX has all kinds of quirks on AppDomain shutdown 109 | #if UNITY_EDITOR_WIN 110 | AppDomain.CurrentDomain.DomainUnload += (_, __) => action(); 111 | #endif 112 | } 113 | 114 | private static int DebuggingPort() 115 | { 116 | return 56000 + (System.Diagnostics.Process.GetCurrentProcess().Id % 1000); 117 | } 118 | 119 | private static int MessagingPort() 120 | { 121 | return DebuggingPort() + 2; 122 | } 123 | 124 | private static void ReceiveMessage(object sender, MessageEventArgs args) 125 | { 126 | OnMessage(args.Message); 127 | } 128 | 129 | private static void OnUpdate() 130 | { 131 | lock (_incomingLock) 132 | { 133 | while (_incoming.Count > 0) 134 | { 135 | ProcessIncoming(_incoming.Dequeue()); 136 | } 137 | } 138 | 139 | lock (_clientsLock) 140 | { 141 | foreach (var client in _clients.Values.ToArray()) 142 | { 143 | // EditorApplication.timeSinceStartup: The time since the editor was started, in seconds, not reset when starting play mode. 144 | if (EditorApplication.timeSinceStartup - client.LastMessage > 4) 145 | _clients.Remove(client.EndPoint); 146 | } 147 | } 148 | } 149 | 150 | private static void AddMessage(Message message) 151 | { 152 | lock (_incomingLock) 153 | { 154 | _incoming.Enqueue(message); 155 | } 156 | } 157 | 158 | private static void ProcessIncoming(Message message) 159 | { 160 | lock (_clientsLock) 161 | { 162 | CheckClient(message); 163 | } 164 | 165 | switch (message.Type) 166 | { 167 | case MessageType.Ping: 168 | Answer(message, MessageType.Pong); 169 | break; 170 | case MessageType.Play: 171 | Shutdown(); 172 | EditorApplication.isPlaying = true; 173 | break; 174 | case MessageType.Stop: 175 | EditorApplication.isPlaying = false; 176 | break; 177 | case MessageType.Pause: 178 | EditorApplication.isPaused = true; 179 | break; 180 | case MessageType.Unpause: 181 | EditorApplication.isPaused = false; 182 | break; 183 | case MessageType.Build: 184 | // Not used anymore 185 | break; 186 | case MessageType.Refresh: 187 | Refresh(); 188 | break; 189 | case MessageType.Version: 190 | Answer(message, MessageType.Version, PackageVersion()); 191 | break; 192 | case MessageType.UpdatePackage: 193 | // Not used anymore 194 | break; 195 | case MessageType.ProjectPath: 196 | Answer(message, MessageType.ProjectPath, Path.GetFullPath(Path.Combine(Application.dataPath, ".."))); 197 | break; 198 | case MessageType.ExecuteTests: 199 | TestRunnerApiListener.ExecuteTests(message.Value); 200 | break; 201 | case MessageType.RetrieveTestList: 202 | TestRunnerApiListener.RetrieveTestList(message.Value); 203 | break; 204 | case MessageType.ShowUsage: 205 | UsageUtility.ShowUsage(message.Value); 206 | break; 207 | } 208 | } 209 | 210 | private static void CheckClient(Message message) 211 | { 212 | var endPoint = message.Origin; 213 | 214 | if (!_clients.TryGetValue(endPoint, out var client)) 215 | { 216 | client = new Client 217 | { 218 | EndPoint = endPoint, 219 | LastMessage = EditorApplication.timeSinceStartup 220 | }; 221 | 222 | _clients.Add(endPoint, client); 223 | } 224 | else 225 | { 226 | client.LastMessage = EditorApplication.timeSinceStartup; 227 | } 228 | } 229 | 230 | internal static string PackageVersion() 231 | { 232 | var package = UnityEditor.PackageManager.PackageInfo.FindForAssembly(typeof(VisualStudioIntegration).Assembly); 233 | return package.version; 234 | } 235 | 236 | private static void Refresh() 237 | { 238 | // If the user disabled auto-refresh in Unity, do not try to force refresh the Asset database 239 | if (!EditorPrefs.GetBool("kAutoRefresh", true)) 240 | return; 241 | 242 | if (UnityInstallation.IsInSafeMode) 243 | return; 244 | 245 | RunOnceOnUpdate(AssetDatabase.Refresh); 246 | } 247 | 248 | private static void OnMessage(Message message) 249 | { 250 | AddMessage(message); 251 | } 252 | 253 | private static void Answer(Client client, MessageType answerType, string answerValue) 254 | { 255 | Answer(client.EndPoint, answerType, answerValue); 256 | } 257 | 258 | private static void Answer(Message message, MessageType answerType, string answerValue = "") 259 | { 260 | var targetEndPoint = message.Origin; 261 | 262 | Answer( 263 | targetEndPoint, 264 | answerType, 265 | answerValue); 266 | } 267 | 268 | private static void Answer(IPEndPoint targetEndPoint, MessageType answerType, string answerValue) 269 | { 270 | _messager?.SendMessage(targetEndPoint, answerType, answerValue); 271 | } 272 | 273 | private static void Shutdown() 274 | { 275 | if (_messager == null) 276 | return; 277 | 278 | _messager.ReceiveMessage -= ReceiveMessage; 279 | _messager.Dispose(); 280 | _messager = null; 281 | } 282 | 283 | internal static void BroadcastMessage(MessageType type, string value) 284 | { 285 | lock (_clientsLock) 286 | { 287 | foreach (var client in _clients.Values.ToArray()) 288 | { 289 | Answer(client, type, value); 290 | } 291 | } 292 | } 293 | } 294 | } 295 | -------------------------------------------------------------------------------- /Editor/VisualStudioIntegration.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b13003ee9d8f4aa4eadd98d24adbc79b 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/com.unity.ide.visualstudio.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Unity.Cursor.Editor", 3 | "references": [], 4 | "includePlatforms": [ 5 | "Editor" 6 | ], 7 | "excludePlatforms": [], 8 | "allowUnsafeCode": false, 9 | "overrideReferences": true, 10 | "precompiledReferences": [ 11 | "Newtonsoft.Json.dll" 12 | ], 13 | "autoReferenced": true, 14 | "defineConstraints": [], 15 | "versionDefines": [], 16 | "noEngineReferences": false 17 | } -------------------------------------------------------------------------------- /Editor/com.unity.ide.visualstudio.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 3bdbd29a5ae7b9d4ead62ff67cdd7d57 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Unity Technologies 4 | Copyright (c) 2019 Microsoft Corporation. All rights reserved. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /LICENSE.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e1e01663a79c63e45b71da40a22e6954 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # How to install 2 |
3 | - Unity->Window->Package Manager
4 | - Click "+" left corner
5 | - Add package from git URL
6 | - Insert https://github.com/liyingsong99/com.unity.ide.cursor-et.git
7 | - Add
8 | - Done 9 | 10 | # 关于Cursor中的插件 11 | - C# Dev Kit 推荐使用v1.18.23 版本,并且禁止自动更新。 12 | - C# 推荐使用v2.72.27版本,同样禁止自动更新。 13 | - Unity 插件安装最新版本即可。 14 | 15 | # 关于ET框架适配内容 16 | - 适配了ET.sln解决方案 17 | - 添加了ET框架的rule参考,最好自己手动添加rule拓展内容,并复制到Cursor的默认Rule中。 18 | -------------------------------------------------------------------------------- /README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e06b3ec6cb984424bbf989b37579c2e2 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /ThirdPartyNotices.md: -------------------------------------------------------------------------------- 1 | This package contains third-party software components governed by the license(s) indicated below: 2 | --------- 3 | 4 | Component Name: VSWhere 5 | 6 | License Type: "MIT" 7 | 8 | The MIT License (MIT) 9 | Copyright (C) Microsoft Corporation. All rights reserved. 10 | 11 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | 17 | --------- 18 | Component Name: benbuck/EnvDTE 19 | 20 | License Type: Zero-Clause BSD 21 | 22 | Permission to use, copy, modify, and/or distribute this software for any purpose 23 | with or without fee is hereby granted. 24 | 25 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 26 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND 27 | FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 28 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS 29 | OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER 30 | TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF 31 | THIS SOFTWARE. -------------------------------------------------------------------------------- /ThirdPartyNotices.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 613aeddc44f353d4d9e9986919e730b9 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /ValidationConfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "FileContentKeywordValidation": 3 | { 4 | "Keywords": 5 | [ 6 | { 7 | "Targets": "+", 8 | "Files": 9 | [ 10 | "Editor/VisualStudioForWindowsInstallation.cs" 11 | ], 12 | "Patterns": 13 | [ 14 | "vswhere\\.exe" 15 | ] 16 | } 17 | ] 18 | } 19 | } -------------------------------------------------------------------------------- /ValidationConfig.json.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 8e9d2159475628940bb30bf1ca8c5432 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /ValidationExceptions.json: -------------------------------------------------------------------------------- 1 | { 2 | "ErrorExceptions": [ 3 | { 4 | "ValidationTest": "API Validation", 5 | "ExceptionMessage": "Breaking changes require a new major version.", 6 | "PackageVersion": "2.0.18" 7 | }, 8 | { 9 | "ValidationTest": "API Validation", 10 | "ExceptionMessage": "Additions require a new minor or major version.", 11 | "PackageVersion": "2.0.18" 12 | } 13 | ], 14 | "WarningExceptions": [] 15 | } -------------------------------------------------------------------------------- /ValidationExceptions.json.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e71f2385c737b5249a357a30c44ba7cb 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.boxqkrtm.ide.cursor", 3 | "displayName": "Cursor Editor", 4 | "description": "Cursor editor integration for supporting Cursor as code editor for unity. Adds support for generating csproj files for intellisense purposes, auto discovery of installations, etc.", 5 | "version": "2.0.24", 6 | "unity": "2019.4", 7 | "unityRelease": "25f1", 8 | "dependencies": { 9 | "com.unity.test-framework": "1.1.9" 10 | }, 11 | "_upm": { 12 | "changelog": "Integration:\n\n- Add support for Cursor" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /package.json.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0214c79dfec0ce44486e7d7e8d9b981b 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /rules/etrules.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | globs: 4 | alwaysApply: true 5 | --- 6 | Always respond in 中文 7 | 8 | # 文件创建规则 9 | -- Packages/cn.etetet.* 路径下创建的所有非静态类的代码文件,默认都为Entity组件 10 | -- 创建的所有类文件,都必须创建到Packages/cn.etetet.*目录下,*号代表具体的包名,例如core包代表着Packages/cn.etetet.core 11 | -- Entity 文件需要创建到Scritps/Model或Scritps/ModelView文件夹下, 并且必须继承Entity类和IAwake接口,至于其他接口,则根据需要添加 12 | -- Entity 类不允许有任何方法,其方法必须在对应的System类中以静态拓展函数的方式实现 13 | -- Entity 对应的System类需要创建到Scritps/Hotfix或Scritps/HotfixView文件夹下,必须为静态类且包含partial关键字,同时默认需要添加[EntitySystemOf(typeof(*))]标签,以及Awake生命周期函数 14 | -- 所有创建的类,其NameSpace默认都为ET或ET.Client或ET.Server 15 | -- 当创建entity到*包下时,需要分析该组件属于Model、ModelView、Hotfix、HotfixView中的某两个程序集,然后自动添加到相应的路径下,例如Packages/cn.etetet.* /Scripts/Model 及 Packages/cn.etetet.* /Scripts/Hotfix目录下 -------------------------------------------------------------------------------- /rules/etrules.mdc.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e6aac6cb208eaa04482b0764d486045e 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | --------------------------------------------------------------------------------