├── img ├── how-to-use-1.png ├── how-to-use-2.png ├── how-to-use-3.png ├── how-to-use-4.png ├── how-to-use-5.png ├── rider-monogame-cover.pdn ├── rider-monogame-cover.png ├── rider-plus-monogame-128.png └── rider-plus-monogame-200.png ├── test └── MonoGameSolution │ ├── Desktop381 │ ├── Program.cs │ ├── Icon.bmp │ ├── Icon.ico │ ├── Content │ │ ├── Content.mgcb │ │ └── File.fx │ ├── .config │ │ └── dotnet-tools.json │ ├── Desktop381.csproj │ ├── Game1.cs │ └── app.manifest │ ├── Desktop38 │ ├── Icon.bmp │ ├── Icon.ico │ ├── Program.cs │ ├── Content │ │ └── Content.mgcb │ ├── Desktop38.csproj │ ├── Game1.cs │ └── app.manifest │ ├── .config │ └── dotnet-tools.json │ └── MonoGameSolution.sln ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── src ├── rider │ └── main │ │ ├── resources │ │ ├── icons │ │ │ ├── fx.png │ │ │ ├── mgcb.png │ │ │ ├── mgcb@2x.png │ │ │ ├── mgcb_dark.png │ │ │ ├── spritefont.png │ │ │ └── mgcb@2x_dark.png │ │ ├── fileTemplates │ │ │ ├── Content Builder File.mgcb.ft │ │ │ ├── Sprite Effect File.fx.ft │ │ │ ├── Effect File.fx.ft │ │ │ ├── SpriteFont File.spritefont.ft │ │ │ └── Localized SpriteFont File.spritefont.ft │ │ ├── META-INF │ │ │ ├── pluginIcon.svg │ │ │ └── plugin.xml │ │ ├── messages │ │ │ └── MonoGameUiBundle.properties │ │ └── xmlSchema │ │ │ └── SpriteFont.xsd │ │ └── kotlin │ │ └── me │ │ └── seclerp │ │ └── rider │ │ ├── plugins │ │ └── monogame │ │ │ ├── KnownNotificationGroups.kt │ │ │ ├── mgcb │ │ │ ├── previewer │ │ │ │ ├── properties │ │ │ │ │ ├── MgcbProperty.kt │ │ │ │ │ ├── MgcbPropertyValueColumn.kt │ │ │ │ │ ├── MgcbPropertyTableModel.kt │ │ │ │ │ ├── MgcbPropertyTable.kt │ │ │ │ │ ├── MgcbPropertyKeyColumn.kt │ │ │ │ │ ├── MgcbPropertyColumn.kt │ │ │ │ │ └── MgcbPropertyRenderer.kt │ │ │ │ ├── tree │ │ │ │ │ ├── MgcbFolderNode.kt │ │ │ │ │ ├── MgcbTreeNode.kt │ │ │ │ │ ├── MgcbBuildEntryNode.kt │ │ │ │ │ ├── MgcbNodeRenderer.kt │ │ │ │ │ └── MgcbTree.kt │ │ │ │ ├── MgcbEditorNotificationPanel.kt │ │ │ │ ├── listeners │ │ │ │ │ ├── MgcbPendingUpdateListener.kt │ │ │ │ │ ├── MgcbProcessedUpdateListener.kt │ │ │ │ │ ├── MgcbFileListener.kt │ │ │ │ │ ├── MgcbDocumentListener.kt │ │ │ │ │ └── MgcbPendingUpdateListenerImpl.kt │ │ │ │ ├── MgcbPreviewerTopics.kt │ │ │ │ ├── services │ │ │ │ │ ├── MgcbAnalyzer.kt │ │ │ │ │ └── MgcbBuildTreeManager.kt │ │ │ │ ├── MgcbEditorWithPreview.kt │ │ │ │ ├── MgcbEditorProvider.kt │ │ │ │ ├── MgcbEditorFloatingToolbarProvider.kt │ │ │ │ ├── MgcbModel.kt │ │ │ │ ├── MgcbModelBuilderVisitor.kt │ │ │ │ └── MgcbEditorPreviewer.kt │ │ │ ├── MgcbLexerAdapter.kt │ │ │ ├── MgcbLanguage.kt │ │ │ ├── actions │ │ │ │ ├── commands │ │ │ │ │ ├── RegisterMgcbEditorCommand.kt │ │ │ │ │ ├── CliCommandResult.kt │ │ │ │ │ ├── InstallMgcbEditorCommand.kt │ │ │ │ │ ├── CheckMgcbEditorInstalledCommand.kt │ │ │ │ │ ├── MgcbEditorCommand.kt │ │ │ │ │ ├── Extensions.kt │ │ │ │ │ ├── MgcbProcessAdapter.kt │ │ │ │ │ └── CliCommand.kt │ │ │ │ └── OpenExternalEditorAction.kt │ │ │ ├── psi │ │ │ │ ├── MgcbPsiImplUtil.kt │ │ │ │ ├── MgcbTokenType.kt │ │ │ │ ├── MgcbElementType.kt │ │ │ │ └── MgcbFile.kt │ │ │ ├── MgcbSyntaxHighlighterFactory.kt │ │ │ ├── toolset │ │ │ │ ├── MgcbResolvedTool.kt │ │ │ │ ├── MgcbToolsetState.kt │ │ │ │ ├── DotNetToolsVersion.kt │ │ │ │ └── MgcbToolsetHost.kt │ │ │ ├── MgcbFileType.kt │ │ │ ├── state │ │ │ │ └── MgcbEditorGlobalState.kt │ │ │ ├── Mgcb.bnf │ │ │ ├── MgcbParserDefinition.kt │ │ │ ├── settings │ │ │ │ └── MgcbColorSettingsPage.kt │ │ │ ├── Mgcb.flex │ │ │ ├── MgcbCompletionContributor.kt │ │ │ └── MgcbSyntaxHighlighter.kt │ │ │ ├── templates │ │ │ ├── MonoGameProjectTemplateCustomizer.kt │ │ │ ├── MonoGameTemplateMetadata.kt │ │ │ ├── MonoGameTemplateType.kt │ │ │ └── MonoGameProjectTemplateGenerator.kt │ │ │ ├── MonoGameIcons.kt │ │ │ ├── effect │ │ │ └── EffectFileType.kt │ │ │ ├── spritefont │ │ │ ├── SpriteFontFileType.kt │ │ │ └── SpriteFontXmlSchemaProvider.kt │ │ │ ├── MonoGameUiBundle.kt │ │ │ ├── settings │ │ │ ├── MonoGameSettingsConfigurable.kt │ │ │ └── MgcbToolsetConfigurableProvider.kt │ │ │ └── CommonExtensions.kt │ │ └── extensions │ │ ├── commandLine │ │ ├── CliCommandExecutor.kt │ │ ├── CommandBuilder.kt │ │ └── DefaultCommandExecutor.kt │ │ ├── ClipboardUtil.kt │ │ ├── observables │ │ └── RdObservableProperty.kt │ │ └── workspaceModel │ │ └── WorkspaceModelExtensions.kt └── dotnet │ ├── Rider.Plugins.MonoGame │ ├── ZoneMarker.cs │ ├── Options │ │ └── ZoneMarker.cs │ ├── Mgcb │ │ ├── KnownMgcbVersions.cs │ │ ├── MgcbToolset.cs │ │ ├── KnownDotNetToolsCommands.cs │ │ ├── KnownDotNetTools.cs │ │ ├── ProjectDotnetToolsTracker.cs │ │ ├── MgcbEditorCommandNameResolver.cs │ │ ├── MgcbToolsetTracker.cs │ │ └── MgcbEditorToolResolver.cs │ ├── Extensions │ │ ├── DotNetToolLocalCacheExtensions.cs │ │ └── FlowExtensions.cs │ ├── IMonoGameRiderZone.cs │ ├── Rider.Plugins.MonoGame.csproj │ ├── Templates.DotSettings │ └── MonoGameRdModelHost.cs │ ├── Rider.Plugins.MonoGame.sln.DotSettings │ ├── Plugin.props │ ├── Rider.Plugins.MonoGame.Tests │ └── Rider.Plugins.MonoGame.Tests.csproj │ ├── Directory.Packages.props │ ├── Directory.Build.props │ └── Rider.Plugins.MonoGame.sln ├── .gitattributes ├── .github ├── variables │ └── .env └── workflows │ ├── environment │ └── action.yml │ ├── build.yml │ └── deploy.yml ├── .gitignore ├── renovate.json ├── .idea ├── .idea.ReSharperPlugin.MonoGameRider │ └── .idea │ │ └── runConfigurations │ │ ├── Rider__Frontend__Unix_.xml │ │ ├── rdgen__Windows_.xml │ │ ├── rdgen__Unix_.xml │ │ ├── Rider__Frontend__Windows_.xml │ │ ├── VisualStudio.xml │ │ ├── Rider__Unix_.xml │ │ └── Rider__Windows_.xml └── runConfigurations │ ├── Rider.xml │ └── rdgen.xml ├── gradle.properties ├── LICENSE ├── protocol ├── src │ └── main │ │ └── kotlin │ │ └── model │ │ └── rider │ │ └── MonoGameRiderModel.kt └── build.gradle.kts ├── settings.gradle.kts ├── docs └── Previewer Architecture.drawio ├── README.md ├── CHANGELOG.md └── gradlew.bat /img/how-to-use-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seclerp/rider-monogame/HEAD/img/how-to-use-1.png -------------------------------------------------------------------------------- /img/how-to-use-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seclerp/rider-monogame/HEAD/img/how-to-use-2.png -------------------------------------------------------------------------------- /img/how-to-use-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seclerp/rider-monogame/HEAD/img/how-to-use-3.png -------------------------------------------------------------------------------- /img/how-to-use-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seclerp/rider-monogame/HEAD/img/how-to-use-4.png -------------------------------------------------------------------------------- /img/how-to-use-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seclerp/rider-monogame/HEAD/img/how-to-use-5.png -------------------------------------------------------------------------------- /img/rider-monogame-cover.pdn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seclerp/rider-monogame/HEAD/img/rider-monogame-cover.pdn -------------------------------------------------------------------------------- /img/rider-monogame-cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seclerp/rider-monogame/HEAD/img/rider-monogame-cover.png -------------------------------------------------------------------------------- /test/MonoGameSolution/Desktop381/Program.cs: -------------------------------------------------------------------------------- 1 | using var game = new MonoGameProjectDesktop381.Game1(); 2 | game.Run(); -------------------------------------------------------------------------------- /img/rider-plus-monogame-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seclerp/rider-monogame/HEAD/img/rider-plus-monogame-128.png -------------------------------------------------------------------------------- /img/rider-plus-monogame-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seclerp/rider-monogame/HEAD/img/rider-plus-monogame-200.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seclerp/rider-monogame/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /src/rider/main/resources/icons/fx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seclerp/rider-monogame/HEAD/src/rider/main/resources/icons/fx.png -------------------------------------------------------------------------------- /src/rider/main/resources/icons/mgcb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seclerp/rider-monogame/HEAD/src/rider/main/resources/icons/mgcb.png -------------------------------------------------------------------------------- /test/MonoGameSolution/Desktop38/Icon.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seclerp/rider-monogame/HEAD/test/MonoGameSolution/Desktop38/Icon.bmp -------------------------------------------------------------------------------- /test/MonoGameSolution/Desktop38/Icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seclerp/rider-monogame/HEAD/test/MonoGameSolution/Desktop38/Icon.ico -------------------------------------------------------------------------------- /src/rider/main/resources/icons/mgcb@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seclerp/rider-monogame/HEAD/src/rider/main/resources/icons/mgcb@2x.png -------------------------------------------------------------------------------- /test/MonoGameSolution/Desktop381/Icon.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seclerp/rider-monogame/HEAD/test/MonoGameSolution/Desktop381/Icon.bmp -------------------------------------------------------------------------------- /test/MonoGameSolution/Desktop381/Icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seclerp/rider-monogame/HEAD/test/MonoGameSolution/Desktop381/Icon.ico -------------------------------------------------------------------------------- /src/rider/main/resources/icons/mgcb_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seclerp/rider-monogame/HEAD/src/rider/main/resources/icons/mgcb_dark.png -------------------------------------------------------------------------------- /src/rider/main/resources/icons/spritefont.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seclerp/rider-monogame/HEAD/src/rider/main/resources/icons/spritefont.png -------------------------------------------------------------------------------- /src/rider/main/resources/icons/mgcb@2x_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seclerp/rider-monogame/HEAD/src/rider/main/resources/icons/mgcb@2x_dark.png -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set the default behavior, in case people don't have core.autocrlf set. 2 | * text=auto 3 | 4 | # Preserve line endings in gradle scripts 5 | gradlew* -text diff 6 | -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/KnownNotificationGroups.kt: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.plugins.monogame 2 | 3 | object KnownNotificationGroups { 4 | val monoGameRider = "MonoGameRider" 5 | } -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/mgcb/previewer/properties/MgcbProperty.kt: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.plugins.monogame.mgcb.previewer.properties 2 | 3 | data class MgcbProperty(val key: String, val value: String) -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/mgcb/MgcbLexerAdapter.kt: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.plugins.monogame.mgcb 2 | 3 | import com.intellij.lexer.FlexAdapter 4 | 5 | class MgcbLexerAdapter : FlexAdapter(MgcbLexer(null)) -------------------------------------------------------------------------------- /.github/variables/.env: -------------------------------------------------------------------------------- 1 | PLUGIN_NAME=rider-monogame 2 | PLUGINS_REPOSITORY=https://plugins.jetbrains.com 3 | ARTIFACTS_FOLDER=artifacts 4 | ARTIFACTS_CHANGELOG=changelog.txt 5 | ARTIFACTS_VERSION=version.txt 6 | ARTIFACTS_PLUGIN_ID=pluginId.txt -------------------------------------------------------------------------------- /src/dotnet/Rider.Plugins.MonoGame/ZoneMarker.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Application.BuildScript.Application.Zones; 2 | 3 | namespace Rider.Plugins.MonoGame; 4 | 5 | [ZoneMarker] 6 | public class ZoneMarker : IRequire 7 | { 8 | } -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/mgcb/MgcbLanguage.kt: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.plugins.monogame.mgcb 2 | 3 | import com.intellij.lang.Language 4 | 5 | class MgcbLanguage : Language("MGCB") { 6 | companion object { 7 | val Instance = MgcbLanguage() 8 | } 9 | } -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/mgcb/previewer/tree/MgcbFolderNode.kt: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.plugins.monogame.mgcb.previewer.tree 2 | 3 | import com.intellij.util.PlatformIcons 4 | 5 | class MgcbFolderNode(name: String) 6 | : MgcbTreeNode(name, PlatformIcons.FOLDER_ICON) -------------------------------------------------------------------------------- /src/dotnet/Rider.Plugins.MonoGame/Options/ZoneMarker.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Application.BuildScript.Application.Zones; 2 | using JetBrains.Application.UI.Options.OptionPages; 3 | 4 | namespace Rider.Plugins.MonoGame.Options; 5 | 6 | [ZoneMarker] 7 | public class ZoneMarker : IRequire 8 | { 9 | } -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/mgcb/previewer/tree/MgcbTreeNode.kt: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.plugins.monogame.mgcb.previewer.tree 2 | 3 | import javax.swing.Icon 4 | import javax.swing.tree.DefaultMutableTreeNode 5 | 6 | abstract class MgcbTreeNode(name: String, val icon: Icon) : DefaultMutableTreeNode(name) -------------------------------------------------------------------------------- /test/MonoGameSolution/Desktop38/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Desktop38 4 | { 5 | public static class Program 6 | { 7 | [STAThread] 8 | static void Main() 9 | { 10 | using (var game = new Game1()) 11 | game.Run(); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build artifacts 2 | /.intellijPlatform/ 3 | [Bb]in/ 4 | [Oo]bj/ 5 | build 6 | output 7 | .gradle 8 | .tmp 9 | 10 | # User-specific files 11 | *.suo 12 | *.user 13 | *.sln.docstates 14 | *.cache 15 | 16 | # IDEs 17 | **/.idea/ 18 | !**/.idea/runConfigurations/ 19 | .vs 20 | _ReSharper* 21 | _dotTrace* 22 | gen 23 | Rd -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/mgcb/actions/commands/RegisterMgcbEditorCommand.kt: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.plugins.monogame.mgcb.actions.commands 2 | 3 | import com.intellij.execution.configurations.GeneralCommandLine 4 | 5 | class RegisterMgcbEditorCommand : CliCommand(GeneralCommandLine("mgcb-editor", "--register")) -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://cache-redirector.jetbrains.com/services.gradle.org/distributions/gradle-8.14.3-all.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /src/dotnet/Rider.Plugins.MonoGame/Mgcb/KnownMgcbVersions.cs: -------------------------------------------------------------------------------- 1 | using NuGet.Versioning; 2 | 3 | namespace Rider.Plugins.MonoGame.Mgcb; 4 | 5 | public class KnownMgcbVersions 6 | { 7 | public static readonly NuGetVersion Version381 = NuGetVersion.Parse("3.8.1"); 8 | public static readonly NuGetVersion Version380 = NuGetVersion.Parse("3.8.0"); 9 | } -------------------------------------------------------------------------------- /src/dotnet/Rider.Plugins.MonoGame/Mgcb/MgcbToolset.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.DataFlow; 2 | 3 | namespace Rider.Plugins.MonoGame.Mgcb; 4 | 5 | public class MgcbToolset where TTool : class 6 | { 7 | public IProperty Editor { get; init; } 8 | 9 | public void Unset() 10 | { 11 | Editor.SetValue(null); 12 | } 13 | } -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/mgcb/previewer/properties/MgcbPropertyValueColumn.kt: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.plugins.monogame.mgcb.previewer.properties 2 | 3 | class MgcbPropertyValueColumn : MgcbPropertyColumn("Value") { 4 | override fun valueOf(item: MgcbProperty?): String? { 5 | return item?.value 6 | } 7 | } -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/mgcb/actions/commands/CliCommandResult.kt: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.plugins.monogame.mgcb.actions.commands 2 | 3 | data class CliCommandResult( 4 | val command: String, 5 | val exitCode: Int, 6 | val output: String, 7 | val succeeded: Boolean, 8 | val error: String? = null 9 | ) 10 | -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/mgcb/previewer/properties/MgcbPropertyTableModel.kt: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.plugins.monogame.mgcb.previewer.properties 2 | 3 | import com.intellij.util.ui.ListTableModel 4 | 5 | class MgcbPropertyTableModel : ListTableModel( 6 | MgcbPropertyKeyColumn(), 7 | MgcbPropertyValueColumn() 8 | ) -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/mgcb/actions/commands/InstallMgcbEditorCommand.kt: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.plugins.monogame.mgcb.actions.commands 2 | 3 | import com.intellij.execution.configurations.GeneralCommandLine 4 | 5 | class InstallMgcbEditorCommand : CliCommand( 6 | GeneralCommandLine("dotnet", "tool", "install", "-g", "dotnet-mgcb-editor")) -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/mgcb/previewer/properties/MgcbPropertyTable.kt: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.plugins.monogame.mgcb.previewer.properties 2 | 3 | import com.intellij.ui.table.TableView 4 | 5 | class MgcbPropertyTable(model: MgcbPropertyTableModel) : TableView(model) { 6 | override fun createDefaultTableHeader() = JBTableHeader() 7 | } 8 | 9 | -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/mgcb/previewer/properties/MgcbPropertyKeyColumn.kt: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.plugins.monogame.mgcb.previewer.properties 2 | 3 | import com.intellij.util.ui.ColumnInfo 4 | 5 | class MgcbPropertyKeyColumn : MgcbPropertyColumn("Key") { 6 | override fun valueOf(item: MgcbProperty?): String? { 7 | return item?.key 8 | } 9 | } 10 | 11 | -------------------------------------------------------------------------------- /test/MonoGameSolution/.config/dotnet-tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "isRoot": true, 4 | "tools": { 5 | "dotnet-mgcb-editor-mac": { 6 | "version": "3.8.4", 7 | "commands": [ 8 | "mgcb-editor-mac" 9 | ] 10 | }, 11 | "dotnet-mgcb-editor": { 12 | "version": "3.8.4", 13 | "commands": [ 14 | "mgcb-editor" 15 | ] 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/extensions/commandLine/CliCommandExecutor.kt: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.extensions.commandLine 2 | 3 | import com.intellij.execution.configurations.GeneralCommandLine 4 | import com.intellij.openapi.project.Project 5 | 6 | abstract class CliCommandExecutor( 7 | protected val intellijProject: Project 8 | ) { 9 | abstract fun execute(command: GeneralCommandLine) 10 | } -------------------------------------------------------------------------------- /.github/workflows/environment/action.yml: -------------------------------------------------------------------------------- 1 | name: Import environment variables 2 | description: Import environment variables from a file 3 | inputs: 4 | envfile: 5 | description: '.env file to import' 6 | required: true 7 | default: .github/variables/.env 8 | runs: 9 | using: "composite" 10 | steps: 11 | - shell: pwsh 12 | run: Get-Content ${{ inputs.envfile }} | Add-Content -Path $env:GITHUB_ENV -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/mgcb/psi/MgcbPsiImplUtil.kt: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.plugins.monogame.mgcb.psi 2 | 3 | fun MgcbOption.getKey(): String? { 4 | val keyNode = node.findChildByType(MgcbTypes.OPTION_KEY) 5 | return keyNode?.text 6 | } 7 | 8 | fun MgcbOption.getValue(): String? { 9 | val valueNode = node.findChildByType(MgcbTypes.OPTION_VALUE) 10 | return valueNode?.text 11 | } 12 | -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/mgcb/previewer/properties/MgcbPropertyColumn.kt: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.plugins.monogame.mgcb.previewer.properties 2 | 3 | import com.intellij.util.ui.ColumnInfo 4 | 5 | abstract class MgcbPropertyColumn(name: String) : ColumnInfo(name) { 6 | private val renderer by lazy { MgcbPropertyRenderer() } 7 | override fun getRenderer(item: MgcbProperty?) = renderer 8 | } -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/mgcb/previewer/tree/MgcbBuildEntryNode.kt: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.plugins.monogame.mgcb.previewer.tree 2 | 3 | import com.intellij.icons.AllIcons 4 | import me.seclerp.rider.plugins.monogame.mgcb.previewer.BuildEntry 5 | import javax.swing.Icon 6 | 7 | class MgcbBuildEntryNode(name: String, icon: Icon?, val buildEntry: BuildEntry) 8 | : MgcbTreeNode(name, icon ?: AllIcons.FileTypes.Text) -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/mgcb/actions/commands/CheckMgcbEditorInstalledCommand.kt: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.plugins.monogame.mgcb.actions.commands 2 | 3 | import com.intellij.execution.configurations.GeneralCommandLine 4 | import java.nio.charset.Charset 5 | 6 | class CheckMgcbEditorInstalledCommand : CliCommand( 7 | GeneralCommandLine("mgcb-editor", "--help") 8 | .withCharset(Charset.forName("UTF-8")) 9 | ) -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/mgcb/psi/MgcbTokenType.kt: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.plugins.monogame.mgcb.psi 2 | 3 | import com.intellij.psi.tree.IElementType 4 | import me.seclerp.rider.plugins.monogame.mgcb.MgcbLanguage 5 | 6 | class MgcbTokenType(debugName: String) : IElementType(debugName, MgcbLanguage.Instance) { 7 | override fun toString(): String { 8 | return "MgcbTokenType." + super.toString() 9 | } 10 | } -------------------------------------------------------------------------------- /src/dotnet/Rider.Plugins.MonoGame/Mgcb/KnownDotNetToolsCommands.cs: -------------------------------------------------------------------------------- 1 | namespace Rider.Plugins.MonoGame.Mgcb; 2 | 3 | public class KnownDotNetToolsCommands 4 | { 5 | public const string MgcbEditor = "mgcb-editor"; 6 | public const string MgcbEditorWindows = "mgcb-editor-windows"; 7 | public const string MgcbEditorLinux = "mgcb-editor-linux"; 8 | public const string MgcbEditorMac = "mgcb-editor-mac"; 9 | public const string Mgcb = "mgcb"; 10 | } -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/extensions/ClipboardUtil.kt: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.extensions 2 | 3 | import java.awt.Toolkit 4 | import java.awt.datatransfer.StringSelection 5 | 6 | internal object ClipboardUtil { 7 | fun copyToClipboard(text: String) { 8 | val selection = StringSelection(text) 9 | val clipboard = Toolkit.getDefaultToolkit().systemClipboard 10 | clipboard.setContents(selection, null) 11 | } 12 | } -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/mgcb/psi/MgcbElementType.kt: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.plugins.monogame.mgcb.psi 2 | 3 | import com.intellij.psi.tree.IElementType 4 | import me.seclerp.rider.plugins.monogame.mgcb.MgcbLanguage 5 | 6 | class MgcbElementType(debugName: String) : IElementType(debugName, MgcbLanguage.Instance) { 7 | override fun toString(): String { 8 | return "MgcbElementType." + super.toString() 9 | } 10 | } -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/mgcb/previewer/MgcbEditorNotificationPanel.kt: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.plugins.monogame.mgcb.previewer 2 | 3 | import com.intellij.openapi.fileEditor.FileEditor 4 | import com.intellij.ui.EditorNotificationPanel 5 | 6 | class MgcbEditorNotificationPanel(fileEditor: FileEditor) : EditorNotificationPanel(fileEditor) { 7 | fun install() { 8 | executeAction("InstallMgcbEditorAction") 9 | } 10 | } -------------------------------------------------------------------------------- /src/dotnet/Rider.Plugins.MonoGame/Mgcb/KnownDotNetTools.cs: -------------------------------------------------------------------------------- 1 | namespace Rider.Plugins.MonoGame.Mgcb; 2 | 3 | public class KnownDotNetTools 4 | { 5 | public const string MgcbEditor = "dotnet-mgcb-editor"; 6 | public const string MgcbEditorWindows = "dotnet-mgcb-editor-windows"; 7 | public const string MgcbEditorLinux = "dotnet-mgcb-editor-linux"; 8 | public const string MgcbEditorMac = "dotnet-mgcb-editor-mac"; 9 | public const string Mgcb = "dotnet-mgcb"; 10 | } -------------------------------------------------------------------------------- /test/MonoGameSolution/Desktop38/Content/Content.mgcb: -------------------------------------------------------------------------------- 1 | 2 | #----------------------------- Global Properties ----------------------------# 3 | 4 | /outputDir:bin/$(Platform) 5 | /intermediateDir:obj/$(Platform) 6 | /platform:DesktopGL 7 | /config: 8 | /profile:Reach 9 | /compress:False 10 | 11 | #-------------------------------- References --------------------------------# 12 | 13 | 14 | #---------------------------------- Content ---------------------------------# 15 | 16 | -------------------------------------------------------------------------------- /src/rider/main/resources/fileTemplates/Content Builder File.mgcb.ft: -------------------------------------------------------------------------------- 1 | #----------------------------- Global Properties ----------------------------# 2 | 3 | /outputDir:bin/$(Platform) 4 | /intermediateDir:obj/$(Platform) 5 | /platform:DesktopGL 6 | /config: 7 | /profile:Reach 8 | /compress:False 9 | 10 | #-------------------------------- References --------------------------------# 11 | 12 | 13 | #---------------------------------- Content ---------------------------------# 14 | -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/templates/MonoGameProjectTemplateCustomizer.kt: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.plugins.monogame.templates 2 | 3 | import com.jetbrains.rider.projectView.projectTemplates.providers.ProjectTemplateCustomizer 4 | 5 | internal class MonoGameProjectTemplateCustomizer : ProjectTemplateCustomizer { 6 | private val monoGameTemplateTypes = setOf(MonoGameTemplateType()) 7 | override fun getCustomProjectTemplateTypes() = monoGameTemplateTypes 8 | } -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/MonoGameIcons.kt: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.plugins.monogame 2 | 3 | import com.intellij.openapi.util.IconLoader 4 | 5 | object MonoGameIcons { 6 | @JvmField 7 | val MgcbFile = IconLoader.getIcon("icons/mgcb.png", javaClass) 8 | 9 | @JvmField 10 | val SpriteFontFile = IconLoader.getIcon("icons/spritefont.png", javaClass) 11 | 12 | @JvmField 13 | val EffectFile = IconLoader.getIcon("icons/fx.png", javaClass) 14 | } -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": ["config:recommended"], 4 | "packageRules": [ 5 | { 6 | "groupName": ".NET MGCB tools", 7 | "matchManagers": ["nuget", "dotnet-tools"], 8 | "matchPackageNames": [ 9 | "dotnet-mgcb-editor", 10 | "dotnet-mgcb-editor-mac", 11 | "dotnet-mgcb-editor-windows", 12 | "dotnet-mgcb-editor-linux", 13 | "dotnet-mgcb" 14 | ] 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /src/dotnet/Rider.Plugins.MonoGame/Extensions/DotNetToolLocalCacheExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using JetBrains.Annotations; 3 | using JetBrains.ProjectModel.NuGet.DotNetTools; 4 | 5 | namespace Rider.Plugins.MonoGame.Extensions; 6 | 7 | public static class DotNetToolLocalCacheExtensions 8 | { 9 | [CanBeNull] 10 | public static LocalTool GetLocalTool(this DotNetToolLocalCache cache, string packageId) => 11 | cache.GetAllLocalTools().FirstOrDefault(tool => tool.PackageId == packageId); 12 | } -------------------------------------------------------------------------------- /src/dotnet/Rider.Plugins.MonoGame/Extensions/FlowExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using JetBrains.Annotations; 3 | 4 | namespace Rider.Plugins.MonoGame.Extensions; 5 | 6 | public static class FlowExtensions 7 | { 8 | public static T Apply(this T self, Action action) 9 | { 10 | action(self); 11 | return self; 12 | } 13 | 14 | public static TTarget Let(this TSource self, [NotNull] Func mapping) 15 | { 16 | return mapping(self); 17 | } 18 | } -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/mgcb/actions/commands/MgcbEditorCommand.kt: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.plugins.monogame.mgcb.actions.commands 2 | 3 | import com.intellij.execution.configurations.GeneralCommandLine 4 | import com.intellij.openapi.project.Project 5 | import java.nio.charset.Charset 6 | 7 | class MgcbEditorCommand(mgcbFilePath: String, project: Project) 8 | : CliCommand(GeneralCommandLine("mgcb-editor", mgcbFilePath) 9 | .withCharset(Charset.forName("UTF-8"))) 10 | -------------------------------------------------------------------------------- /src/dotnet/Rider.Plugins.MonoGame.sln.DotSettings: -------------------------------------------------------------------------------- 1 | 2 | True 3 | True -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/mgcb/MgcbSyntaxHighlighterFactory.kt: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.plugins.monogame.mgcb 2 | 3 | import com.intellij.openapi.fileTypes.SyntaxHighlighter 4 | import com.intellij.openapi.fileTypes.SyntaxHighlighterFactory 5 | import com.intellij.openapi.project.Project 6 | import com.intellij.openapi.vfs.VirtualFile 7 | 8 | class MgcbSyntaxHighlighterFactory : SyntaxHighlighterFactory() { 9 | override fun getSyntaxHighlighter(p0: Project?, p1: VirtualFile?): SyntaxHighlighter = 10 | MgcbSyntaxHighlighter() 11 | } -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/mgcb/toolset/MgcbResolvedTool.kt: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.plugins.monogame.mgcb.toolset 2 | 3 | import me.seclerp.rider.plugins.monogame.ToolDefinition 4 | 5 | sealed class MgcbResolvedTool { 6 | class Local(val definition: ToolDefinition) : MgcbResolvedTool() 7 | class Global(val definition: ToolDefinition) : MgcbResolvedTool() 8 | object None : MgcbResolvedTool() 9 | } 10 | 11 | fun MgcbResolvedTool.isNone() = this is MgcbResolvedTool.None 12 | fun MgcbResolvedTool.isNotNone() = this !is MgcbResolvedTool.None -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/mgcb/previewer/listeners/MgcbPendingUpdateListener.kt: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.plugins.monogame.mgcb.previewer.listeners 2 | 3 | import com.intellij.openapi.vfs.VirtualFile 4 | import me.seclerp.rider.plugins.monogame.mgcb.previewer.MgcbPreviewerTopics 5 | import java.util.* 6 | 7 | interface MgcbPendingUpdateListener : EventListener { 8 | companion object { 9 | @JvmField 10 | val TOPIC = MgcbPreviewerTopics.MGCB_PENDING_UPDATE_TOPIC 11 | } 12 | 13 | fun handle(file: VirtualFile) {} 14 | } 15 | 16 | -------------------------------------------------------------------------------- /src/dotnet/Rider.Plugins.MonoGame/IMonoGameRiderZone.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Application.BuildScript.Application.Zones; 2 | using JetBrains.ProjectModel.NuGet; 3 | using JetBrains.ReSharper.Feature.Services.Daemon; 4 | using JetBrains.ReSharper.Psi; 5 | using JetBrains.ReSharper.Psi.CSharp; 6 | 7 | namespace Rider.Plugins.MonoGame; 8 | 9 | [ZoneDefinition] 10 | // [ZoneDefinitionConfigurableFeature("Title", "Description", IsInProductSection: false)] 11 | public interface IMonoGameRiderZone : IPsiLanguageZone, 12 | IRequire, 13 | IRequire, 14 | IRequire 15 | { 16 | } -------------------------------------------------------------------------------- /test/MonoGameSolution/Desktop381/Content/Content.mgcb: -------------------------------------------------------------------------------- 1 | 2 | #----------------------------- Global Properties ----------------------------# 3 | 4 | /outputDir:bin/$(Platform) 5 | /intermediateDir:obj/$(Platform) 6 | /platform:DesktopGL 7 | /config: 8 | /profile:Reach 9 | /compress:False 10 | 11 | #-------------------------------- References --------------------------------# 12 | 13 | 14 | #---------------------------------- Content ---------------------------------# 15 | 16 | #begin File.fx 17 | /importer:EffectImporter 18 | /processor:EffectProcessor 19 | /processorParam:DebugMode=Auto 20 | /build:File.fx 21 | 22 | -------------------------------------------------------------------------------- /.idea/.idea.ReSharperPlugin.MonoGameRider/.idea/runConfigurations/Rider__Frontend__Unix_.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | -------------------------------------------------------------------------------- /src/dotnet/Plugin.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 2022.3.0-eap01 5 | 6 | MonoGame 7 | JetBrains Rider plugin for MonoGame 8 | 9 | Andrew Rublyov 10 | Copyright $([System.DateTime]::Now.Year) Andrew Rublyov 11 | resharper plugin 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/dotnet/Rider.Plugins.MonoGame.Tests/Rider.Plugins.MonoGame.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net48 5 | false 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /.idea/.idea.ReSharperPlugin.MonoGameRider/.idea/runConfigurations/rdgen__Windows_.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/mgcb/psi/MgcbFile.kt: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.plugins.monogame.mgcb.psi 2 | 3 | import com.intellij.extapi.psi.PsiFileBase 4 | import com.intellij.openapi.fileTypes.FileType 5 | import com.intellij.psi.FileViewProvider 6 | import me.seclerp.rider.plugins.monogame.mgcb.MgcbFileType 7 | import me.seclerp.rider.plugins.monogame.mgcb.MgcbLanguage 8 | 9 | class MgcbFile(viewProvider: FileViewProvider) : PsiFileBase(viewProvider, MgcbLanguage.Instance) { 10 | override fun getFileType(): FileType = MgcbFileType.Instance 11 | 12 | override fun toString(): String = "MGCB File" 13 | } -------------------------------------------------------------------------------- /.idea/.idea.ReSharperPlugin.MonoGameRider/.idea/runConfigurations/rdgen__Unix_.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/.idea.ReSharperPlugin.MonoGameRider/.idea/runConfigurations/Rider__Frontend__Windows_.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/templates/MonoGameTemplateMetadata.kt: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.plugins.monogame.templates 2 | 3 | internal object MonoGameTemplateMetadata { 4 | internal object Names { 5 | const val CROSS_PLATFORM_APP = "Cross-Platform Desktop App" 6 | const val WINDOWS_DESKTOP_APP = "Windows Desktop App" 7 | const val ANDROID_APP = "Android App" 8 | const val IOS_APP = "iOS App" 9 | const val GAME_LIB = "Game Library" 10 | const val CONTENT_PIPELINE_EXTENSION = "Content Pipeline Extension" 11 | const val SHARED_LIB = "Shared Library Project" 12 | } 13 | } -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/mgcb/previewer/listeners/MgcbProcessedUpdateListener.kt: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.plugins.monogame.mgcb.previewer.listeners 2 | 3 | import com.intellij.openapi.vfs.VirtualFile 4 | import me.seclerp.rider.plugins.monogame.mgcb.previewer.MgcbModel 5 | import me.seclerp.rider.plugins.monogame.mgcb.previewer.MgcbPreviewerTopics 6 | import java.util.* 7 | 8 | interface MgcbProcessedUpdateListener : EventListener { 9 | companion object { 10 | @JvmField 11 | val TOPIC = MgcbPreviewerTopics.MGCB_PROCESSED_UPDATE_TOPIC 12 | } 13 | 14 | fun handle(file: VirtualFile, mgcbModel: MgcbModel) {} 15 | } 16 | -------------------------------------------------------------------------------- /.idea/.idea.ReSharperPlugin.MonoGameRider/.idea/runConfigurations/VisualStudio.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/mgcb/MgcbFileType.kt: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.plugins.monogame.mgcb 2 | 3 | import com.intellij.openapi.fileTypes.LanguageFileType 4 | import me.seclerp.rider.plugins.monogame.MonoGameIcons 5 | import javax.swing.Icon 6 | 7 | class MgcbFileType : LanguageFileType(MgcbLanguage.Instance) { 8 | override fun getName(): String = "MonoGame Content Pipeline File" 9 | 10 | override fun getDescription(): String = "Content Pipeline file used by MonoGame" 11 | 12 | override fun getDefaultExtension(): String = "mgcb" 13 | 14 | override fun getIcon(): Icon = MonoGameIcons.MgcbFile 15 | 16 | companion object { 17 | val Instance = MgcbFileType() 18 | } 19 | } -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/mgcb/previewer/MgcbPreviewerTopics.kt: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.plugins.monogame.mgcb.previewer 2 | 3 | import com.intellij.util.messages.Topic 4 | import me.seclerp.rider.plugins.monogame.mgcb.previewer.listeners.MgcbPendingUpdateListener 5 | import me.seclerp.rider.plugins.monogame.mgcb.previewer.listeners.MgcbProcessedUpdateListener 6 | 7 | object MgcbPreviewerTopics { 8 | @JvmField 9 | @Topic.ProjectLevel 10 | val MGCB_PENDING_UPDATE_TOPIC = Topic.create("MgcbPendingUpdateTopic", MgcbPendingUpdateListener::class.java) 11 | 12 | @JvmField 13 | @Topic.ProjectLevel 14 | val MGCB_PROCESSED_UPDATE_TOPIC = Topic.create("MgcbProcessedUpdateTopic", MgcbProcessedUpdateListener::class.java) 15 | } -------------------------------------------------------------------------------- /src/dotnet/Directory.Packages.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/mgcb/previewer/tree/MgcbNodeRenderer.kt: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.plugins.monogame.mgcb.previewer.tree 2 | 3 | import com.intellij.ide.util.treeView.NodeRenderer 4 | import javax.swing.JTree 5 | 6 | class MgcbNodeRenderer : NodeRenderer() { 7 | override fun customizeCellRenderer( 8 | tree: JTree, 9 | value: Any?, 10 | selected: Boolean, 11 | expanded: Boolean, 12 | leaf: Boolean, 13 | row: Int, 14 | hasFocus: Boolean 15 | ) { 16 | super.customizeCellRenderer(tree, value, selected, expanded, leaf, row, hasFocus) 17 | 18 | if (value is MgcbTreeNode) { 19 | icon = fixIconIfNeeded(value.icon, selected, hasFocus) 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /src/dotnet/Rider.Plugins.MonoGame/Mgcb/ProjectDotnetToolsTracker.cs: -------------------------------------------------------------------------------- 1 | using JetBrains.Application.FileSystemTracker; 2 | using JetBrains.Application.Threading; 3 | using JetBrains.Lifetimes; 4 | using JetBrains.ProjectModel; 5 | using JetBrains.ProjectModel.NuGet.DotNetTools; 6 | using JetBrains.Util; 7 | 8 | namespace Rider.Plugins.MonoGame.Mgcb; 9 | 10 | public class ProjectDotnetToolsTracker : NuGetDotnetToolsTrackerBase 11 | { 12 | public ProjectDotnetToolsTracker( 13 | Lifetime lifetime, 14 | IProject project, 15 | IFileSystemTracker fileSystemTracker, 16 | IShellLocks locks, 17 | ILogger logger, 18 | ISolutionToolset solutionToolset) : base(lifetime, project.Location, fileSystemTracker, locks, logger, solutionToolset) 19 | { 20 | } 21 | } -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/effect/EffectFileType.kt: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.plugins.monogame.effect 2 | 3 | import com.intellij.openapi.fileTypes.LanguageFileType 4 | import com.intellij.openapi.fileTypes.PlainTextLanguage 5 | import me.seclerp.rider.plugins.monogame.MonoGameIcons 6 | import javax.swing.Icon 7 | 8 | class EffectFileType : LanguageFileType(PlainTextLanguage.INSTANCE) { 9 | override fun getName(): String = "MonoGame Effect File" 10 | 11 | override fun getDescription(): String = "Shader language effect used by MonoGame" 12 | 13 | override fun getDefaultExtension(): String = "fx" 14 | 15 | override fun getIcon(): Icon = MonoGameIcons.EffectFile 16 | 17 | companion object { 18 | val Instance = EffectFileType() 19 | } 20 | } -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/mgcb/previewer/properties/MgcbPropertyRenderer.kt: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.plugins.monogame.mgcb.previewer.properties 2 | 3 | import com.intellij.util.ui.JBUI 4 | import java.awt.Component 5 | import javax.swing.JTable 6 | import javax.swing.table.DefaultTableCellRenderer 7 | 8 | class MgcbPropertyRenderer : DefaultTableCellRenderer() { 9 | override fun getTableCellRendererComponent( 10 | table: JTable?, 11 | value: Any?, 12 | isSelected: Boolean, 13 | hasFocus: Boolean, 14 | row: Int, 15 | column: Int 16 | ): Component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column).apply { 17 | border = JBUI.Borders.empty(JBUI.scale(2), JBUI.scale(8)) 18 | } 19 | } -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/spritefont/SpriteFontFileType.kt: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.plugins.monogame.spritefont 2 | 3 | import com.intellij.lang.xml.XMLLanguage 4 | import com.intellij.openapi.fileTypes.LanguageFileType 5 | import me.seclerp.rider.plugins.monogame.MonoGameIcons 6 | import javax.swing.Icon 7 | 8 | class SpriteFontFileType : LanguageFileType(XMLLanguage.INSTANCE) { 9 | override fun getName(): String = "MonoGame SpriteFont File" 10 | 11 | override fun getDescription(): String = "Description of a font used by MonoGame" 12 | 13 | override fun getDefaultExtension(): String = "spritefont" 14 | 15 | override fun getIcon(): Icon = MonoGameIcons.SpriteFontFile 16 | 17 | companion object { 18 | val Instance = SpriteFontFileType() 19 | } 20 | } -------------------------------------------------------------------------------- /test/MonoGameSolution/Desktop381/.config/dotnet-tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "isRoot": true, 4 | "tools": { 5 | "dotnet-mgcb": { 6 | "version": "3.8.4", 7 | "commands": [ 8 | "mgcb" 9 | ] 10 | }, 11 | "dotnet-mgcb-editor": { 12 | "version": "3.8.4", 13 | "commands": [ 14 | "mgcb-editor" 15 | ] 16 | }, 17 | "dotnet-mgcb-editor-linux": { 18 | "version": "3.8.4", 19 | "commands": [ 20 | "mgcb-editor-linux" 21 | ] 22 | }, 23 | "dotnet-mgcb-editor-windows": { 24 | "version": "3.8.4", 25 | "commands": [ 26 | "mgcb-editor-windows" 27 | ] 28 | }, 29 | "dotnet-mgcb-editor-mac": { 30 | "version": "3.8.4", 31 | "commands": [ 32 | "mgcb-editor-mac" 33 | ] 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/MonoGameUiBundle.kt: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.plugins.monogame 2 | 3 | import com.intellij.DynamicBundle 4 | import org.jetbrains.annotations.Nls 5 | import org.jetbrains.annotations.PropertyKey 6 | import java.util.function.Supplier 7 | 8 | private const val BUNDLE = "messages.MonoGameUiBundle" 9 | 10 | object MonoGameUiBundle : DynamicBundle(BUNDLE) { 11 | 12 | @Nls 13 | fun message( 14 | @PropertyKey(resourceBundle = BUNDLE) key: String, 15 | vararg params: Any 16 | ): String { 17 | return getMessage(key, *params) 18 | } 19 | 20 | fun messagePointer( 21 | @PropertyKey(resourceBundle = BUNDLE) key: String, 22 | vararg params: Any 23 | ): Supplier<@Nls String> { 24 | return getLazyMessage(key, *params) 25 | } 26 | } -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/settings/MonoGameSettingsConfigurable.kt: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.plugins.monogame.settings 2 | 3 | import com.intellij.openapi.options.SearchableConfigurable 4 | import com.intellij.ui.dsl.builder.panel 5 | import me.seclerp.rider.plugins.monogame.MonoGameUiBundle 6 | 7 | class MonoGameSettingsConfigurable : SearchableConfigurable { 8 | override fun createComponent() = panel { 9 | row { 10 | label(MonoGameUiBundle.message("configurable.group.tools.monogame.settings.description")) 11 | } 12 | } 13 | 14 | override fun isModified() = false 15 | 16 | override fun apply() {} 17 | 18 | override fun getDisplayName() = MonoGameUiBundle.message("configurable.group.tools.monogame.settings.display.name") 19 | 20 | override fun getId() = "tools.monogame" 21 | } -------------------------------------------------------------------------------- /.idea/runConfigurations/Rider.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 16 | 18 | true 19 | 20 | 21 | -------------------------------------------------------------------------------- /.idea/runConfigurations/rdgen.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 16 | 18 | true 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/mgcb/previewer/services/MgcbAnalyzer.kt: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.plugins.monogame.mgcb.previewer.services 2 | 3 | import com.intellij.openapi.components.Service 4 | import com.intellij.psi.util.PsiTreeUtil 5 | import me.seclerp.rider.plugins.monogame.mgcb.previewer.MgcbModel 6 | import me.seclerp.rider.plugins.monogame.mgcb.previewer.MgcbModelBuilderVisitor 7 | import me.seclerp.rider.plugins.monogame.mgcb.psi.MgcbFile 8 | import me.seclerp.rider.plugins.monogame.mgcb.psi.MgcbOption 9 | 10 | @Service 11 | class MgcbAnalyzer { 12 | fun analyzeFile(file: MgcbFile): MgcbModel { 13 | val mgcbOptions = PsiTreeUtil.getChildrenOfType(file, MgcbOption::class.java) 14 | val visitor = MgcbModelBuilderVisitor() 15 | mgcbOptions?.forEach { it.accept(visitor) } 16 | 17 | return visitor.resultingModel 18 | } 19 | } -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/mgcb/state/MgcbEditorGlobalState.kt: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.plugins.monogame.mgcb.state 2 | 3 | import com.intellij.openapi.components.PersistentStateComponent 4 | import com.intellij.openapi.components.Service 5 | import com.intellij.openapi.components.State 6 | import com.intellij.openapi.components.Storage 7 | 8 | class MgcbEditorGlobalState { 9 | var installNotificationClosed: Boolean = false 10 | } 11 | 12 | @Service 13 | @State(name = "EfCoreCommonOptions", storages = [Storage("MgcbEditorGlobalState.xml")]) 14 | class MgcbEditorGlobalStateService : PersistentStateComponent { 15 | private var myState = MgcbEditorGlobalState() 16 | 17 | override fun getState(): MgcbEditorGlobalState = myState 18 | 19 | override fun loadState(state: MgcbEditorGlobalState) { 20 | myState = state 21 | } 22 | } -------------------------------------------------------------------------------- /src/rider/main/resources/fileTemplates/Sprite Effect File.fx.ft: -------------------------------------------------------------------------------- 1 | \#if OPENGL 2 | \#define SV_POSITION POSITION 3 | \#define VS_SHADERMODEL vs_3_0 4 | \#define PS_SHADERMODEL ps_3_0 5 | \#else 6 | \#define VS_SHADERMODEL vs_4_0_level_9_1 7 | \#define PS_SHADERMODEL ps_4_0_level_9_1 8 | #endif 9 | 10 | Texture2D SpriteTexture; 11 | 12 | sampler2D SpriteTextureSampler = sampler_state 13 | { 14 | Texture = ; 15 | }; 16 | 17 | struct VertexShaderOutput 18 | { 19 | float4 Position : SV_POSITION; 20 | float4 Color : COLOR0; 21 | float2 TextureCoordinates : TEXCOORD0; 22 | }; 23 | 24 | float4 MainPS(VertexShaderOutput input) : COLOR 25 | { 26 | return tex2D(SpriteTextureSampler,input.TextureCoordinates) * input.Color; 27 | } 28 | 29 | technique SpriteDrawing 30 | { 31 | pass P0 32 | { 33 | PixelShader = compile PS_SHADERMODEL MainPS(); 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/extensions/observables/RdObservableProperty.kt: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.extensions.observables 2 | 3 | import com.intellij.openapi.Disposable 4 | import com.intellij.openapi.observable.properties.ObservableProperty 5 | import com.intellij.openapi.rd.createLifetime 6 | import com.jetbrains.rd.util.lifetime.Lifetime 7 | import com.jetbrains.rd.util.reactive.IProperty 8 | import com.jetbrains.rd.util.reactive.IPropertyView 9 | 10 | class RdObservableProperty ( 11 | private val propertyView: IPropertyView, 12 | private val propertyLifetime: Lifetime 13 | ) : ObservableProperty { 14 | override fun get() = propertyView.value 15 | 16 | override fun afterChange(parentDisposable: Disposable?, listener: (T) -> Unit) { 17 | val lifetime = parentDisposable?.createLifetime() ?: propertyLifetime 18 | propertyView.change.advise(lifetime, listener) 19 | } 20 | } -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/mgcb/previewer/listeners/MgcbFileListener.kt: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.plugins.monogame.mgcb.previewer.listeners 2 | 3 | import com.intellij.openapi.project.Project 4 | import com.intellij.openapi.vfs.newvfs.BulkFileListener 5 | import com.intellij.openapi.vfs.newvfs.events.VFileEvent 6 | import me.seclerp.rider.plugins.monogame.mgcb.previewer.MgcbPreviewerTopics 7 | 8 | class MgcbFileListener( 9 | project: Project, 10 | ) : BulkFileListener { 11 | private val publisher = project.messageBus.syncPublisher(MgcbPreviewerTopics.MGCB_PENDING_UPDATE_TOPIC) 12 | 13 | override fun after(events: MutableList) { 14 | val mgcbFiles = events 15 | .filter { it.file?.extension == "mgcb" } 16 | .mapNotNull { it.file } 17 | 18 | for (file in mgcbFiles) { 19 | publisher.handle(file) 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/mgcb/toolset/MgcbToolsetState.kt: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.plugins.monogame.mgcb.toolset 2 | 3 | import com.intellij.openapi.components.PersistentStateComponent 4 | import com.intellij.openapi.components.State 5 | import com.intellij.openapi.components.Storage 6 | import com.intellij.openapi.components.service 7 | import com.intellij.util.xmlb.XmlSerializerUtil 8 | 9 | @State( 10 | name = "me.seclerp.rider.plugins.monogame.mgcb.toolset.MgcbToolsetState", 11 | storages = [Storage("MgcbToolsetState.xml")] 12 | ) 13 | class MgcbToolsetState : PersistentStateComponent { 14 | companion object { 15 | fun getInstance() = service() 16 | } 17 | override fun getState(): MgcbToolsetState { 18 | return this 19 | } 20 | 21 | override fun loadState(state: MgcbToolsetState) { 22 | XmlSerializerUtil.copyBean(state, this) 23 | } 24 | } -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/mgcb/previewer/listeners/MgcbDocumentListener.kt: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.plugins.monogame.mgcb.previewer.listeners 2 | 3 | import com.intellij.openapi.editor.event.DocumentEvent 4 | import com.intellij.openapi.editor.event.DocumentListener 5 | import com.intellij.openapi.project.Project 6 | import com.intellij.openapi.vfs.VirtualFile 7 | import me.seclerp.rider.plugins.monogame.mgcb.previewer.MgcbPreviewerTopics 8 | import javax.swing.Timer 9 | 10 | class MgcbDocumentListener( 11 | project: Project, 12 | private val file: VirtualFile 13 | ) : DocumentListener { 14 | private val publisher = project.messageBus.syncPublisher(MgcbPreviewerTopics.MGCB_PENDING_UPDATE_TOPIC) 15 | private val timer = Timer(1000) { publisher.handle(file) } 16 | 17 | init { 18 | timer.isRepeats = false 19 | timer.stop() 20 | } 21 | 22 | override fun documentChanged(event: DocumentEvent) { 23 | timer.restart() 24 | } 25 | } -------------------------------------------------------------------------------- /.idea/.idea.ReSharperPlugin.MonoGameRider/.idea/runConfigurations/Rider__Unix_.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 17 | -------------------------------------------------------------------------------- /.idea/.idea.ReSharperPlugin.MonoGameRider/.idea/runConfigurations/Rider__Windows_.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 17 | -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/mgcb/actions/commands/Extensions.kt: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.plugins.monogame.mgcb.actions.commands 2 | 3 | import com.intellij.openapi.progress.runBackgroundableTask 4 | import com.intellij.openapi.project.Project 5 | 6 | fun executeCommandUnderProgress( 7 | project: Project, taskTitle: String, 8 | what: (Unit) -> CliCommandResult 9 | ) { 10 | runBackgroundableTask(taskTitle, project, false) { 11 | val result = what(Unit) 12 | if (!result.succeeded) { 13 | val errorTextBuilder = StringBuilder() 14 | errorTextBuilder.append("Command: ${result.command}") 15 | 16 | if (result.output.trim().isNotEmpty()) 17 | errorTextBuilder.append("\n\nOutput:\n${result.output}") 18 | 19 | if (result.error?.trim()?.isNotEmpty() == true) 20 | errorTextBuilder.append("\n\nError:\n${result.error}") 21 | 22 | errorTextBuilder.append("\n\nExit code: ${result.exitCode}") 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /src/dotnet/Rider.Plugins.MonoGame/Rider.Plugins.MonoGame.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(MSBuildToolsPath)\Microsoft.CSharp.targets 5 | 6 | 7 | 8 | 9 | 10 | net48 11 | Rider.Plugins.MonoGame 12 | $(AssemblyName) 13 | false 14 | $(DefineConstants);RIDER 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/dotnet/Rider.Plugins.MonoGame/Mgcb/MgcbEditorCommandNameResolver.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using JetBrains.Annotations; 3 | using JetBrains.ProjectModel.NuGet.DotNetTools; 4 | 5 | namespace Rider.Plugins.MonoGame.Mgcb; 6 | 7 | public static class MgcbEditorCommandNameResolver 8 | { 9 | // TODO: Replace with global tool version lookup using project.assets.json and DotNetSettings.xml 10 | public static string Resolve([NotNull] GlobalToolCacheEntry tool) => tool.ToolName switch 11 | { 12 | KnownDotNetTools.MgcbEditor => KnownDotNetToolsCommands.MgcbEditor, 13 | KnownDotNetTools.MgcbEditorWindows => KnownDotNetToolsCommands.MgcbEditorWindows, 14 | KnownDotNetTools.MgcbEditorLinux => KnownDotNetToolsCommands.MgcbEditorLinux, 15 | KnownDotNetTools.MgcbEditorMac => KnownDotNetToolsCommands.MgcbEditorMac, 16 | var other => other 17 | }; 18 | 19 | public static string Resolve([NotNull] LocalTool tool) => tool.Commands 20 | .Select(command => command.Name) 21 | .FirstOrDefault() ?? tool.PackageId; 22 | } -------------------------------------------------------------------------------- /test/MonoGameSolution/Desktop381/Content/File.fx: -------------------------------------------------------------------------------- 1 | #if OPENGL 2 | #define SV_POSITION POSITION 3 | #define VS_SHADERMODEL vs_3_0 4 | #define PS_SHADERMODEL ps_3_0 5 | #else 6 | #define VS_SHADERMODEL vs_4_0_level_9_1 7 | #define PS_SHADERMODEL ps_4_0_level_9_1 8 | #endif 9 | 10 | matrix WorldViewProjection; 11 | 12 | struct VertexShaderInput 13 | { 14 | float4 Position : POSITION0; 15 | float4 Color : COLOR0; 16 | }; 17 | 18 | struct VertexShaderOutput 19 | { 20 | float4 Position : SV_POSITION; 21 | float4 Color : COLOR0; 22 | }; 23 | 24 | VertexShaderOutput MainVS(in VertexShaderInput input) 25 | { 26 | VertexShaderOutput output = (VertexShaderOutput)0; 27 | 28 | output.Position = mul(input.Position, WorldViewProjection); 29 | output.Color = input.Color; 30 | 31 | return output; 32 | } 33 | 34 | float4 MainPS(VertexShaderOutput input) : COLOR 35 | { 36 | return input.Color; 37 | } 38 | 39 | technique BasicColorDrawing 40 | { 41 | pass P0 42 | { 43 | VertexShader = compile VS_SHADERMODEL MainVS(); 44 | PixelShader = compile PS_SHADERMODEL MainPS(); 45 | } 46 | }; -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Any property can be overwritten from command-line via 2 | # -P= 3 | javaVersion=21 4 | 5 | dotnetPluginId=Rider.Plugins.MonoGame 6 | riderPluginId=me.seclerp.rider.plugins.monogame 7 | pluginVersion=253.1.0 8 | 9 | rdVersion=2025.3.1 10 | rdKotlinVersion=2.2.0 11 | intellijPlatformGradleVersion=2.10.4 12 | gradleJvmWrapperVersion=0.15.0 13 | 14 | buildConfiguration=Debug 15 | 16 | publishToken="_PLACEHOLDER_" 17 | publishChannel=default 18 | 19 | # Possible values (minor is omitted): 20 | # Release: 2020.2 21 | # Nightly: 2020.3-SNAPSHOT 22 | # EAP: 2020.3-EAP2-SNAPSHOT 23 | productVersion=2025.3 24 | 25 | # Kotlin 1.4 will bundle the stdlib dependency by default, causing problems with the version bundled with the IDE 26 | # https://blog.jetbrains.com/kotlin/2020/07/kotlin-1-4-rc-released/#stdlib-default 27 | kotlin.stdlib.default.dependency=false 28 | 29 | # To use IDE builds from Maven repo rather than official installers. 30 | org.jetbrains.intellij.platform.buildFeature.useBinaryReleases=false 31 | org.gradle.jvmargs=-Xmx8192M 32 | -------------------------------------------------------------------------------- /src/rider/main/resources/META-INF/pluginIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Layer 1 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Andrew Rublyov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/mgcb/previewer/listeners/MgcbPendingUpdateListenerImpl.kt: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.plugins.monogame.mgcb.previewer.listeners 2 | 3 | import com.intellij.openapi.project.Project 4 | import com.intellij.openapi.vfs.VirtualFile 5 | import com.intellij.psi.PsiManager 6 | import com.jetbrains.rider.util.idea.getService 7 | import me.seclerp.rider.plugins.monogame.mgcb.previewer.MgcbPreviewerTopics 8 | import me.seclerp.rider.plugins.monogame.mgcb.previewer.services.MgcbAnalyzer 9 | import me.seclerp.rider.plugins.monogame.mgcb.psi.MgcbFile 10 | 11 | class MgcbPendingUpdateListenerImpl( 12 | private val project: Project 13 | ) : MgcbPendingUpdateListener { 14 | private val publisher = project.messageBus.syncPublisher(MgcbPreviewerTopics.MGCB_PROCESSED_UPDATE_TOPIC) 15 | private val mgcbAnalyzer = project.getService() 16 | 17 | override fun handle(file: VirtualFile) { 18 | val mgcbFile = PsiManager.getInstance(project).findFile(file) as MgcbFile 19 | val mgcbModel = mgcbAnalyzer.analyzeFile(mgcbFile) 20 | 21 | publisher.handle(file, mgcbModel) 22 | } 23 | } -------------------------------------------------------------------------------- /src/rider/main/resources/fileTemplates/Effect File.fx.ft: -------------------------------------------------------------------------------- 1 | \#if OPENGL 2 | \#define SV_POSITION POSITION 3 | \#define VS_SHADERMODEL vs_3_0 4 | \#define PS_SHADERMODEL ps_3_0 5 | \#else 6 | \#define VS_SHADERMODEL vs_4_0_level_9_1 7 | \#define PS_SHADERMODEL ps_4_0_level_9_1 8 | #endif 9 | 10 | matrix WorldViewProjection; 11 | 12 | struct VertexShaderInput 13 | { 14 | float4 Position : POSITION0; 15 | float4 Color : COLOR0; 16 | }; 17 | 18 | struct VertexShaderOutput 19 | { 20 | float4 Position : SV_POSITION; 21 | float4 Color : COLOR0; 22 | }; 23 | 24 | VertexShaderOutput MainVS(in VertexShaderInput input) 25 | { 26 | VertexShaderOutput output = (VertexShaderOutput)0; 27 | 28 | output.Position = mul(input.Position, WorldViewProjection); 29 | output.Color = input.Color; 30 | 31 | return output; 32 | } 33 | 34 | float4 MainPS(VertexShaderOutput input) : COLOR 35 | { 36 | return input.Color; 37 | } 38 | 39 | technique BasicColorDrawing 40 | { 41 | pass P0 42 | { 43 | VertexShader = compile VS_SHADERMODEL MainVS(); 44 | PixelShader = compile PS_SHADERMODEL MainPS(); 45 | } 46 | }; -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/mgcb/previewer/MgcbEditorWithPreview.kt: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.plugins.monogame.mgcb.previewer 2 | 3 | import com.intellij.openapi.fileEditor.TextEditor 4 | import com.intellij.openapi.fileEditor.TextEditorWithPreview 5 | import com.intellij.openapi.project.Project 6 | import com.intellij.openapi.vfs.VirtualFile 7 | import me.seclerp.rider.plugins.monogame.mgcb.previewer.listeners.MgcbDocumentListener 8 | 9 | class MgcbEditorWithPreview( 10 | private val project: Project, 11 | private val file: VirtualFile, 12 | textEditor: TextEditor, 13 | previewer: MgcbEditorPreviewer 14 | ) : TextEditorWithPreview(textEditor, previewer, MgcbEditorWithPreview::javaClass.name, Layout.SHOW_EDITOR_AND_PREVIEW) { 15 | private val documentListener = MgcbDocumentListener(project, file) 16 | 17 | init { 18 | textEditor.editor.document.addDocumentListener(documentListener) 19 | } 20 | 21 | override val isShowFloatingToolbar = false 22 | 23 | override fun dispose() { 24 | textEditor.editor.document.removeDocumentListener(documentListener) 25 | 26 | super.dispose() 27 | } 28 | } -------------------------------------------------------------------------------- /src/rider/main/resources/messages/MonoGameUiBundle.properties: -------------------------------------------------------------------------------- 1 | configurable.group.tools.monogame.settings.description=Settings related to MonoGame tools for Rider 2 | configurable.group.tools.monogame.settings.display.name=MonoGame 3 | 4 | loading.title=Loading... 5 | settings.mgcb.display.name=MGCB 6 | settings.mgcb.editor.group=MonoGame Content Builder Editor (mgcb-editor) 7 | settings.mgcb.editor.group.global=Global tools 8 | settings.mgcb.editor.group.solution=Solution-level tools 9 | settings.mgcb.editor.group.project=Project-level tools 10 | 11 | command.execution.title=Executing command 12 | command.execution.error.title=Command Failed 13 | command.execution.error.message.exception=Error occurred while command execution: {0}.\ 14 | See logs for details. 15 | command.execution.error.message.code=Status code doesn't indicate success: {0}.\ 16 | See logs for details. 17 | 18 | command.mgcb.open.title=Open in external MGCB editor 19 | command.mgcb.open.missing.editor.title=mgcb-editor tool is missing 20 | 21 | templates.loading.templates=Loading templates... 22 | templates.no.templates.found=No MonoGame templates were found. 23 | templates.no.templates.install.hint=Please install them using the following command: 24 | -------------------------------------------------------------------------------- /protocol/src/main/kotlin/model/rider/MonoGameRiderModel.kt: -------------------------------------------------------------------------------- 1 | package model.rider 2 | 3 | import com.jetbrains.rider.model.nova.ide.SolutionModel 4 | import com.jetbrains.rd.generator.nova.* 5 | import com.jetbrains.rd.generator.nova.csharp.CSharp50Generator 6 | import com.jetbrains.rd.generator.nova.kotlin.Kotlin11Generator 7 | 8 | @Suppress("unused") 9 | object MonoGameRiderModel : Ext(SolutionModel.Solution) { 10 | 11 | private val ToolDefinition = structdef { 12 | field("packageId", PredefinedType.string) 13 | field("commandName", PredefinedType.string) 14 | field("version", PredefinedType.string) 15 | } 16 | 17 | private val MgcbEditorToolset = aggregatedef("MgcbEditorToolset") { 18 | property("editor", ToolDefinition.nullable) 19 | } 20 | 21 | private val projectId = PredefinedType.guid 22 | 23 | init { 24 | setting(CSharp50Generator.Namespace, "Rider.Plugins.MonoGame") 25 | setting(Kotlin11Generator.Namespace, "me.seclerp.rider.plugins.monogame") 26 | 27 | field("mgcbGlobalToolset", MgcbEditorToolset) 28 | field("mgcbSolutionToolset", MgcbEditorToolset) 29 | map("mgcbProjectsToolsets", projectId, MgcbEditorToolset) 30 | } 31 | } -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/extensions/workspaceModel/WorkspaceModelExtensions.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("UnstableApiUsage") 2 | 3 | package me.seclerp.rider.extensions.workspaceModel 4 | 5 | import com.intellij.openapi.project.Project 6 | import com.intellij.openapi.vfs.VirtualFile 7 | import com.intellij.platform.backend.workspace.WorkspaceModel 8 | import com.intellij.platform.backend.workspace.virtualFile 9 | import com.jetbrains.rider.projectView.workspace.ProjectModelEntity 10 | import com.jetbrains.rider.projectView.workspace.containingProjectEntity 11 | import com.jetbrains.rider.projectView.workspace.getContentRootUrl 12 | import com.jetbrains.rider.projectView.workspace.getProjectModelEntities 13 | 14 | fun WorkspaceModel.containingProjectEntity(file: VirtualFile, project: Project): ProjectModelEntity? { 15 | return getProjectModelEntities(file, project) 16 | .firstOrNull() 17 | ?.containingProjectEntity() 18 | } 19 | 20 | fun WorkspaceModel.containingProjectDirectory(file: VirtualFile, project: Project): VirtualFile? { 21 | return containingProjectEntity(file, project) 22 | ?.getContentRootUrl(WorkspaceModel.getInstance(project).getVirtualFileUrlManager()) 23 | ?.virtualFile 24 | } -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/templates/MonoGameTemplateType.kt: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.plugins.monogame.templates 2 | 3 | import com.jetbrains.rd.util.lifetime.Lifetime 4 | import com.jetbrains.rider.model.RdProjectTemplate 5 | import com.jetbrains.rider.projectView.projectTemplates.NewProjectDialogContext 6 | import com.jetbrains.rider.projectView.projectTemplates.ProjectTemplatesSharedModel 7 | import com.jetbrains.rider.projectView.projectTemplates.templateTypes.PredefinedProjectTemplateType 8 | import com.jetbrains.rider.projectView.projectTemplates.utils.hasClassification 9 | import me.seclerp.rider.plugins.monogame.MonoGameIcons 10 | 11 | internal class MonoGameTemplateType : PredefinedProjectTemplateType() { 12 | override val icon = MonoGameIcons.MgcbFile 13 | override val name = "MonoGame" 14 | override val order = 50 15 | 16 | override fun acceptableForTemplate(projectTemplate: RdProjectTemplate): Boolean { 17 | return projectTemplate.hasClassification("MonoGame") 18 | } 19 | 20 | override fun createGenerator(lifetime: Lifetime, context: NewProjectDialogContext, sharedModel: ProjectTemplatesSharedModel) = 21 | MonoGameProjectTemplateGenerator(lifetime, context, sharedModel, projectTemplates) 22 | } -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/mgcb/actions/commands/MgcbProcessAdapter.kt: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.plugins.monogame.mgcb.actions.commands 2 | 3 | import com.intellij.execution.process.ProcessAdapter 4 | import com.intellij.execution.process.ProcessEvent 5 | import com.intellij.execution.process.ProcessOutputTypes 6 | import com.intellij.openapi.util.Key 7 | 8 | class MgcbProcessAdapter(private val commandString: String, private val listener: (CliCommandResult) -> Unit) : ProcessAdapter() { 9 | private val outputBuilder = StringBuilder() 10 | private val errorBuilder = StringBuilder() 11 | 12 | override fun onTextAvailable(event: ProcessEvent, outputType: Key<*>) { 13 | val builder = 14 | when (outputType) { 15 | ProcessOutputTypes.STDERR -> errorBuilder 16 | else -> outputBuilder 17 | } 18 | 19 | builder.append(event.text) 20 | } 21 | 22 | override fun processTerminated(event: ProcessEvent) { 23 | val cliCommandResult = CliCommandResult( 24 | commandString, 25 | event.exitCode, 26 | outputBuilder.toString(), 27 | event.exitCode == 0, 28 | errorBuilder.toString() 29 | ) 30 | 31 | listener(cliCommandResult) 32 | } 33 | } -------------------------------------------------------------------------------- /test/MonoGameSolution/Desktop381/Desktop381.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | WinExe 4 | net6.0 5 | Major 6 | false 7 | false 8 | MonoGameProjectDesktop381 9 | 10 | 11 | app.manifest 12 | Icon.ico 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/mgcb/previewer/MgcbEditorProvider.kt: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.plugins.monogame.mgcb.previewer 2 | 3 | import com.intellij.openapi.fileEditor.FileEditor 4 | import com.intellij.openapi.fileEditor.FileEditorPolicy 5 | import com.intellij.openapi.fileEditor.FileEditorProvider 6 | import com.intellij.openapi.fileEditor.TextEditor 7 | import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider 8 | import com.intellij.openapi.project.DumbAware 9 | import com.intellij.openapi.project.Project 10 | import com.intellij.openapi.vfs.VirtualFile 11 | import me.seclerp.rider.plugins.monogame.mgcb.MgcbFileType 12 | 13 | class MgcbEditorProvider : FileEditorProvider, DumbAware { 14 | override fun getEditorTypeId() = "MgcbEditorWithPreviewer" 15 | override fun getPolicy() = FileEditorPolicy.HIDE_DEFAULT_EDITOR 16 | 17 | override fun accept(project: Project, file: VirtualFile) = 18 | file.fileType is MgcbFileType 19 | 20 | override fun createEditor(project: Project, file: VirtualFile): FileEditor { 21 | val textEditor = TextEditorProvider.getInstance().createEditor(project, file) as TextEditor 22 | val previewerEditor = MgcbEditorPreviewer(project, file) 23 | 24 | return MgcbEditorWithPreview(project, file, textEditor, previewerEditor) 25 | } 26 | } -------------------------------------------------------------------------------- /test/MonoGameSolution/Desktop38/Desktop38.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | WinExe 4 | netcoreapp3.1 5 | false 6 | false 7 | 8 | 9 | app.manifest 10 | Icon.ico 11 | 12 | 13 | dotnet --roll-forward Major 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/mgcb/Mgcb.bnf: -------------------------------------------------------------------------------- 1 | { 2 | parserClass="me.seclerp.rider.plugins.monogame.mgcb.parser.MgcbParser" 3 | 4 | extends="com.intellij.extapi.psi.ASTWrapperPsiElement" 5 | 6 | psiClassPrefix="Mgcb" 7 | psiImplClassSuffix="Impl" 8 | psiPackage="me.seclerp.rider.plugins.monogame.mgcb.psi" 9 | psiImplPackage="me.seclerp.rider.plugins.monogame.mgcb.psi" 10 | 11 | elementTypeHolderClass="me.seclerp.rider.plugins.monogame.mgcb.psi.MgcbTypes" 12 | elementTypeClass="me.seclerp.rider.plugins.monogame.mgcb.psi.MgcbElementType" 13 | tokenTypeClass="me.seclerp.rider.plugins.monogame.mgcb.psi.MgcbTokenType" 14 | } 15 | 16 | mgcbFile ::= item_* 17 | 18 | private item_ ::= ( 19 | option 20 | | set_instruction 21 | | if_instruction 22 | | COMMENT 23 | | WHITE_SPACE 24 | ) 25 | 26 | option ::= ( 27 | OPTION_KEY OPTION_SEPARATOR OPTION_VALUE 28 | | OPTION_KEY OPTION_SEPARATOR 29 | | OPTION_KEY 30 | ) 31 | 32 | set_instruction ::= ( 33 | SET_KEYWORD WHITE_SPACE PREPROCESSOR_IDENTIFIER EQ PREPROCESSOR_VALUE 34 | | SET_KEYWORD WHITE_SPACE PREPROCESSOR_IDENTIFIER 35 | ) 36 | 37 | if_instruction ::= ( 38 | IF_KEYWORD WHITE_SPACE PREPROCESSOR_IDENTIFIER EQ PREPROCESSOR_VALUE WHITE_SPACE item_* ENDIF_KEYWORD 39 | | IF_KEYWORD WHITE_SPACE PREPROCESSOR_IDENTIFIER WHITE_SPACE item_* ENDIF_KEYWORD 40 | ) -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/CommonExtensions.kt: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.plugins.monogame 2 | 3 | import com.intellij.util.ui.ListTableModel 4 | 5 | fun String.substringAfter(regex: Regex, missingDelimiterValue: String = this): String { 6 | val firstMatch = regex.find(this) ?: return missingDelimiterValue 7 | 8 | return substring(firstMatch.range.first + 1) 9 | } 10 | 11 | fun String.substringBefore(regex: Regex, missingDelimiterValue: String = this): String { 12 | val firstMatch = regex.find(this) ?: return missingDelimiterValue 13 | 14 | return substring(0, firstMatch.range.last) 15 | } 16 | 17 | fun String.substringBeforeLast(regex: Regex, missingDelimiterValue: String = this): String { 18 | val matches = regex.findAll(this) 19 | 20 | if (!matches.any()) { 21 | return missingDelimiterValue 22 | } 23 | 24 | return substring(0, matches.last().range.last) 25 | } 26 | 27 | fun String.substringAfterLast(regex: Regex, missingDelimiterValue: String = this): String { 28 | val matches = regex.findAll(this) 29 | 30 | if (!matches.any()) { 31 | return missingDelimiterValue 32 | } 33 | 34 | return substring(matches.last().range.last + 1) 35 | } 36 | 37 | fun ListTableModel.removeAllRows() { 38 | val cachedRowCount = rowCount 39 | for (row in 0 until cachedRowCount) 40 | removeRow(0) 41 | } -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "rider-monogame" 2 | 3 | pluginManagement { 4 | val rdVersion: String by settings 5 | val rdKotlinVersion: String by settings 6 | val intellijPlatformGradleVersion: String by settings 7 | val gradleJvmWrapperVersion: String by settings 8 | 9 | repositories { 10 | maven("https://cache-redirector.jetbrains.com/intellij-dependencies") 11 | maven("https://cache-redirector.jetbrains.com/plugins.gradle.org") 12 | maven("https://cache-redirector.jetbrains.com/maven-central") 13 | 14 | if (rdVersion == "SNAPSHOT") { 15 | mavenLocal() 16 | } 17 | } 18 | 19 | plugins { 20 | id("com.jetbrains.rdgen") version rdVersion 21 | id("org.jetbrains.kotlin.jvm") version rdKotlinVersion 22 | id("org.jetbrains.intellij.platform") version intellijPlatformGradleVersion 23 | id("me.filippov.gradle.jvm.wrapper") version gradleJvmWrapperVersion 24 | } 25 | 26 | resolutionStrategy { 27 | eachPlugin { 28 | when (requested.id.name) { 29 | // This required to correctly rd-gen plugin resolution. 30 | // Maybe we should switch our naming to match Gradle plugin naming convention. 31 | "rdgen" -> { 32 | useModule("com.jetbrains.rd:rd-gen:${rdVersion}") 33 | } 34 | } 35 | } 36 | } 37 | } 38 | 39 | include(":protocol") -------------------------------------------------------------------------------- /src/dotnet/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Latest 5 | MSB3277 6 | true 7 | false 8 | None 9 | 10 | obj\$(MSBuildProjectName)\ 11 | $(DefaultItemExcludes);obj\** 12 | bin\$(MSBuildProjectName)\$(Configuration)\ 13 | false 14 | 15 | 16 | 17 | 18 | true 19 | true 20 | 21 | 22 | 23 | TRACE;DEBUG;JET_MODE_ASSERT 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /test/MonoGameSolution/Desktop381/Game1.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using Microsoft.Xna.Framework.Input; 4 | 5 | namespace MonoGameProjectDesktop381; 6 | 7 | public class Game1 : Game 8 | { 9 | private GraphicsDeviceManager _graphics; 10 | private SpriteBatch _spriteBatch; 11 | 12 | public Game1() 13 | { 14 | _graphics = new GraphicsDeviceManager(this); 15 | Content.RootDirectory = "Content"; 16 | IsMouseVisible = true; 17 | } 18 | 19 | protected override void Initialize() 20 | { 21 | // TODO: Add your initialization logic here 22 | 23 | base.Initialize(); 24 | } 25 | 26 | protected override void LoadContent() 27 | { 28 | _spriteBatch = new SpriteBatch(GraphicsDevice); 29 | 30 | // TODO: use this.Content to load your game content here 31 | } 32 | 33 | protected override void Update(GameTime gameTime) 34 | { 35 | if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || 36 | Keyboard.GetState().IsKeyDown(Keys.Escape)) 37 | Exit(); 38 | 39 | // TODO: Add your update logic here 40 | 41 | base.Update(gameTime); 42 | } 43 | 44 | protected override void Draw(GameTime gameTime) 45 | { 46 | GraphicsDevice.Clear(Color.CornflowerBlue); 47 | 48 | // TODO: Add your drawing code here 49 | 50 | base.Draw(gameTime); 51 | } 52 | } -------------------------------------------------------------------------------- /test/MonoGameSolution/Desktop38/Game1.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using Microsoft.Xna.Framework.Input; 4 | 5 | namespace Desktop38 6 | { 7 | public class Game1 : Game 8 | { 9 | private GraphicsDeviceManager _graphics; 10 | private SpriteBatch _spriteBatch; 11 | 12 | public Game1() 13 | { 14 | _graphics = new GraphicsDeviceManager(this); 15 | Content.RootDirectory = "Content"; 16 | IsMouseVisible = true; 17 | } 18 | 19 | protected override void Initialize() 20 | { 21 | // TODO: Add your initialization logic here 22 | 23 | base.Initialize(); 24 | } 25 | 26 | protected override void LoadContent() 27 | { 28 | _spriteBatch = new SpriteBatch(GraphicsDevice); 29 | 30 | // TODO: use this.Content to load your game content here 31 | } 32 | 33 | protected override void Update(GameTime gameTime) 34 | { 35 | if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape)) 36 | Exit(); 37 | 38 | // TODO: Add your update logic here 39 | 40 | base.Update(gameTime); 41 | } 42 | 43 | protected override void Draw(GameTime gameTime) 44 | { 45 | GraphicsDevice.Clear(Color.CornflowerBlue); 46 | 47 | // TODO: Add your drawing code here 48 | 49 | base.Draw(gameTime); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /test/MonoGameSolution/MonoGameSolution.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.31903.59 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Desktop381", "Desktop381\Desktop381.csproj", "{A8AFF547-57D0-44C3-A6CE-23860F2D638E}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Desktop38", "Desktop38\Desktop38.csproj", "{3E97F549-0D4F-4A53-B863-81AAFE51B6DB}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(SolutionProperties) = preSolution 16 | HideSolutionNode = FALSE 17 | EndGlobalSection 18 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 19 | {A8AFF547-57D0-44C3-A6CE-23860F2D638E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 20 | {A8AFF547-57D0-44C3-A6CE-23860F2D638E}.Debug|Any CPU.Build.0 = Debug|Any CPU 21 | {A8AFF547-57D0-44C3-A6CE-23860F2D638E}.Release|Any CPU.ActiveCfg = Release|Any CPU 22 | {A8AFF547-57D0-44C3-A6CE-23860F2D638E}.Release|Any CPU.Build.0 = Release|Any CPU 23 | {3E97F549-0D4F-4A53-B863-81AAFE51B6DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 24 | {3E97F549-0D4F-4A53-B863-81AAFE51B6DB}.Debug|Any CPU.Build.0 = Debug|Any CPU 25 | {3E97F549-0D4F-4A53-B863-81AAFE51B6DB}.Release|Any CPU.ActiveCfg = Release|Any CPU 26 | {3E97F549-0D4F-4A53-B863-81AAFE51B6DB}.Release|Any CPU.Build.0 = Release|Any CPU 27 | EndGlobalSection 28 | EndGlobal 29 | -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/mgcb/previewer/MgcbEditorFloatingToolbarProvider.kt: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.plugins.monogame.mgcb.previewer 2 | 3 | import com.intellij.openapi.Disposable 4 | import com.intellij.openapi.actionSystem.CommonDataKeys 5 | import com.intellij.openapi.actionSystem.DataContext 6 | import com.intellij.openapi.actionSystem.DefaultActionGroup 7 | import com.intellij.openapi.editor.Editor 8 | import com.intellij.openapi.editor.toolbar.floating.FloatingToolbarComponent 9 | import com.intellij.openapi.editor.toolbar.floating.FloatingToolbarProvider 10 | import com.intellij.openapi.fileEditor.FileDocumentManager 11 | import com.intellij.testFramework.LightVirtualFileBase 12 | import me.seclerp.rider.plugins.monogame.mgcb.MgcbFileType 13 | import me.seclerp.rider.plugins.monogame.mgcb.actions.OpenExternalEditorAction 14 | 15 | class MgcbEditorFloatingToolbarProvider : FloatingToolbarProvider { 16 | override val actionGroup = DefaultActionGroup(OpenExternalEditorAction()) 17 | override val autoHideable = false 18 | 19 | override fun register(dataContext: DataContext, component: FloatingToolbarComponent, parentDisposable: Disposable) { 20 | val editor = dataContext.getData(CommonDataKeys.EDITOR) ?: return 21 | if (editor.isMgcbFileEditor()) { 22 | component.scheduleShow() 23 | } 24 | } 25 | 26 | private fun Editor.isMgcbFileEditor(): Boolean { 27 | val documentManager = FileDocumentManager.getInstance() 28 | val virtualFile = documentManager.getFile(document) ?: return false 29 | return virtualFile !is LightVirtualFileBase 30 | && virtualFile.isValid 31 | && virtualFile.fileType == MgcbFileType.Instance 32 | } 33 | } -------------------------------------------------------------------------------- /test/MonoGameSolution/Desktop38/app.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | true/pm 39 | permonitorv2,permonitor 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /test/MonoGameSolution/Desktop381/app.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | true/pm 39 | permonitorv2,permonitor 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/mgcb/MgcbParserDefinition.kt: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.plugins.monogame.mgcb 2 | 3 | import com.intellij.lang.ASTNode 4 | import com.intellij.lang.ParserDefinition 5 | import com.intellij.lang.PsiParser 6 | import com.intellij.lexer.Lexer 7 | import com.intellij.openapi.project.Project 8 | import com.intellij.psi.FileViewProvider 9 | import com.intellij.psi.PsiElement 10 | import com.intellij.psi.PsiFile 11 | import com.intellij.psi.TokenType 12 | import com.intellij.psi.tree.IFileElementType 13 | import com.intellij.psi.tree.TokenSet 14 | import me.seclerp.rider.plugins.monogame.mgcb.parser.MgcbParser 15 | import me.seclerp.rider.plugins.monogame.mgcb.psi.MgcbFile 16 | import me.seclerp.rider.plugins.monogame.mgcb.psi.MgcbTypes 17 | 18 | class MgcbParserDefinition : ParserDefinition { 19 | val WHITE_SPACES = TokenSet.create(TokenType.WHITE_SPACE) 20 | 21 | val COMMENTS = TokenSet.create(MgcbTypes.COMMENT) 22 | 23 | val FILE = IFileElementType(MgcbLanguage.Instance) 24 | 25 | override fun createLexer(project: Project?): Lexer = MgcbLexerAdapter() 26 | 27 | override fun getWhitespaceTokens(): TokenSet = WHITE_SPACES 28 | 29 | override fun getCommentTokens(): TokenSet = COMMENTS 30 | 31 | override fun getStringLiteralElements(): TokenSet = TokenSet.EMPTY 32 | 33 | override fun createParser(project: Project?): PsiParser = MgcbParser() 34 | 35 | override fun getFileNodeType(): IFileElementType = FILE 36 | 37 | override fun createFile(viewProvider: FileViewProvider): PsiFile = MgcbFile(viewProvider) 38 | 39 | override fun spaceExistenceTypeBetweenTokens(left: ASTNode?, right: ASTNode?): ParserDefinition.SpaceRequirements = 40 | ParserDefinition.SpaceRequirements.MAY 41 | 42 | override fun createElement(node: ASTNode?): PsiElement = MgcbTypes.Factory.createElement(node) 43 | } -------------------------------------------------------------------------------- /protocol/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import com.jetbrains.rd.generator.gradle.RdGenTask 2 | 3 | plugins { 4 | id("com.jetbrains.rdgen") 5 | id("org.jetbrains.kotlin.jvm") 6 | } 7 | 8 | repositories { 9 | maven("https://cache-redirector.jetbrains.com/intellij-dependencies") 10 | maven("https://cache-redirector.jetbrains.com/maven-central") 11 | } 12 | 13 | val repoRoot: File = projectDir.parentFile 14 | 15 | sourceSets { 16 | main { 17 | kotlin { 18 | srcDir(repoRoot.resolve("protocol/src/main/kotlin/model")) 19 | } 20 | } 21 | } 22 | 23 | rdgen { 24 | verbose = true 25 | packages = "model" 26 | 27 | generator { 28 | language = "kotlin" 29 | transform = "asis" 30 | root = "com.jetbrains.rider.model.nova.ide.IdeRoot" 31 | namespace = "com.jetbrains.rider.plugins.efcore.model" 32 | directory = file("$repoRoot/src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/rd").absolutePath 33 | } 34 | 35 | generator { 36 | language = "csharp" 37 | transform = "reversed" 38 | root = "com.jetbrains.rider.model.nova.ide.IdeRoot" 39 | namespace = "Rider.Plugins.EfCore" 40 | directory = file("$repoRoot/src/dotnet/Rider.Plugins.MonoGame/Rd").absolutePath 41 | } 42 | } 43 | 44 | tasks.withType { 45 | dependsOn(sourceSets["main"].runtimeClasspath) 46 | classpath(sourceSets["main"].runtimeClasspath) 47 | } 48 | 49 | dependencies { 50 | val rdVersion: String by project 51 | val rdKotlinVersion: String by project 52 | 53 | implementation("com.jetbrains.rd:rd-gen:$rdVersion") 54 | implementation("org.jetbrains.kotlin:kotlin-stdlib:$rdKotlinVersion") 55 | implementation( 56 | project( 57 | mapOf( 58 | "path" to ":", 59 | "configuration" to "riderModel" 60 | ) 61 | ) 62 | ) 63 | } -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/mgcb/toolset/DotNetToolsVersion.kt: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.plugins.monogame.mgcb.toolset 2 | 3 | import org.jetbrains.annotations.NonNls 4 | 5 | class DotNetToolsVersion private constructor(private val version: String) : Comparable { 6 | companion object { 7 | @NonNls 8 | val SEMVER_REGEX = 9 | Regex("(\\d+)\\.(\\d+)\\.(\\d+)(?:-([\\dA-Za-z-]+(?:\\.[\\dA-Za-z-]+)*))?(?:\\+[\\dA-Za-z-]+)?") 10 | 11 | fun parse(version: String): DotNetToolsVersion? { 12 | val match = SEMVER_REGEX.find(version) ?: return null 13 | 14 | if (match.groups.size < 3) { 15 | return null 16 | } 17 | 18 | return DotNetToolsVersion(version) 19 | } 20 | } 21 | 22 | override operator fun compareTo(that: DotNetToolsVersion?): Int { 23 | if (that == null) return 1 24 | val thisParts = version.split("\\.".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() 25 | val thatParts = version.split("\\.".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() 26 | val length = Math.max(thisParts.size, thatParts.size) 27 | for (i in 0 until length) { 28 | val thisPart = if (i < thisParts.size) thisParts[i].toInt() else 0 29 | val thatPart = if (i < thatParts.size) thatParts[i].toInt() else 0 30 | if (thisPart < thatPart) return -1 31 | if (thisPart > thatPart) return 1 32 | } 33 | return 0 34 | } 35 | 36 | override fun equals(other: Any?): Boolean { 37 | if (this === other) return true 38 | if (other == null) return false 39 | return if (this.javaClass != other.javaClass) false else this.compareTo(other as DotNetToolsVersion) == 0 40 | } 41 | 42 | override fun toString() = version 43 | override fun hashCode() = version.hashCode() 44 | } -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/mgcb/actions/commands/CliCommand.kt: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.plugins.monogame.mgcb.actions.commands 2 | 3 | import com.intellij.execution.configurations.GeneralCommandLine 4 | import com.intellij.execution.process.OSProcessHandler 5 | import com.intellij.execution.util.ExecUtil 6 | import com.intellij.openapi.progress.ProgressIndicator 7 | import com.intellij.openapi.progress.ProgressManager 8 | import com.intellij.openapi.progress.Task 9 | import com.intellij.openapi.project.Project 10 | 11 | @Suppress("UnstableApiUsage") 12 | open class CliCommand( 13 | private val command: GeneralCommandLine, 14 | ) { 15 | fun executeLater(listener: (CliCommandResult) -> Unit = {}) { 16 | try { 17 | val handler = OSProcessHandler(command) 18 | handler.addProcessListener(MgcbProcessAdapter(command.commandLineString, listener)) 19 | handler.startNotify() 20 | } catch(e: Exception) { 21 | e.printStackTrace() 22 | listener(CliCommandResult(command.commandLineString, -1, e.toString(), false)) 23 | } 24 | } 25 | 26 | fun executeUnderProgress(project: Project, title: String, listener: (CliCommandResult) -> Unit = {}) { 27 | ProgressManager.getInstance().run(object : Task.Backgroundable(project, title, false) { 28 | override fun run(progress: ProgressIndicator) { 29 | try { 30 | val output = ExecUtil.execAndGetOutput(command) 31 | val commandResult = CliCommandResult(command.commandLineString, output.exitCode, output.stdout, output.exitCode == 0, output.stderr) 32 | listener(commandResult) 33 | } catch(e: Exception) { 34 | e.printStackTrace() 35 | listener(CliCommandResult(command.commandLineString, -1, e.toString(), false)) 36 | } 37 | } 38 | }) 39 | } 40 | } -------------------------------------------------------------------------------- /src/dotnet/Rider.Plugins.MonoGame.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Rider.Plugins.MonoGame", "Rider.Plugins.MonoGame\Rider.Plugins.MonoGame.csproj", "{084172D1-A9C6-46D0-96AD-05C5B09A5E5D}" 4 | EndProject 5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Rider.Plugins.MonoGame.Tests", "Rider.Plugins.MonoGame.Tests\Rider.Plugins.MonoGame.Tests.csproj", "{01C3DEF5-50B2-47CB-9467-19BC6DDF9D3D}" 6 | EndProject 7 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "misc", "misc", "{4A9ABB95-3762-448B-B5BF-099E46DB22DE}" 8 | ProjectSection(SolutionItems) = preProject 9 | Plugin.props = Plugin.props 10 | ..\..\README.md = ..\..\README.md 11 | ..\..\CHANGELOG.md = ..\..\CHANGELOG.md 12 | ..\rider\main\resources\META-INF\plugin.xml = ..\rider\main\resources\META-INF\plugin.xml 13 | ..\..\gradle.properties = ..\..\gradle.properties 14 | RiderSdk.PackageVersions.Generated.props = RiderSdk.PackageVersions.Generated.props 15 | Directory.Build.props = Directory.Build.props 16 | EndProjectSection 17 | EndProject 18 | Global 19 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 20 | Debug|Any CPU = Debug|Any CPU 21 | Release|Any CPU = Release|Any CPU 22 | EndGlobalSection 23 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 24 | {084172D1-A9C6-46D0-96AD-05C5B09A5E5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {084172D1-A9C6-46D0-96AD-05C5B09A5E5D}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {084172D1-A9C6-46D0-96AD-05C5B09A5E5D}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {084172D1-A9C6-46D0-96AD-05C5B09A5E5D}.Release|Any CPU.Build.0 = Release|Any CPU 28 | {01C3DEF5-50B2-47CB-9467-19BC6DDF9D3D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {01C3DEF5-50B2-47CB-9467-19BC6DDF9D3D}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {01C3DEF5-50B2-47CB-9467-19BC6DDF9D3D}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {01C3DEF5-50B2-47CB-9467-19BC6DDF9D3D}.Release|Any CPU.Build.0 = Release|Any CPU 32 | EndGlobalSection 33 | EndGlobal 34 | -------------------------------------------------------------------------------- /src/rider/main/resources/xmlSchema/SpriteFont.xsd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /docs/Previewer Architecture.drawio: -------------------------------------------------------------------------------- 1 | 7Vtbl6I4EP41PrYHEoL4OH2bfeg+6x535/IYISIzSDwhttq/foMEuQRoulWEdvqlSZGEUFXfV0lRDuDdcvuV4dXimTrEHwDN2Q7g/QAAHSJD/Isku1hiASlwmefITqlg6r0SKdSkdO05JMx15JT63FvlhTYNAmLznAwzRjf5bnPq55+6wi5RBFMb+6r0u+fwRfIWo1T+F/HcRfJk3RzHd5Y46SzfJFxgh24yIvgwgHeMUh5fLbd3xI+Ul+glHvdYcfewMEYC3mTAj8mrNf31yID59dvz93/59AYbN7p8j5DvkjcmjlCAbFLGF9SlAfYfUukto+vAIdG0mmilfZ4oXQmhLoS/COc7aU285lSIFnzpy7tixWz3Ixo/REnzp5xu37jf5lq7bGtCmLcknDApjF8gWnWlYqQopGtmkxptQOlgmLmE12lNP9hPOD6hYjlsJwYy4mPuveQXgqUHuod+cugXxvAu02FFvYCHmZknkUB0kGBCllyfhJJhFAxe6G8kTlreX1zEK0hamVdJRXsneodDySW+YH8t1fDs2rNHzydPXshJIMxWdLi8O20WHifTFd4baiNIJe86cnrCONnW21s1T6JHPa9HCCVCNynAdakrbZEBt6lVWzSj1fcrzewTCE+IN9QQb/BIuB1lnJHi0d88xtfYj5xa3Lhb4MAVhigz4ROeiXiYUzv2PTcQ17bQVsRht5EveyLgfJE3lp7jxBYmofeKZ/v5IsVLfhCTo9sBuq8Dg4yGcnAag7JGqvbESuTcaENdgxI97yO7lJ0k6m4KI+h8HgonKFrrBJSEVAM+ThVzifC8ii7tne8JYDH4NhfNYgg+zQ4CbP9298D8e83FNETKwxiDOjoRgaE8gQFNJTCzhL+sc/EXHF8pgVlNCawCV+0wmGEqCPhUpBW7Xx1rASsHmIR7Pkph+XUnKGyLzyzFmnMRicBwKTZaA2D6PGIisc0y3ejqntrrZaS/KsILKCcpR4F29mDib4i6RWIJqV4dienJufotFkPgkiyWLDPj96q3T0jgeIEruv23crBwbKXHP2sihitg4Iz+PpzwQd5GCVKWWzdKcgznPt3YC8z4UDwCz3BIKjBzAqDAUeEQNy45rGglOBmdDScXCfan9PdxQ3+Pz88X8/dxKc/rFTwfHbNjn+/MQVtQeoHkLXDxo7amqKXDLH/uhBdoSv2gYovVDhSASv11W54OQgGiUfegoCtquWYoNE3+Qq3c0i1BQU2wRlCA/YEC0mHnoADUJF9drJ0w8uKRTYkyL3uogqig2XFJanvU5qEKlG9iqpi7o4qF4+Jp9eKKTZ7fkAc6qlhkWJ1TrNWnqHjCAAgbB0B0yQBoGIrjf65kplWLmCiZaUoVfDR9ef4UJSzfpVTF027mKHWjczlKo1en11OSk9GUnC6aszHUfeSnIiej4uyTISfdzEHm2E8tLXCVGlDqdlLd5CqodY+r/qQXlE1TZ2vL9FEfasvUSo63MuJ1+C3e6UyG5JD7qClQM5EK5bOlRwzwB8rFYo7OQhmgPkBZLUl5K6PfSygftladgXKvqhzODuVRp6EMYR+gXJ47r/si0UsoQ61jUEbXWjZuNC27rLJoO8kA1Kvf1lzCPvqxVQUfYtUiS47G9awKRnpd//OwKrrWzxDNwX3sr0JacR5kGe07j6GWCKuBVZUkJZPahFGbhGGUtfxk5ZOmpQbt0vLJ85UZG30C9rk34Ag0RLtR8VmspVAOFEBF+9hDtXE5krqzdUV5FKCyIuITFdyIZvqb5pjQ0l+Gw4f/AQ== -------------------------------------------------------------------------------- /src/dotnet/Rider.Plugins.MonoGame/Templates.DotSettings: -------------------------------------------------------------------------------- 1 | 2 | True 3 | True 4 | MonoGameRider Template 5 | True 6 | 0 7 | True 8 | True 9 | 2.0 10 | InCSharpTypeMember 11 | monogamerider_template 12 | True 13 | private string MonoGameRider { get; set; }$END$ 14 | -------------------------------------------------------------------------------- /src/rider/main/resources/fileTemplates/SpriteFont File.spritefont.ft: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 14 | Arial 15 | 16 | 20 | 12 21 | 22 | 26 | 0 27 | 28 | 32 | true 33 | 34 | 38 | 39 | 40 | 44 | 45 | 46 | 53 | 54 | 55 | 56 | ~ 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/mgcb/settings/MgcbColorSettingsPage.kt: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.plugins.monogame.mgcb.settings 2 | 3 | import com.intellij.openapi.editor.colors.TextAttributesKey 4 | import com.intellij.openapi.fileTypes.SyntaxHighlighter 5 | import com.intellij.openapi.options.colors.AttributesDescriptor 6 | import com.intellij.openapi.options.colors.ColorDescriptor 7 | import com.intellij.openapi.options.colors.ColorSettingsPage 8 | import me.seclerp.rider.plugins.monogame.MonoGameIcons 9 | import me.seclerp.rider.plugins.monogame.mgcb.MgcbSyntaxHighlighter 10 | import javax.swing.Icon 11 | 12 | class MgcbColorSettingsPage : ColorSettingsPage { 13 | override fun getAttributeDescriptors(): Array = DESCRIPTORS 14 | 15 | override fun getColorDescriptors(): Array = ColorDescriptor.EMPTY_ARRAY 16 | 17 | override fun getDisplayName(): String = "MGCB" 18 | 19 | override fun getIcon(): Icon = MonoGameIcons.MgcbFile 20 | 21 | override fun getHighlighter(): SyntaxHighlighter = MgcbSyntaxHighlighter() 22 | 23 | override fun getDemoText(): String = """# Directories 24 | /outputDir:bin/foo 25 | /intermediateDir:obj/foo 26 | 27 | /rebuild 28 | 29 | ${'$'}set BuildEffects=Yes 30 | 31 | # Build a texture 32 | /importer:TextureImporter 33 | /processor:TextureProcessor 34 | /processorParam:ColorKeyEnabled=false 35 | /build:Textures\wood.png 36 | /build:Textures\metal.png 37 | /build:Textures\plastic.png""" 38 | 39 | override fun getAdditionalHighlightingTagToDescriptorMap(): MutableMap? = null 40 | 41 | companion object { 42 | private val DESCRIPTORS = arrayOf( 43 | AttributesDescriptor("Option name", MgcbSyntaxHighlighter.OPTION_KEY), 44 | AttributesDescriptor("Option separator", MgcbSyntaxHighlighter.OPTION_SEPARATOR), 45 | AttributesDescriptor("Option value", MgcbSyntaxHighlighter.OPTION_VALUE), 46 | AttributesDescriptor("Preprocessor keywords", MgcbSyntaxHighlighter.PREPROCESSOR_KEYWORD), 47 | AttributesDescriptor("Preprocessor identifier", MgcbSyntaxHighlighter.PREPROCESSOR_IDENTIFIER), 48 | AttributesDescriptor("Preprocessor separator", MgcbSyntaxHighlighter.PREPROCESSOR_SEPARATOR), 49 | AttributesDescriptor("Preprocessor value", MgcbSyntaxHighlighter.PREPROCESSOR_VALUE), 50 | // AttributesDescriptor("Bad value", MgcbSyntaxHighlighter.BAD_CHARACTER) 51 | ) 52 | } 53 | } -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/mgcb/previewer/tree/MgcbTree.kt: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.plugins.monogame.mgcb.previewer.tree 2 | 3 | import com.intellij.ui.treeStructure.Tree 4 | import javax.swing.tree.DefaultTreeModel 5 | import javax.swing.tree.TreePath 6 | 7 | class MgcbTree(val root: MgcbFolderNode = MgcbFolderNode("Content")) : Tree(root) { 8 | init { 9 | this.expandsSelectedPaths = true 10 | } 11 | 12 | fun reload() { 13 | this.treeDidChange() 14 | (model as DefaultTreeModel).reload() 15 | } 16 | 17 | fun clear() { 18 | root.removeAllChildren() 19 | } 20 | 21 | fun getExpandedUrls(): Set { 22 | val expanded = mutableListOf() 23 | for (i in 0 until rowCount - 1) { 24 | val currentPath: TreePath = getPathForRow(i) 25 | val nextPath: TreePath = getPathForRow(i + 1) 26 | if (currentPath.isDescendant(nextPath)) { 27 | expanded.add(currentPath) 28 | } 29 | } 30 | 31 | return expanded.map { treePath -> treePath.path.joinToString("/") { it.toString() } }.toSet() 32 | } 33 | 34 | fun getSelectedUrl(): String? { 35 | return selectionPath?.path?.joinToString("/") { it.toString() } 36 | } 37 | 38 | fun restoreExpandedUrl(urls: Set) { 39 | restoreExpandedInternal(root, urls) 40 | } 41 | 42 | fun restoreSelectedUrl(url: String) { 43 | selectionPath = null 44 | restoreSelectedUrlInternal(root, url) 45 | } 46 | 47 | private fun restoreExpandedInternal(node: MgcbTreeNode, urls: Set) { 48 | for (i in 0 until node.childCount) { 49 | val childNode = node.getChildAt(i) as MgcbTreeNode 50 | val url = childNode.path.joinToString("/") { it.toString() } 51 | 52 | if (urls.contains(url)) { 53 | val path = TreePath(childNode.path) 54 | expandPath(path) 55 | } 56 | 57 | if (childNode.childCount > 0) { 58 | restoreExpandedInternal(childNode, urls) 59 | } 60 | } 61 | } 62 | 63 | private fun restoreSelectedUrlInternal(node: MgcbTreeNode, url: String) { 64 | val nodeUrl = node.path.joinToString("/") { it.toString() } 65 | 66 | if (nodeUrl == url) { 67 | selectionPath = TreePath(node.path) 68 | return 69 | } 70 | 71 | for (i in 0 until node.childCount) { 72 | val childNode = node.getChildAt(i) as MgcbTreeNode 73 | 74 | restoreSelectedUrlInternal(childNode, url) 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/mgcb/previewer/MgcbModel.kt: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.plugins.monogame.mgcb.previewer 2 | 3 | data class MgcbModel( 4 | // Specifies the directory where all content is written. 5 | // Defaults to the current working directory. 6 | var outputDir: String? = null, 7 | // Specifies the directory where all intermediate files are written. 8 | // Defaults to the current working directory. 9 | var intermediateDir: String? = null, 10 | // Force a full rebuild of all content. 11 | var rebuild: Boolean = false, 12 | // Delete all previously built content and intermediate files. 13 | // Only the /intermediateDir and /outputDir need to be defined for clean to do its job. 14 | var clean: Boolean = false, 15 | // Only build content that changed since the last build. 16 | var incremental: Boolean = false, 17 | // An optional parameter which adds an assembly reference which contains importers, processors, or writers needed during content building. 18 | var references: MutableList = mutableListOf(), 19 | // Set the target platform for this build. It must be a member of the TargetPlatform enum 20 | var platform: String? = null, 21 | // Set the target graphics profile for this build. It must be a member of the GraphicsProfile enum 22 | var profile: String? = null, 23 | // The optional build configuration name from the build system. This is sometimes used as a hint in content processors. 24 | var config: String? = null, 25 | // Uses LZ4 compression to compress the contents of the XNB files 26 | var compress: Boolean = false, 27 | // Instructs the content builder to build the specified content file using the previously set switches and options 28 | var buildEntries: MutableList = mutableListOf(), 29 | // Allows a debugger to attach to the MGCB executable before content is built 30 | var launchDebugger: Boolean = false 31 | ) 32 | 33 | data class BuildEntry( 34 | // Source path to the asset to build 35 | var contentFilepath: String? = null, 36 | // Destination path of the asset to build to 37 | var destinationFilepath: String? = null, 38 | // An optional parameter which defines the class name of the content importer for reading source content 39 | var importer: String? = null, 40 | // An optional parameter which defines the class name of the content processor for processing imported content 41 | var processor: String? = null, 42 | // An optional parameter which defines a parameter name and value to set on a content processor 43 | var processorParams: Map = mapOf(), 44 | ) -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/mgcb/previewer/MgcbModelBuilderVisitor.kt: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.plugins.monogame.mgcb.previewer 2 | 3 | import me.seclerp.rider.plugins.monogame.mgcb.psi.* 4 | 5 | class MgcbModelBuilderVisitor : MgcbVisitor() { 6 | val resultingModel = MgcbModel() 7 | private val currentProcessorParams = mutableMapOf() 8 | 9 | private var currentBuildEntry = BuildEntry() 10 | 11 | override fun visitOption(option: MgcbOption) { 12 | super.visitOption(option) 13 | val optionValue = option.getValue() 14 | 15 | when (option.getKey()) { 16 | "/outputDir" -> resultingModel.outputDir = optionValue 17 | "/intermediateDir" -> resultingModel.intermediateDir = optionValue 18 | "/rebuild" -> resultingModel.rebuild = true 19 | "/clean" -> resultingModel.clean = true 20 | "/incremental" -> resultingModel.incremental = true 21 | "/reference" -> resultingModel.references.add(optionValue!!) 22 | "/platform" -> resultingModel.platform = optionValue 23 | "/profile" -> resultingModel.profile = optionValue 24 | "/config" -> resultingModel.config = optionValue 25 | "/compress" -> resultingModel.compress = true 26 | 27 | "/importer" -> currentBuildEntry.importer = optionValue 28 | "/processor" -> { 29 | currentProcessorParams.clear() 30 | currentBuildEntry.processor = optionValue 31 | } 32 | "/processorParam" -> { 33 | val paramKey = optionValue!!.substringBefore("=") 34 | val paramValue = optionValue.substringAfter("=") 35 | 36 | currentProcessorParams[paramKey] = paramValue 37 | } 38 | "/build" -> { 39 | val sourcePath = optionValue!!.substringBefore(";") 40 | val destinationPath = optionValue.substringAfter(";", "") 41 | 42 | currentBuildEntry.contentFilepath = sourcePath 43 | if (destinationPath != "") { 44 | currentBuildEntry.destinationFilepath = destinationPath 45 | } 46 | currentBuildEntry.processorParams = currentProcessorParams.toMap() 47 | resultingModel.buildEntries.add(currentBuildEntry) 48 | currentBuildEntry = BuildEntry( 49 | importer = currentBuildEntry.importer, 50 | processor = currentBuildEntry.processor 51 | ) 52 | } 53 | "/launchdebugger" -> resultingModel.launchDebugger = true 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /src/rider/main/resources/fileTemplates/Localized SpriteFont File.spritefont.ft: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 14 | Arial 15 | 16 | 20 | 12 21 | 22 | 26 | 0 27 | 28 | 32 | true 33 | 34 | 38 | 39 | 40 | 44 | 45 | 46 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 66 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/mgcb/toolset/MgcbToolsetHost.kt: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.plugins.monogame.mgcb.toolset 2 | 3 | import com.intellij.openapi.components.Service 4 | import com.intellij.openapi.components.service 5 | import com.intellij.openapi.project.Project 6 | import com.jetbrains.rd.platform.util.lifetime 7 | import com.jetbrains.rd.util.reactive.compose 8 | import com.jetbrains.rider.model.RdProjectDescriptor 9 | import com.jetbrains.rider.projectView.solution 10 | import me.seclerp.rider.plugins.monogame.ToolDefinition 11 | import me.seclerp.rider.plugins.monogame.monoGameRiderModel 12 | import java.util.* 13 | 14 | @Service(Service.Level.PROJECT) 15 | class MgcbToolsetHost(private val intellijProject: Project) { 16 | companion object { 17 | fun getInstance(project: Project) = project.service() 18 | } 19 | 20 | private val hostModel by lazy { intellijProject.solution.monoGameRiderModel } 21 | 22 | val globalToolset by lazy { hostModel.mgcbGlobalToolset } 23 | val solutionToolset by lazy { hostModel.mgcbSolutionToolset } 24 | val projectsToolset by lazy { hostModel.mgcbProjectsToolsets } 25 | val editorTool by lazy { 26 | hostModel.mgcbGlobalToolset.editor 27 | .compose(hostModel.mgcbSolutionToolset.editor) { globalToolset, solutionToolset -> 28 | // Solution (local) toolset always has priority over global 29 | when { 30 | solutionToolset is ToolDefinition -> MgcbResolvedTool.Local(solutionToolset) 31 | globalToolset is ToolDefinition -> MgcbResolvedTool.Global(globalToolset) 32 | else -> MgcbResolvedTool.None 33 | } 34 | } 35 | } 36 | 37 | private val projectsEditorTools by lazy { 38 | val projectTools = mutableMapOf() 39 | hostModel.mgcbProjectsToolsets.view(intellijProject.lifetime) { toolsetRuntime, key, value -> 40 | toolsetRuntime.bracketIfAlive({ 41 | value.editor.view(toolsetRuntime) { editorLifetime, editor -> 42 | projectTools[key] = when(editor) { 43 | null -> MgcbResolvedTool.None 44 | else -> MgcbResolvedTool.Local(editor) 45 | } 46 | } 47 | }, { projectTools.remove(key) }) 48 | } 49 | projectTools 50 | } 51 | 52 | fun getEditorTool() = editorTool.value 53 | 54 | fun getEditorTool(project: RdProjectDescriptor): MgcbResolvedTool { 55 | val projectTool = projectsEditorTools[project.originalGuid] 56 | if (projectTool == null || projectTool.isNone()) 57 | return getEditorTool() 58 | return projectTool 59 | } 60 | 61 | fun areToolsAvailable() = getEditorTool() !is MgcbResolvedTool.None 62 | 63 | fun areToolsAvailable(project: RdProjectDescriptor) = getEditorTool(project) !is MgcbResolvedTool.None 64 | } 65 | 66 | -------------------------------------------------------------------------------- /src/dotnet/Rider.Plugins.MonoGame/MonoGameRdModelHost.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using JetBrains.Annotations; 3 | using JetBrains.Application.Parts; 4 | using JetBrains.Collections.Viewable; 5 | using JetBrains.Lifetimes; 6 | using JetBrains.ProjectModel; 7 | using JetBrains.ProjectModel.NuGet.DotNetTools; 8 | using JetBrains.Rd.Base; 9 | using JetBrains.RdBackend.Common.Features; 10 | using JetBrains.ReSharper.Feature.Services.Protocol; 11 | using Rider.Plugins.MonoGame.Mgcb; 12 | 13 | namespace Rider.Plugins.MonoGame; 14 | 15 | [SolutionComponent(Instantiation.ContainerAsyncPrimaryThread)] 16 | public class MonoGameRdModelHost 17 | { 18 | public MonoGameRdModelHost( 19 | Lifetime lifetime, 20 | ISolution solution, 21 | MgcbToolsetTracker toolsetTracker) 22 | { 23 | var model = solution.GetProtocolSolution().GetMonoGameRiderModel(); 24 | 25 | BindGlobalToolset(toolsetTracker.MgcbGlobalToolset, model.MgcbGlobalToolset, lifetime); 26 | BindLocalToolset(toolsetTracker.MgcbSolutionToolset, model.MgcbSolutionToolset, lifetime); 27 | BindProjectsToolsets(toolsetTracker.MgcbProjectsToolset, model.MgcbProjectsToolsets, lifetime); 28 | } 29 | 30 | private void BindGlobalToolset(MgcbToolset source, MgcbEditorToolset target, Lifetime lifetime) 31 | { 32 | source.Editor.FlowInto(lifetime, target.Editor, cacheEntry => 33 | cacheEntry is not null ? MapGlobalTool(cacheEntry) : null); 34 | } 35 | 36 | private void BindLocalToolset(MgcbToolset source, MgcbEditorToolset target, Lifetime lifetime) 37 | { 38 | source.Editor.FlowInto(lifetime, target.Editor, cacheEntry => 39 | cacheEntry is not null ? MapLocalTool(cacheEntry) : null); 40 | } 41 | 42 | private void BindProjectsToolsets(IViewableMap> source, IViewableMap target, Lifetime lifetime) 43 | { 44 | source.View(lifetime, (projectLifetime, project, toolProperty) => 45 | { 46 | projectLifetime.Bracket( 47 | () => 48 | { 49 | var projectToolset = new MgcbEditorToolset(); 50 | BindLocalToolset(toolProperty, projectToolset, projectLifetime); 51 | target.Add(project.Guid, projectToolset); 52 | }, 53 | () => 54 | { 55 | Unset(target[project.Guid]); 56 | target.Remove(project.Guid); 57 | }); 58 | }); 59 | } 60 | 61 | [NotNull] 62 | private static ToolDefinition MapGlobalTool([NotNull] GlobalToolCacheEntry tool) => 63 | new (tool.ToolName, MgcbEditorCommandNameResolver.Resolve(tool), tool.Version.ToString()); 64 | 65 | [NotNull] 66 | private static ToolDefinition MapLocalTool([NotNull] LocalTool tool) => 67 | new (tool.PackageId, MgcbEditorCommandNameResolver.Resolve(tool), tool.Version); 68 | 69 | private static void Unset(MgcbEditorToolset toolset) 70 | { 71 | toolset.Editor.SetValue(null); 72 | } 73 | } -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/spritefont/SpriteFontXmlSchemaProvider.kt: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.plugins.monogame.spritefont 2 | 3 | import com.intellij.javaee.ExternalResourceManagerEx 4 | import com.intellij.openapi.module.Module 5 | import com.intellij.openapi.util.ModificationTracker 6 | import com.intellij.openapi.vfs.VfsUtil 7 | import com.intellij.psi.PsiFile 8 | import com.intellij.psi.PsiManager 9 | import com.intellij.psi.util.CachedValue 10 | import com.intellij.psi.util.CachedValueProvider 11 | import com.intellij.psi.util.CachedValuesManager 12 | import com.intellij.psi.xml.XmlFile 13 | import com.intellij.xml.XmlSchemaProvider 14 | import com.intellij.openapi.diagnostic.logger 15 | 16 | class SpriteFontXmlSchemaProvider : XmlSchemaProvider() { 17 | companion object { 18 | private val logger = logger() 19 | const val SCHEMA_LOCATION = "/xmlSchema/SpriteFont.xsd" 20 | const val FILE_EXTENSION = "spritefont" 21 | } 22 | 23 | private var schema: CachedValue? = null 24 | 25 | override fun getSchema(url: String, module: Module?, baseFile: PsiFile): XmlFile? { 26 | logger.info("Schema location: $SCHEMA_LOCATION") 27 | val project = baseFile.project 28 | logger.info("Project: ${project.name}") 29 | 30 | if (schema != null) { 31 | logger.info("Returning cached schema for location: $SCHEMA_LOCATION") 32 | return schema?.value 33 | } 34 | 35 | schema = CachedValuesManager.getManager(project).createCachedValue(object : CachedValueProvider { 36 | override fun compute(): CachedValueProvider.Result { 37 | logger.info("Computing value for cached schema") 38 | val resource = javaClass.getResource(SCHEMA_LOCATION)!! 39 | val fileByURL = VfsUtil.findFileByURL(resource) 40 | if (fileByURL == null) { 41 | logger.error("xsd file '$SCHEMA_LOCATION' not found") 42 | return CachedValueProvider.Result(null, ModificationTracker.EVER_CHANGED) 43 | } 44 | 45 | val psiFile = PsiManager.getInstance(project).findFile(fileByURL) 46 | if (psiFile !is XmlFile) { 47 | logger.warn("PSI file is not XML file") 48 | return CachedValueProvider.Result(null, ModificationTracker.EVER_CHANGED) 49 | } 50 | 51 | logger.info("PSI file null: ${false}") 52 | val manager = ExternalResourceManagerEx.getInstanceEx() 53 | val externalResourcesTracker = ModificationTracker { manager.getModificationCount(project) } 54 | return CachedValueProvider.Result.create(psiFile, psiFile, externalResourcesTracker) 55 | } 56 | }, false) 57 | 58 | logger.info("Added schema to cache for location: $SCHEMA_LOCATION") 59 | return schema?.value 60 | } 61 | 62 | override fun isAvailable(file: XmlFile): Boolean { 63 | return file.originalFile.virtualFile?.extension.equals(FILE_EXTENSION, true) 64 | } 65 | } -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/extensions/commandLine/CommandBuilder.kt: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.extensions.commandLine 2 | 3 | import com.intellij.execution.configurations.GeneralCommandLine 4 | import com.intellij.openapi.project.Project 5 | import com.jetbrains.rider.model.dotNetActiveRuntimeModel 6 | import com.jetbrains.rider.projectView.solution 7 | import com.jetbrains.rider.projectView.solutionDirectoryPath 8 | import com.jetbrains.rider.shared.run.FormatPreservingCommandLine 9 | import com.jetbrains.rider.shared.run.withRawParameters 10 | import org.jetbrains.annotations.NonNls 11 | import java.nio.charset.Charset 12 | 13 | open class CommandBuilder(vararg baseCommands: @NonNls String) { 14 | @NonNls 15 | protected var generalCommandLine = 16 | FormatPreservingCommandLine() 17 | .withRawParameters(baseCommands.joinToString(" ")) 18 | .withCharset(Charset.forName("UTF-8")) 19 | 20 | fun executable(executable: String) { 21 | generalCommandLine = generalCommandLine.withExePath(executable) 22 | } 23 | 24 | fun workingDirectory(workingDirectory: String) { 25 | generalCommandLine = generalCommandLine.withWorkDirectory(workingDirectory) 26 | } 27 | 28 | @NonNls 29 | fun param(value: String) { 30 | generalCommandLine = generalCommandLine.withParameters(value) 31 | } 32 | 33 | 34 | @NonNls 35 | fun param(name: String, value: String) { 36 | generalCommandLine = generalCommandLine.withParameters(name, value) 37 | } 38 | 39 | @NonNls 40 | fun nullableParam(value: String?) { 41 | if (value != null) 42 | generalCommandLine = generalCommandLine.withParameters(value) 43 | } 44 | 45 | @NonNls 46 | fun nullableParam(name: String, value: String?) { 47 | if (value != null) 48 | generalCommandLine = generalCommandLine.withParameters(name, value) 49 | } 50 | 51 | @NonNls 52 | fun paramWhen(key: String, condition: Boolean) { 53 | if (condition) 54 | generalCommandLine = generalCommandLine.withParameters(key) 55 | } 56 | 57 | fun environment(key: String, value: String) { 58 | generalCommandLine = generalCommandLine.withEnvironment(key, value) 59 | } 60 | 61 | fun build(): GeneralCommandLine = generalCommandLine 62 | } 63 | 64 | fun buildDotnetCommand(project: Project, vararg baseCommands: @NonNls String, builder: CommandBuilder.() -> Unit = {}) = 65 | CommandBuilder(*baseCommands) 66 | .apply { 67 | val activeToolset = project.solution.dotNetActiveRuntimeModel.activeRuntime.valueOrNull 68 | executable(activeToolset?.dotNetCliExePath ?: throw Exception(".NET / .NET Core is not configured, unable to run commands.")) 69 | workingDirectory(project.solutionDirectoryPath.toString()) 70 | environment("DOTNET_SKIP_FIRST_TIME_EXPERIENCE", "true") 71 | environment("DOTNET_NOLOGO", "true") 72 | } 73 | .apply(builder) 74 | .build() 75 | 76 | fun buildCommand(vararg baseCommands: @NonNls String, builder: CommandBuilder.() -> Unit = {}) = 77 | CommandBuilder(*baseCommands).apply(builder).build() -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/extensions/commandLine/DefaultCommandExecutor.kt: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.extensions.commandLine 2 | 3 | import com.intellij.execution.configurations.GeneralCommandLine 4 | import com.intellij.execution.util.ExecUtil 5 | import com.intellij.openapi.components.Service 6 | import com.intellij.openapi.components.service 7 | import com.intellij.openapi.diagnostic.logger 8 | import com.intellij.openapi.progress.runBackgroundableTask 9 | import com.intellij.openapi.project.Project 10 | import com.intellij.openapi.ui.Messages 11 | import com.jetbrains.rd.util.string.printToString 12 | import me.seclerp.rider.plugins.monogame.MonoGameUiBundle 13 | import java.io.BufferedReader 14 | import java.io.IOException 15 | import java.util.concurrent.TimeUnit 16 | 17 | @Service 18 | class DefaultCommandExecutor( 19 | intellijProject: Project 20 | ) : CliCommandExecutor(intellijProject) { 21 | companion object { 22 | fun getInstance(project: Project) = project.service() 23 | } 24 | private val logger = logger() 25 | 26 | override fun execute(command: GeneralCommandLine) { 27 | runBackgroundableTask(MonoGameUiBundle.message("command.execution.title"), intellijProject, false) { 28 | try { 29 | command.toProcessBuilder() 30 | val process = command 31 | .toProcessBuilder() 32 | .redirectOutput(ProcessBuilder.Redirect.PIPE) 33 | .redirectError(ProcessBuilder.Redirect.PIPE) 34 | .start() 35 | process.waitFor(30, TimeUnit.MINUTES) 36 | val exitCode = process.exitValue() 37 | val output = process.inputStream.bufferedReader().readText() 38 | val error = process.errorStream.bufferedReader().readText() 39 | 40 | // val executionResult = ExecUtil.execAndGetOutput(command) 41 | // val output = executionResult.stdout 42 | // val error = executionResult.stderr 43 | // val exitCode = executionResult.exitCode 44 | if (exitCode != 0) { 45 | failed(command.commandLineString, output, error, exitCode) 46 | } 47 | } catch (e: Exception) { 48 | failed(command.commandLineString, e) 49 | } 50 | } 51 | } 52 | 53 | private fun failed(command: String, exception: Exception) { 54 | logger.error(buildString { 55 | append("Command '$command' failed\n") 56 | append("\tException: ${exception.stackTraceToString()}") 57 | }) 58 | Messages.showErrorDialog( 59 | MonoGameUiBundle.message("command.execution.error.message.exception", exception.message ?: ""), 60 | MonoGameUiBundle.message("command.execution.error.title")) 61 | } 62 | 63 | private fun failed(command: String, stdout: String, stderr: String, exitCode: Int) { 64 | logger.error(buildString { 65 | append("Command '$command' failed with exit code $exitCode\n") 66 | append("\tSTDOUT: $stdout\n") 67 | append("\tSTDERR: $stderr") 68 | }) 69 | Messages.showErrorDialog( 70 | MonoGameUiBundle.message("command.execution.error.message.code", exitCode), 71 | MonoGameUiBundle.message("command.execution.error.title")) 72 | } 73 | } -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build & Text 2 | on: 3 | push: 4 | branches: [ release/* ] 5 | pull_request: 6 | branches: [ release/* ] 7 | workflow_dispatch: 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | os: [ windows-latest, macos-latest, ubuntu-latest ] 16 | steps: 17 | - name: 📝 Fetch Sources 18 | uses: actions/checkout@v5 19 | 20 | - name: 🛠 Import environment 21 | uses: ./.github/workflows/environment 22 | 23 | - name: 🧐 Validate Gradle wrapper 24 | uses: gradle/actions/wrapper-validation@v4 25 | 26 | - name: 🛠 Setup JDK 27 | uses: actions/setup-java@v4 28 | with: 29 | java-version: 21 30 | distribution: jetbrains 31 | 32 | - name: 🛠 Setup .NET SDK 33 | uses: actions/setup-dotnet@v5 34 | with: 35 | dotnet-version: 9.0.300 36 | 37 | - name: 🔧 Prepare build devenv 38 | uses: gradle/gradle-build-action@v2 39 | with: 40 | cache-disabled: true 41 | arguments: prepare 42 | 43 | - name: 🏗 Build Plugin (Stable) 44 | uses: gradle/gradle-build-action@v2 45 | with: 46 | arguments: buildPlugin 47 | 48 | - name: 📦 Prepare artifacts folder 49 | shell: pwsh 50 | run: | 51 | mkdir artifacts 52 | 53 | - name: 📦 Emit metadata artifacts 54 | shell: pwsh 55 | run: | 56 | $PROPERTIES = ./gradlew properties 57 | 58 | $VERSION_ROW = $PROPERTIES | Select-String "pluginVersion" 59 | $VERSION = ($VERSION_ROW -split ':')[1].Trim() 60 | 61 | $PLUGIN_ID_ROW = $PROPERTIES | Select-String "riderPluginId" 62 | $PLUGIN_ID = ($PLUGIN_ID_ROW -split ':')[1].Trim() 63 | 64 | echo "PLUGIN_VERSION=$VERSION" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append 65 | echo $VERSION > ${{ env.ARTIFACTS_FOLDER }}/${{ env.ARTIFACTS_VERSION }} 66 | 67 | echo "PLUGIN_ID=$PLUGIN_ID" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append 68 | echo $PLUGIN_ID > ${{ env.ARTIFACTS_FOLDER }}/${{ env.ARTIFACTS_PLUGIN_ID }} 69 | 70 | - name: 📦 Emit plugin archive artifact 71 | shell: pwsh 72 | run: | 73 | Get-ChildItem -Path "build\distributions\*.zip" | 74 | Move-Item -Destination "${{ env.ARTIFACTS_FOLDER }}\${{ env.PLUGIN_NAME }}-${{ env.PLUGIN_VERSION }}.zip" -Force 75 | 76 | - name: 📦 Emit changelog artifact 77 | shell: pwsh 78 | run: | 79 | $CHANGELOG = (./gradlew getChangelog --console=plain -q --no-header) 80 | # We need to escape CHANGELOG because it will be transferred over HTTP for GitHub Release creation 81 | $CHANGELOG = $CHANGELOG -replace '%', '%25' 82 | $CHANGELOG = $CHANGELOG -replace "`n", '%0A' 83 | $CHANGELOG = $CHANGELOG -replace "`r", '%0D' 84 | echo $CHANGELOG 85 | echo $CHANGELOG > ${{ env.ARTIFACTS_FOLDER }}/${{ env.ARTIFACTS_CHANGELOG }} 86 | 87 | - name: 📦 Tree artifacts 88 | shell: pwsh 89 | run: | 90 | tree ${{ env.ARTIFACTS_FOLDER }} 91 | 92 | - name: 📦 Upload artifacts 93 | uses: actions/upload-artifact@v4 94 | with: 95 | name: plugin-artifacts-${{ matrix.os }} 96 | path: ${{ env.ARTIFACTS_FOLDER }}/ 97 | if-no-files-found: error -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/mgcb/Mgcb.flex: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.plugins.monogame.mgcb; 2 | 3 | import com.intellij.lexer.FlexLexer; 4 | import com.intellij.psi.tree.IElementType; 5 | import me.seclerp.rider.plugins.monogame.mgcb.psi.MgcbTypes; 6 | import com.intellij.psi.TokenType; 7 | 8 | %% 9 | 10 | %class MgcbLexer 11 | %implements FlexLexer 12 | %unicode 13 | %function advance 14 | %type IElementType 15 | %eof{ return; 16 | %eof} 17 | 18 | CRLF=\R 19 | EOL=[\r\n] 20 | SPACES=[\ \t\f] 21 | WHITE_SPACE=[\ \n\t\f] 22 | COMMENT="#"[^\r\n]* 23 | 24 | OPTION_KEY="/"[^\ \t:\r\n]+ 25 | OPTION_SEPARATOR=":" 26 | OPTION_VALUE=[^\r\n]+ 27 | 28 | PREPROCESSOR_IDENTIFIER=[^\ \t:=\r\n]+ 29 | PREPROCESSOR_VALUE=[^\ \t:=\r\n]+ 30 | EQ="=" 31 | 32 | SET_KEYWORD="$set" 33 | IF_KEYWORD="$if" 34 | ENDIF_KEYWORD="$endif" 35 | 36 | %state WAITING_OPTION_KEY 37 | %state WAITING_OPTION_VALUE 38 | %state WAITING_SET_KEY 39 | %state WAITING_SET_VALUE 40 | %state WAITING_IF_KEY 41 | %state WAITING_IF_VALUE 42 | %state WAITING_WHITESPACE 43 | 44 | %% 45 | 46 | {COMMENT} { yybegin(YYINITIAL); return MgcbTypes.COMMENT; } 47 | {OPTION_KEY} { yybegin(WAITING_OPTION_KEY); return MgcbTypes.OPTION_KEY; } 48 | {WHITE_SPACE}*{EOL} { yybegin(YYINITIAL); return MgcbTypes.WHITE_SPACE; } 49 | {OPTION_SEPARATOR} { yybegin(WAITING_OPTION_VALUE); return MgcbTypes.OPTION_SEPARATOR; } 50 | {WHITE_SPACE}*{EOL} { yybegin(YYINITIAL); return MgcbTypes.WHITE_SPACE; } 51 | {OPTION_VALUE} { yybegin(WAITING_WHITESPACE); return MgcbTypes.OPTION_VALUE; } 52 | 53 | {SET_KEYWORD} { yybegin(WAITING_SET_KEY); return MgcbTypes.SET_KEYWORD; } 54 | {SPACES}+ { yybegin(WAITING_SET_KEY); return MgcbTypes.WHITE_SPACE; } 55 | {PREPROCESSOR_IDENTIFIER} { yybegin(WAITING_SET_KEY); return MgcbTypes.PREPROCESSOR_IDENTIFIER; } 56 | {WHITE_SPACE}*{EOL} { yybegin(YYINITIAL); return MgcbTypes.WHITE_SPACE; } 57 | {EQ} { yybegin(WAITING_SET_VALUE); return MgcbTypes.EQ; } 58 | {PREPROCESSOR_VALUE} { yybegin(WAITING_WHITESPACE); return MgcbTypes.PREPROCESSOR_VALUE; } 59 | 60 | {IF_KEYWORD} { yybegin(WAITING_IF_KEY); return MgcbTypes.IF_KEYWORD; } 61 | {SPACES}+ { yybegin(WAITING_IF_KEY); return MgcbTypes.WHITE_SPACE; } 62 | {PREPROCESSOR_IDENTIFIER} { yybegin(WAITING_IF_KEY); return MgcbTypes.PREPROCESSOR_IDENTIFIER; } 63 | {WHITE_SPACE}*{EOL} { yybegin(YYINITIAL); return MgcbTypes.WHITE_SPACE; } 64 | {EQ} { yybegin(WAITING_IF_VALUE); return MgcbTypes.EQ; } 65 | {PREPROCESSOR_VALUE} { yybegin(WAITING_WHITESPACE); return MgcbTypes.PREPROCESSOR_VALUE; } 66 | {ENDIF_KEYWORD} { yybegin(YYINITIAL); return MgcbTypes.ENDIF_KEYWORD; } 67 | 68 | ({CRLF}|{WHITE_SPACE})+ { yybegin(YYINITIAL); return MgcbTypes.WHITE_SPACE; } 69 | ({CRLF}|{WHITE_SPACE})+ { yybegin(YYINITIAL); return MgcbTypes.WHITE_SPACE; } 70 | 71 | [^] { return TokenType.BAD_CHARACTER; } -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/mgcb/MgcbCompletionContributor.kt: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.plugins.monogame.mgcb 2 | 3 | import com.intellij.codeInsight.completion.* 4 | import com.intellij.codeInsight.lookup.LookupElementBuilder 5 | import com.intellij.patterns.PlatformPatterns 6 | import com.intellij.util.ProcessingContext 7 | import me.seclerp.rider.plugins.monogame.mgcb.psi.MgcbTypes 8 | 9 | class MgcbCompletionContributor : CompletionContributor() { 10 | init { 11 | extend(CompletionType.BASIC, PlatformPatterns.psiElement(MgcbTypes.OPTION_KEY), OptionCompletionProvider()) 12 | // extend(CompletionType.BASIC, PlatformPatterns.psiElement(), KeywordCompletionProvider()) 13 | } 14 | 15 | abstract class MgcbCompletionProvider(private val values: Array) 16 | : CompletionProvider() { 17 | override fun addCompletions(params: CompletionParameters, ctx: ProcessingContext, resultSet: CompletionResultSet) { 18 | values.forEach { 19 | resultSet.addElement(LookupElementBuilder.create(it)) 20 | } 21 | } 22 | 23 | companion object { 24 | private const val specialMarker = "IntellijIdeaRulezzz" 25 | 26 | // fun getPrefix(params: CompletionParameters): String { 27 | // val text = params.position.text 28 | // println(text) 29 | // 30 | // if (text == specialMarker) { 31 | // return specialMarker 32 | // } 33 | // 34 | // if (!text.endsWith(specialMarker)) { 35 | // return "" 36 | // } 37 | // 38 | // return text.substring(0, text.length - specialMarker.length) 39 | // } 40 | } 41 | } 42 | 43 | // class KeywordCompletionProvider : MgcbCompletionProvider(Keywords, ::handleInsert) { 44 | // companion object { 45 | // private fun handleInsert(ctx: InsertionContext, item: LookupElement): Unit { 46 | // ctx.commitDocument() 47 | // } 48 | // 49 | // val Keywords = arrayOf( 50 | //// "\$set", 51 | // "\$if", 52 | //// "\$endif" 53 | // ) 54 | // } 55 | // } 56 | 57 | class OptionCompletionProvider : MgcbCompletionProvider(OptionTypes) { 58 | override fun addCompletions( 59 | params: CompletionParameters, 60 | ctx: ProcessingContext, 61 | resultSet: CompletionResultSet 62 | ) { 63 | val prefixMatcher = resultSet.prefixMatcher.cloneWithPrefix("/${resultSet.prefixMatcher.prefix}") 64 | super.addCompletions(params, ctx, resultSet.withPrefixMatcher(prefixMatcher)) 65 | } 66 | 67 | companion object { 68 | val OptionTypes = arrayOf( 69 | "/outputDir", 70 | "/intermediateDir", 71 | "/rebuild", 72 | "/clean", 73 | "/incremental", 74 | "/reference", 75 | "/platform", 76 | "/profile", 77 | "/config", 78 | "/compress", 79 | "/importer", 80 | "/processor", 81 | "/processorParam", 82 | "/build", 83 | "/launchdebugger" 84 | ) 85 | } 86 | } 87 | // 88 | // class MgcbPrefixMatcher : PrefixMatcher() { 89 | // override fun prefixMatches(p0: String): Boolean { 90 | // TODO("Not yet implemented") 91 | // } 92 | // 93 | // override fun cloneWithPrefix(p0: String): PrefixMatcher { 94 | // TODO("Not yet implemented") 95 | // } 96 | // } 97 | } -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/mgcb/MgcbSyntaxHighlighter.kt: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.plugins.monogame.mgcb 2 | 3 | import com.intellij.lexer.Lexer 4 | import com.intellij.openapi.editor.DefaultLanguageHighlighterColors 5 | import com.intellij.openapi.editor.HighlighterColors 6 | import com.intellij.openapi.editor.colors.TextAttributesKey 7 | import com.intellij.openapi.editor.colors.TextAttributesKey.createTextAttributesKey 8 | import com.intellij.openapi.fileTypes.SyntaxHighlighterBase 9 | import com.intellij.psi.TokenType 10 | import com.intellij.psi.tree.IElementType 11 | import me.seclerp.rider.plugins.monogame.mgcb.psi.MgcbTypes 12 | 13 | class MgcbSyntaxHighlighter : SyntaxHighlighterBase() { 14 | override fun getHighlightingLexer(): Lexer { 15 | return MgcbLexerAdapter() 16 | } 17 | 18 | override fun getTokenHighlights(tokenType: IElementType): Array = 19 | when (tokenType) { 20 | MgcbTypes.OPTION_KEY -> { OPTION_KEY_KEYS } 21 | MgcbTypes.OPTION_SEPARATOR -> { OPTION_SEPARATOR_KEYS } 22 | MgcbTypes.OPTION_VALUE -> { OPTION_VALUE_KEYS } 23 | MgcbTypes.SET_KEYWORD -> { PREPROCESSOR_KEYWORD_KEYS } 24 | MgcbTypes.IF_KEYWORD -> { PREPROCESSOR_KEYWORD_KEYS } 25 | MgcbTypes.ENDIF_KEYWORD -> { PREPROCESSOR_KEYWORD_KEYS } 26 | MgcbTypes.PREPROCESSOR_IDENTIFIER -> { PREPROCESSOR_IDENTIFIER_KEYS } 27 | MgcbTypes.EQ -> { PREPROCESSOR_SEPARATOR_KEYS } 28 | MgcbTypes.PREPROCESSOR_VALUE -> { PREPROCESSOR_VALUE_KEYS } 29 | MgcbTypes.COMMENT -> { COMMENT_KEYS } 30 | TokenType.BAD_CHARACTER -> { BAD_CHAR_KEYS } 31 | else -> { EMPTY_KEYS } 32 | } 33 | 34 | companion object { 35 | val OPTION_KEY: TextAttributesKey = createTextAttributesKey("MGCB_OPTION_KEY", DefaultLanguageHighlighterColors.IDENTIFIER) 36 | val OPTION_SEPARATOR: TextAttributesKey = createTextAttributesKey("MGCB_OPTION_SEPARATOR", DefaultLanguageHighlighterColors.OPERATION_SIGN) 37 | val OPTION_VALUE: TextAttributesKey = createTextAttributesKey("MGCB_OPTION_VALUE", DefaultLanguageHighlighterColors.STRING) 38 | val PREPROCESSOR_KEYWORD: TextAttributesKey = createTextAttributesKey("MGCB_PREPROCESSOR_KEYWORD", DefaultLanguageHighlighterColors.KEYWORD) 39 | val PREPROCESSOR_IDENTIFIER: TextAttributesKey = createTextAttributesKey("MGCB_PREPROCESSOR_IDENTIFIER", DefaultLanguageHighlighterColors.IDENTIFIER) 40 | val PREPROCESSOR_SEPARATOR: TextAttributesKey = createTextAttributesKey("MGCB_PREPROCESSOR_SEPARATOR", DefaultLanguageHighlighterColors.OPERATION_SIGN) 41 | val PREPROCESSOR_VALUE: TextAttributesKey = createTextAttributesKey("MGCB_PREPROCESSOR_VALUE", DefaultLanguageHighlighterColors.STRING) 42 | val COMMENT: TextAttributesKey = createTextAttributesKey("MGCB_COMMENT", DefaultLanguageHighlighterColors.LINE_COMMENT) 43 | val BAD_CHARACTER: TextAttributesKey = createTextAttributesKey("MGCB_BAD_CHARACTER", HighlighterColors.BAD_CHARACTER) 44 | 45 | private val OPTION_KEY_KEYS: Array = arrayOf(OPTION_KEY) 46 | private val OPTION_SEPARATOR_KEYS: Array = arrayOf(OPTION_SEPARATOR) 47 | private val OPTION_VALUE_KEYS: Array = arrayOf(OPTION_VALUE) 48 | private val PREPROCESSOR_KEYWORD_KEYS: Array = arrayOf(PREPROCESSOR_KEYWORD) 49 | private val PREPROCESSOR_IDENTIFIER_KEYS: Array = arrayOf(PREPROCESSOR_IDENTIFIER) 50 | private val PREPROCESSOR_SEPARATOR_KEYS: Array = arrayOf(PREPROCESSOR_SEPARATOR) 51 | private val PREPROCESSOR_VALUE_KEYS: Array = arrayOf(PREPROCESSOR_VALUE) 52 | private val COMMENT_KEYS: Array = arrayOf(COMMENT) 53 | private val BAD_CHAR_KEYS: Array = arrayOf(BAD_CHARACTER) 54 | private val EMPTY_KEYS: Array = arrayOfNulls(0) 55 | } 56 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |

MonoGame plugin for JetBrains Rider

3 | Logo 4 |

5 | This plugin improves MonoGame usage experience inside JetBrains Rider. 6 |

7 | Build 8 |
9 | 10 | --- 11 | 12 |
13 |

MGCB file editing

14 |
15 | 16 |

17 | How to use 1 18 |

Autocomplete and syntax highlighting

19 | All supported MGCB options properly highlighted and could be autocompleted 20 |

21 | 22 |

23 | 24 |

25 | How to use 1 26 |

Build entries previewer

27 | See all your assets in a realtime tree view according to their declarations 28 |

29 | 30 |

31 | 32 |

33 | How to use 1 34 |

Table view for a build entry properties

35 | Review build entry properties and processor parameters in a table representation 36 |

37 | 38 |

39 | 40 |

41 | How to use 1 42 |

Open in external MGCB editor action

43 | Jump to external MGCB editor GUI in one click 44 |

45 | 46 |
47 | 48 |
49 |

Other

50 |
51 | 52 |

53 | How to use 1 54 |

Additional file templates

55 | 56 | Easily create MonoGame related assets using file templates under Add menu 57 |

58 |
59 | 60 | --- 61 | 62 | ### How to install 63 | 64 | #### Using marketplace: 65 | 66 | 1. Go to `Settings` / `Plugins` / `Marketplace` 67 | 1. Search for "MonoGame" 68 | 1. Click `Install`, then `Save` 69 | 1. After saving restart Rider 70 | 71 | #### Using `.zip` file 72 | 1. Go to [**Releases**](https://github.com/seclerp/rider-monogame/releases) 73 | 2. Download the latest release of plugin for your edition of JetBrains Rider (Stable or EAP) 74 | 3. Proceed to `Settings` / `Plugins` / `⚙` / `Install plugin from disk` 75 | 4. Click `Save` 76 | 5. After saving restart Rider 77 | 78 | ### How to use 79 | 80 | Just open .mgcb file for editing. Previewer will be on the right side of the editor. 81 | 82 | Additional file templates are located under Add section of a folder or project context menu. 83 | 84 | ### Requirements 85 | 86 | - JetBrains Rider **2025.1+** 87 | 88 | - Project with MonoGame installed (**3.8+ is recommended**) 89 | 90 | > **Note**: Projects with older versions of MonoGame might work, but with issues 91 | 92 | ### Development 93 | 94 | > **Note**: You should have JDK 21 and .NET SDK 8.0+ installed and configured. 95 | 96 | #### Preparing 97 | 98 | `./gradlew rdgen` - generates RD protocol data for plugin internal communication 99 | 100 | #### Building plugin parts 101 | 102 | `./gradlew buildPlugin` 103 | 104 | It will build both frontend and backend parts. 105 | 106 | #### Running 107 | 108 | Next command will start instance of JetBrains Rider with plugin attached to it: 109 | 110 | `./gradlew runIde` 111 | 112 | ### Contributing 113 | 114 | Contributions are welcome! 🎉 115 | 116 | It's better to create an issue with description of your bug/feature before creating pull requests. 117 | 118 | #### About branching 119 | 120 | This project uses customized git strategy. 121 | 122 | Each `release/*` branch plays main development branch role for specific release. 123 | 124 | For example, `release/251` means that branch is related to `251.*` release cycle for `2025.1` Rider version. 125 | 126 | ### See also 127 | 128 | - [**Marketplace page**](https://plugins.jetbrains.com/plugin/18415-monogame) 129 | - [**Changelog**](CHANGELOG.md) 130 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 4 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 5 | 6 | ## [Unreleased] 7 | 8 | ## [253.1.0] - 2025-11-12 9 | ### Added 10 | - General: Support for Rider 2025.3 11 | 12 | ## [252.0.0-eap02] - 2025-05-28 13 | ### Added 14 | - General: Support for Rider 2025.2 (EAP 2) 15 | 16 | ## [251.1.0] - 2025-05-28 17 | ### Added 18 | - General: Support for Rider 2025.1 19 | 20 | ## [251.0.0-eap05] - 2025-02-26 21 | ### Added 22 | - General: Support for Rider 2025.1.0 EAP 5 23 | 24 | ## [243.1.0] - 2024-12-21 25 | ### Added 26 | - General: Support for Rider 2024.3.2 27 | 28 | ## [243.0.0-eap02] - 2024-09-28 29 | ### Added 30 | - General: Support for Rider 2024.3 EAP 2 31 | 32 | ## [242.1.0] - 2024-09-22 33 | ### Added 34 | - General: Support for Rider 2024.2 35 | 36 | ## [242.0.0-eap03] - 2024-06-21 37 | ### Added 38 | - General: Support for Rider 2024.2 (EAP 3) 39 | 40 | ## [241.0.0-rc1] - 2024-03-28 41 | ### Added 42 | - General: Support for Rider 2024.1 RC 43 | 44 | ## [233.1.0] - 2023-12-27 45 | ### Added 46 | - General: Support for Rider 2023.3 47 | 48 | ## [232.0.0-rc1] - 2023-07-27 49 | ### Added 50 | - General: Support for Rider 2023.2 51 | 52 | ## [231.1.0] - 2023-07-11 53 | ### Added 54 | - General: Support for Rider 2023.1 (#16) 55 | - MGCB: Support for new MGCB Editor tools layout of MonoGame 3.8.1 (#13) 56 | - MGCB Previewer: Minor style adjustments for parameters and preprocessor values tables 57 | ### Changed 58 | - MGCB: Refactor of `mgcb-editor` tools detection logic (#14) 59 | ### Fixed 60 | - MGCB: False-positive trigger of `Install` toolbar decorator even if tools are installed (#15) 61 | ### Removed 62 | - MGCB: Temporary removed `Install` action due to broken behavior (#15) 63 | 64 | ## [223.0.0] - 2022-09-30 65 | ### Added 66 | - Enable support for Rider 2022.3 EAP 67 | 68 | ## [222.0.0] - 2022-08-15 69 | ### Added 70 | - Add support for Rider 2022.2 71 | ### Changed 72 | - Change version numbering to reflect Rider version in it 73 | 74 | ## [1.0.1] - 2022-04-27 75 | ### Changed 76 | - Upgrade Rider SDK to 2022.1 77 | ### Fixed 78 | - Fix: Exception Thrown when switching solutions 79 | 80 | ## [1.0.0] - 2022-01-15 81 | ### Added 82 | - Autocomplete and syntax highlighting 83 | - Build entries provider 84 | - Table view for a build entry properties 85 | - Open in external MGCB editor action 86 | - Additional file templates 87 | 88 | [Unreleased]: https://github.com/seclerp/rider-monogame/compare/v252.0.0-eap02...v253.1.0 89 | [253.1.0]: https://github.com/seclerp/rider-monogame/compare/v252.0.0-eap02...v253.1.0 90 | [252.0.0-eap02]: https://github.com/seclerp/rider-monogame/compare/v251.1.0...252.0.0-eap02 91 | [251.1.0]: https://github.com/seclerp/rider-monogame/compare/v251.0.0-eap05...v251.1.0 92 | [251.0.0-eap05]: https://github.com/seclerp/rider-monogame/compare/v243.1.0...v251.0.0-eap05 93 | [243.1.0]: https://github.com/seclerp/rider-monogame/compare/v243.0.0-eap02...v243.1.0 94 | [243.0.0-eap02]: https://github.com/seclerp/rider-monogame/compare/v242.1.0...v243.0.0-eap02 95 | [242.1.0]: https://github.com/seclerp/rider-monogame/compare/v242.0.0-eap03...v242.1.0 96 | [242.0.0-eap03]: https://github.com/seclerp/rider-monogame/compare/v241.0.0-rc1...v242.0.0-eap03 97 | [241.0.0-rc1]: https://github.com/seclerp/rider-monogame/compare/v233.1.0...v241.0.0-rc1 98 | [233.1.0]: https://github.com/seclerp/rider-monogame/compare/v232.0.0-rc1...v233.1.0 99 | [232.0.0-rc1]: https://github.com/seclerp/rider-monogame/compare/v231.1.0...v232.0.0-rc1 100 | [231.1.0]: https://github.com/seclerp/rider-monogame/compare/v223.0.0...v231.1.0 101 | [223.0.0]: https://github.com/seclerp/rider-monogame/compare/v1.0.1...v223.0.0 102 | [222.0.0]: https://github.com/seclerp/rider-monogame/compare/v1.0.1...v222.0.0 103 | [1.0.1]: https://github.com/seclerp/rider-monogame/compare/v1.0.0...v1.0.1 104 | [1.0.0]: https://github.com/seclerp/rider-monogame/releases/tag/v1.0.0 105 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy 2 | on: 3 | workflow_run: 4 | workflows: [Build & Text] 5 | types: [completed] 6 | branches: [ release/* ] 7 | jobs: 8 | on-success-build: 9 | runs-on: ubuntu-latest 10 | if: ${{ github.event.workflow_run.conclusion == 'success' }} 11 | environment: production 12 | steps: 13 | - name: 📝 Fetch Sources 14 | uses: actions/checkout@v5 15 | 16 | - name: 🛠 Import environment 17 | uses: ./.github/workflows/environment 18 | 19 | - name: 🛠 Setup JDK 20 | uses: actions/setup-java@v4 21 | with: 22 | java-version: 21 23 | distribution: jetbrains 24 | 25 | - name: 📦 Download artifacts 26 | uses: dawidd6/action-download-artifact@v2 27 | with: 28 | name: plugin-artifacts-ubuntu-latest 29 | path: ${{ env.ARTIFACTS_FOLDER }} 30 | run_id: ${{ github.event.workflow_run.id }} 31 | if_no_artifact_found: fail 32 | 33 | - name: 📦 Tree artifacts 34 | shell: pwsh 35 | run: | 36 | tree ${{ env.ARTIFACTS_FOLDER }} 37 | 38 | - name: 📦 Extract metadata 39 | shell: pwsh 40 | run: | 41 | $PLUGIN_VERSION = (cat "${{ env.ARTIFACTS_FOLDER }}/${{ env.ARTIFACTS_VERSION }}") 42 | echo "PLUGIN_VERSION=$PLUGIN_VERSION" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append 43 | 44 | $PLUGIN_ID = (cat "${{ env.ARTIFACTS_FOLDER }}/${{ env.ARTIFACTS_PLUGIN_ID }}") 45 | echo "PLUGIN_ID=$PLUGIN_ID" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append 46 | 47 | - name: 🚀 Create Release 48 | uses: ncipollo/release-action@v1 49 | with: 50 | name: ${{ env.PLUGIN_VERSION }} 51 | commit: ${{ github.sha }} # Commit SHA 52 | tag: v${{ env.PLUGIN_VERSION }} # 'v' + version from gradle.properties 53 | draft: true 54 | bodyFile: ${{ env.ARTIFACTS_FOLDER }}/${{ env.ARTIFACTS_CHANGELOG }} 55 | artifacts: "${{ env.ARTIFACTS_FOLDER }}/${{ env.PLUGIN_NAME }}-${{ env.PLUGIN_VERSION }}.zip" 56 | token: ${{ secrets.GITHUB_TOKEN }} 57 | 58 | - name: 🚀 Publish Plugin 59 | shell: pwsh 60 | run: | 61 | $uploadUrl = '${{ env.PLUGINS_REPOSITORY }}/api/updates/upload' 62 | $token = '${{ secrets.JB_PUBLISH_TOKEN }}' 63 | Write-Host "Upload URL: $uploadUrl" 64 | 65 | $pluginXmlId = Get-Content -Raw -Path '${{ env.ARTIFACTS_FOLDER }}/${{ env.ARTIFACTS_PLUGIN_ID }}' 66 | Write-Host "Plugin ID to upload: $pluginXmlId" 67 | $channel = 'stable' 68 | Write-Host "Channel: $channel" 69 | $pluginPath = '${{ env.ARTIFACTS_FOLDER }}/${{ env.PLUGIN_NAME }}-${{ env.PLUGIN_VERSION }}.zip' 70 | Write-Host "Plugin archive location: $pluginPath" 71 | 72 | $content = [System.Net.Http.MultipartFormDataContent]::new() 73 | $content.Add((New-Object System.Net.Http.StringContent $pluginXmlId.Trim()), 'xmlId') 74 | $content.Add((New-Object System.Net.Http.StringContent $channel), 'channel') 75 | $filePart = [System.Net.Http.StreamContent]::new([System.IO.File]::OpenRead($pluginPath)) 76 | $filePart.Headers.ContentDisposition = 'form-data; name="file"; filename="{0}"' -f (Split-Path -Leaf $pluginPath) 77 | $content.Add($filePart, 'file') 78 | Write-Host "Multipart form data created" 79 | 80 | Write-Host "Requesting POST $uploadUrl..." 81 | $client = [System.Net.Http.HttpClient]::new() 82 | $client.DefaultRequestHeaders.Authorization = New-Object System.Net.Http.Headers.AuthenticationHeaderValue('Bearer', $token) 83 | $result = $client.PostAsync($uploadUrl, $content).Result 84 | 85 | $status = $result.StatusCode 86 | $isSuccess = $result.IsSuccessStatusCode 87 | Write-Host "Status Code: $status" 88 | Write-Host "Is Success: $isSuccess" 89 | 90 | $responseContent = $result.Content.ReadAsStringAsync().Result 91 | echo "Response: $responseContent" 92 | 93 | if (-not $isSuccess) { 94 | Write-Host "Request failed. Exiting." 95 | exit 1 96 | } 97 | -------------------------------------------------------------------------------- /src/dotnet/Rider.Plugins.MonoGame/Mgcb/MgcbToolsetTracker.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using JetBrains.Application.FileSystemTracker; 3 | using JetBrains.Application.Parts; 4 | using JetBrains.Application.Threading; 5 | using JetBrains.Collections.Viewable; 6 | using JetBrains.DataFlow; 7 | using JetBrains.Lifetimes; 8 | using JetBrains.ProjectModel; 9 | using JetBrains.ProjectModel.NuGet.DotNetTools; 10 | using JetBrains.Util; 11 | using Rider.Plugins.MonoGame.Extensions; 12 | 13 | namespace Rider.Plugins.MonoGame.Mgcb; 14 | 15 | /// 16 | /// MonoGame MGCB tools aka mgcb-editor could be installed in different places:
17 | /// - globally on user's machine (3.8.0 approach)
18 | /// - locally in solution (3.8.0 approach)
19 | /// - locally in project (3.8.1 approach)
20 | /// So we need to track all these places. 21 | ///
22 | [SolutionComponent(Instantiation.ContainerAsyncPrimaryThread)] 23 | public class MgcbToolsetTracker 24 | { 25 | public MgcbToolset MgcbGlobalToolset; 26 | public MgcbToolset MgcbSolutionToolset; 27 | public IViewableMap> MgcbProjectsToolset; 28 | 29 | private IDictionary _projectToolsTrackers = new Dictionary(); 30 | 31 | public MgcbToolsetTracker( 32 | IViewableProjectsCollection projectsCollection, 33 | Lifetime lifetime, 34 | SolutionDotnetToolsTracker solutionToolsTracker, 35 | IFileSystemTracker fileSystemTracker, 36 | IShellLocks locks, 37 | ILogger logger, 38 | ISolutionToolset toolset) 39 | { 40 | // while (!Debugger.IsAttached) Thread.Sleep(1000); 41 | MgcbGlobalToolset = CreateGlobalToolset(solutionToolsTracker); 42 | MgcbSolutionToolset = CreateSolutionToolset(solutionToolsTracker); 43 | MgcbProjectsToolset = new ViewableMap>(); 44 | 45 | projectsCollection.Projects.View( 46 | lifetime, 47 | (projectLifetime, project) => 48 | { 49 | if (project.Kind != ProjectItemKind.PROJECT || project.ProjectFile == null) 50 | return; 51 | 52 | projectLifetime.Bracket( 53 | () => 54 | { 55 | var tracker = new ProjectDotnetToolsTracker(projectLifetime, project, 56 | fileSystemTracker, locks, 57 | logger, toolset); 58 | _projectToolsTrackers.Add(project, tracker); 59 | MgcbProjectsToolset.Add(project, CreateProjectToolset(tracker)); 60 | }, 61 | () => 62 | { 63 | _projectToolsTrackers.Remove(project); 64 | MgcbProjectsToolset[project].Unset(); 65 | MgcbProjectsToolset.Remove(project); 66 | }); 67 | }); 68 | } 69 | 70 | private MgcbToolset CreateGlobalToolset(SolutionDotnetToolsTracker solutionToolsTracker) => 71 | new() 72 | { 73 | Editor = ObserveGlobalTool(solutionToolsTracker), 74 | }; 75 | 76 | private MgcbToolset CreateSolutionToolset(SolutionDotnetToolsTracker solutionToolsTracker) => 77 | new() 78 | { 79 | Editor = ObserveLocalTool(solutionToolsTracker), 80 | }; 81 | 82 | private MgcbToolset CreateProjectToolset(ProjectDotnetToolsTracker projectToolsTracker) => 83 | new() 84 | { 85 | Editor = ObserveLocalTool(projectToolsTracker), 86 | }; 87 | 88 | private IProperty ObserveGlobalTool(NuGetDotnetToolsTrackerBase toolsTracker) => 89 | toolsTracker.DotNetToolCache.Select( 90 | nameof(MgcbToolsetTracker), 91 | cache => cache?.ToolGlobalCache?.Let(MgcbEditorToolResolver.Resolve)); 92 | 93 | private IProperty ObserveLocalTool(NuGetDotnetToolsTrackerBase toolsTracker) => 94 | toolsTracker.DotNetToolCache.Select( 95 | nameof(MgcbToolsetTracker), 96 | cache => cache?.ToolLocalCache?.Let(MgcbEditorToolResolver.Resolve)); 97 | } -------------------------------------------------------------------------------- /src/dotnet/Rider.Plugins.MonoGame/Mgcb/MgcbEditorToolResolver.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Runtime.InteropServices; 4 | using JetBrains.Annotations; 5 | using JetBrains.ProjectModel.NuGet.DotNetTools; 6 | using NuGet.Versioning; 7 | using Rider.Plugins.MonoGame.Extensions; 8 | 9 | namespace Rider.Plugins.MonoGame.Mgcb; 10 | 11 | public static class MgcbEditorToolResolver 12 | { 13 | private static readonly string PlatformSpecificToolName = ResolvePlatformSpecificToolName(); 14 | 15 | [CanBeNull] 16 | public static LocalTool Resolve([NotNull] DotNetToolLocalCache cache) 17 | { 18 | var platformSpecificTool = cache.GetLocalTool(PlatformSpecificToolName); 19 | var platformSpecificToolRef = platformSpecificTool?.Let(VersionedTool.Create); 20 | 21 | var platformAgnosticTool = cache.GetLocalTool(KnownDotNetTools.MgcbEditor); 22 | var platformAgnosticToolRef = platformAgnosticTool?.Let(VersionedTool.Create); 23 | 24 | return Resolve(platformSpecificToolRef, platformAgnosticToolRef)?.Tool; 25 | } 26 | 27 | [CanBeNull] 28 | public static GlobalToolCacheEntry Resolve([NotNull] DotNetToolGlobalCache cache) 29 | { 30 | var platformSpecificTool = cache.GetGlobalTool(PlatformSpecificToolName)?.FirstOrDefault(); 31 | var platformSpecificToolRef = platformSpecificTool?.Let(VersionedTool.Create); 32 | 33 | var platformAgnosticTool = cache.GetGlobalTool(KnownDotNetTools.MgcbEditor)?.FirstOrDefault(); 34 | var platformAgnosticToolRef = platformAgnosticTool?.Let(VersionedTool.Create); 35 | 36 | return Resolve(platformSpecificToolRef, platformAgnosticToolRef)?.Tool; 37 | } 38 | 39 | [CanBeNull] 40 | private static VersionedTool Resolve( 41 | [CanBeNull] VersionedTool platformSpecificVersionedTool, 42 | [CanBeNull] VersionedTool platformAgnosticVersionedTool) 43 | { 44 | switch (platformSpecificVersionedTool, platformAgnosticVersionedTool) 45 | { 46 | // If we have only platform-specific (i.e. mgcb-editor-mac) tool, use it 47 | case (not null, null): 48 | { 49 | return platformSpecificVersionedTool; 50 | } 51 | // If we have only platform-agnostic tool (i.e. mgcb-editor) 52 | case (null, not null): 53 | { 54 | // We need to check if it's only a bootstrapper (>=3.8.1) or an editor itself (<=3.8.0) 55 | // If it's a bootsrapper, return null as it requires a platform-specific tool that we checked for before 56 | if (platformAgnosticVersionedTool.Version >= KnownMgcbVersions.Version381) 57 | { 58 | return null; 59 | } 60 | 61 | // If it's an editor itself, use it 62 | return platformAgnosticVersionedTool; 63 | } 64 | // If we have both, specific one always has the priority because it's always newer by default 65 | case (not null, not null): 66 | { 67 | return platformSpecificVersionedTool; 68 | } 69 | // Nothing at all, nothing to choose from. 70 | case (null, null): 71 | { 72 | return null; 73 | } 74 | } 75 | } 76 | 77 | private static string ResolvePlatformSpecificToolName() 78 | { 79 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) 80 | return KnownDotNetTools.MgcbEditorWindows; 81 | 82 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) 83 | return KnownDotNetTools.MgcbEditorLinux; 84 | 85 | if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) 86 | return KnownDotNetTools.MgcbEditorMac; 87 | 88 | throw new NotImplementedException("Unsupported Operating System"); 89 | } 90 | 91 | private record VersionedTool( 92 | NuGetVersion Version, 93 | [CanBeNull] TTool Tool 94 | ); 95 | 96 | private static class VersionedTool 97 | { 98 | public static VersionedTool Create(GlobalToolCacheEntry tool) => 99 | new(tool.Version, tool); 100 | 101 | public static VersionedTool Create(LocalTool tool) => 102 | new(NuGetVersion.Parse(tool.Version), tool); 103 | } 104 | } -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/mgcb/actions/OpenExternalEditorAction.kt: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.plugins.monogame.mgcb.actions 2 | 3 | import com.intellij.openapi.actionSystem.ActionUpdateThread 4 | import com.intellij.openapi.actionSystem.AnAction 5 | import com.intellij.openapi.actionSystem.AnActionEvent 6 | import com.intellij.openapi.actionSystem.CommonDataKeys 7 | import com.intellij.openapi.project.Project 8 | import com.intellij.openapi.vfs.VirtualFile 9 | import com.intellij.platform.backend.workspace.workspaceModel 10 | import com.jetbrains.rider.model.RdProjectDescriptor 11 | import me.seclerp.rider.extensions.commandLine.CommandBuilder 12 | import me.seclerp.rider.extensions.commandLine.DefaultCommandExecutor 13 | import me.seclerp.rider.extensions.commandLine.buildCommand 14 | import me.seclerp.rider.extensions.commandLine.buildDotnetCommand 15 | import me.seclerp.rider.extensions.workspaceModel.containingProjectDirectory 16 | import me.seclerp.rider.extensions.workspaceModel.containingProjectEntity 17 | import me.seclerp.rider.plugins.monogame.MonoGameIcons 18 | import me.seclerp.rider.plugins.monogame.MonoGameUiBundle 19 | import me.seclerp.rider.plugins.monogame.mgcb.toolset.MgcbResolvedTool 20 | import me.seclerp.rider.plugins.monogame.mgcb.toolset.MgcbToolsetHost 21 | 22 | @Suppress("DialogTitleCapitalization", "UnstableApiUsage") 23 | class OpenExternalEditorAction : AnAction(MonoGameIcons.MgcbFile) { 24 | override fun actionPerformed(actionEvent: AnActionEvent) { 25 | val intellijProject = actionEvent.project ?: return 26 | val file = actionEvent.dataContext.getData(CommonDataKeys.VIRTUAL_FILE) ?: return 27 | val dotnetProject = intellijProject.workspaceModel.containingProjectEntity(file, intellijProject) ?: return 28 | val descriptor = dotnetProject.descriptor as? RdProjectDescriptor ?: return 29 | val dotnetProjectDirectory = intellijProject.workspaceModel.containingProjectDirectory(file, intellijProject) ?: return 30 | val editorTool = MgcbToolsetHost.getInstance(intellijProject).getEditorTool(descriptor) 31 | runEditor(intellijProject, editorTool, dotnetProjectDirectory, file) 32 | } 33 | 34 | override fun getActionUpdateThread() = ActionUpdateThread.EDT 35 | 36 | override fun update(actionEvent: AnActionEvent) { 37 | actionEvent.presentation.isEnabledAndVisible = false 38 | val intellijProject = actionEvent.project ?: return 39 | val file = actionEvent.dataContext.getData(CommonDataKeys.VIRTUAL_FILE) ?: return 40 | val dotnetProject = intellijProject.workspaceModel.containingProjectEntity(file, intellijProject) ?: return 41 | val descriptor = dotnetProject.descriptor as? RdProjectDescriptor ?: return 42 | val mgcbEditorInstalled = MgcbToolsetHost.getInstance(intellijProject).areToolsAvailable(descriptor) 43 | actionEvent.presentation.isVisible = true 44 | actionEvent.presentation.isEnabled = mgcbEditorInstalled 45 | actionEvent.presentation.text = 46 | if (mgcbEditorInstalled) 47 | MonoGameUiBundle.message("command.mgcb.open.title") 48 | else 49 | MonoGameUiBundle.message("command.mgcb.open.missing.editor.title") 50 | } 51 | 52 | // .NET tools have different rules for running local and global tools. 53 | // For global tool, it should be executed as a stand-alone program, like that: 54 | // > mgcb-editor 55 | // For local tool, it should be executed as a sub-command for 'dotnet', like that: 56 | // > dotnet mgcb-editor 57 | // or in more explicit way: 58 | // > dotnet tool run mgcb-editor 59 | // Source: https://learn.microsoft.com/en-us/dotnet/core/tools/global-tools#use-a-tool 60 | private fun runEditor(intellijProject: Project, editorTool: MgcbResolvedTool, projectDirectory: VirtualFile, contentFile: VirtualFile) { 61 | fun CommandBuilder.configureEditorCommand() { 62 | workingDirectory(projectDirectory.path) 63 | param(contentFile.path) 64 | } 65 | 66 | val command = 67 | when (editorTool) { 68 | is MgcbResolvedTool.Local -> buildDotnetCommand(intellijProject, editorTool.definition.commandName) { configureEditorCommand() } 69 | is MgcbResolvedTool.Global -> buildCommand(editorTool.definition.commandName) { configureEditorCommand() } 70 | is MgcbResolvedTool.None -> null 71 | } 72 | 73 | DefaultCommandExecutor.getInstance(intellijProject).execute(command ?: return) 74 | } 75 | } -------------------------------------------------------------------------------- /src/rider/main/resources/META-INF/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | me.seclerp.rider.plugins.monogame 3 | MonoGame 4 | _PLACEHOLDER_ 5 | Andrew Rublyov 6 | 7 | com.intellij.modules.rider 8 | messages.MonoGameUiBundle 9 | 10 | 11 | This plugin improves MonoGame usage experience inside JetBrains Rider

13 |

14 | MGCB file editing 15 |

    16 |
  • Autocomplete and syntax highlighting
  • 17 |
  • Build entries tree previewer
  • 18 |
  • Table view for a build entry properties
  • 19 |
  • Open in external MGCB editor action
  • 20 |
21 | File templates for 22 |
    23 |
  • MGCB
  • 24 |
  • Effect
  • 25 |
  • Sprite Effect
  • 26 |
  • SpriteFont
  • 27 |
  • Localized SpriteFont
  • 28 |
29 |

30 |

31 | Links 32 |

36 |

37 | ]]> 38 |
39 | 40 | 41 | 43 | 44 | 46 | 47 | 49 | 50 | 52 | 54 | 55 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 79 | 80 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 95 | 97 | 98 | 99 |
100 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem GRADLE JVM WRAPPER START MARKER 42 | 43 | setlocal 44 | set BUILD_DIR=%LOCALAPPDATA%\gradle-jvm 45 | set JVM_TARGET_DIR=%BUILD_DIR%\jdk-21.0.3_windows-x64_bin-125c41\ 46 | 47 | set JVM_URL=https://download.oracle.com/java/21/archive/jdk-21.0.3_windows-x64_bin.zip 48 | 49 | set IS_TAR_GZ=0 50 | set JVM_TEMP_FILE=gradle-jvm.zip 51 | 52 | if /I "%JVM_URL:~-7%"==".tar.gz" ( 53 | set IS_TAR_GZ=1 54 | set JVM_TEMP_FILE=gradle-jvm.tar.gz 55 | ) 56 | 57 | set POWERSHELL=%SystemRoot%\system32\WindowsPowerShell\v1.0\powershell.exe 58 | 59 | if not exist "%JVM_TARGET_DIR%" MD "%JVM_TARGET_DIR%" 60 | 61 | if not exist "%JVM_TARGET_DIR%.flag" goto downloadAndExtractJvm 62 | 63 | set /p CURRENT_FLAG=<"%JVM_TARGET_DIR%.flag" 64 | if "%CURRENT_FLAG%" == "%JVM_URL%" goto continueWithJvm 65 | 66 | :downloadAndExtractJvm 67 | 68 | PUSHD "%BUILD_DIR%" 69 | if errorlevel 1 goto fail 70 | 71 | echo Downloading %JVM_URL% to %BUILD_DIR%\%JVM_TEMP_FILE% 72 | if exist "%JVM_TEMP_FILE%" DEL /F "%JVM_TEMP_FILE%" 73 | "%POWERSHELL%" -nologo -noprofile -Command "Set-StrictMode -Version 3.0; $ErrorActionPreference = \"Stop\"; (New-Object Net.WebClient).DownloadFile('%JVM_URL%', '%JVM_TEMP_FILE%')" 74 | if errorlevel 1 goto fail 75 | 76 | POPD 77 | 78 | RMDIR /S /Q "%JVM_TARGET_DIR%" 79 | if errorlevel 1 goto fail 80 | 81 | MKDIR "%JVM_TARGET_DIR%" 82 | if errorlevel 1 goto fail 83 | 84 | PUSHD "%JVM_TARGET_DIR%" 85 | if errorlevel 1 goto fail 86 | 87 | echo Extracting %BUILD_DIR%\%JVM_TEMP_FILE% to %JVM_TARGET_DIR% 88 | 89 | if "%IS_TAR_GZ%"=="1" ( 90 | tar xf "..\\%JVM_TEMP_FILE%" 91 | ) else ( 92 | "%POWERSHELL%" -nologo -noprofile -command "Set-StrictMode -Version 3.0; $ErrorActionPreference = \"Stop\"; Add-Type -A 'System.IO.Compression.FileSystem'; [IO.Compression.ZipFile]::ExtractToDirectory('..\\%JVM_TEMP_FILE%', '.');" 93 | ) 94 | if errorlevel 1 goto fail 95 | 96 | DEL /F "..\%JVM_TEMP_FILE%" 97 | if errorlevel 1 goto fail 98 | 99 | POPD 100 | 101 | echo %JVM_URL%>"%JVM_TARGET_DIR%.flag" 102 | if errorlevel 1 goto fail 103 | 104 | :continueWithJvm 105 | 106 | set JAVA_HOME= 107 | for /d %%d in ("%JVM_TARGET_DIR%"*) do if exist "%%d\bin\java.exe" set JAVA_HOME=%%d 108 | if not exist "%JAVA_HOME%\bin\java.exe" ( 109 | echo Unable to find java.exe under %JVM_TARGET_DIR% 110 | goto fail 111 | ) 112 | 113 | endlocal & set JAVA_HOME=%JAVA_HOME% 114 | 115 | @rem GRADLE JVM WRAPPER END MARKER 116 | 117 | @rem Find java.exe 118 | if defined JAVA_HOME goto findJavaFromJavaHome 119 | 120 | set JAVA_EXE=java.exe 121 | %JAVA_EXE% -version >NUL 2>&1 122 | if %ERRORLEVEL% equ 0 goto execute 123 | 124 | echo. 1>&2 125 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 126 | echo. 1>&2 127 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 128 | echo location of your Java installation. 1>&2 129 | 130 | goto fail 131 | 132 | :findJavaFromJavaHome 133 | set JAVA_HOME=%JAVA_HOME:"=% 134 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 135 | 136 | if exist "%JAVA_EXE%" goto execute 137 | 138 | echo. 1>&2 139 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 140 | echo. 1>&2 141 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 142 | echo location of your Java installation. 1>&2 143 | 144 | goto fail 145 | 146 | :execute 147 | @rem Setup the command line 148 | 149 | set CLASSPATH= 150 | 151 | 152 | @rem Execute Gradle 153 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* 154 | 155 | :end 156 | @rem End local scope for the variables with windows NT shell 157 | if %ERRORLEVEL% equ 0 goto mainEnd 158 | 159 | :fail 160 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 161 | rem the _cmd.exe /c_ return code! 162 | set EXIT_CODE=%ERRORLEVEL% 163 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 164 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 165 | exit /b %EXIT_CODE% 166 | 167 | :mainEnd 168 | if "%OS%"=="Windows_NT" endlocal 169 | 170 | :omega 171 | -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/settings/MgcbToolsetConfigurableProvider.kt: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.plugins.monogame.settings 2 | 3 | import com.intellij.openapi.observable.properties.AtomicProperty 4 | import com.intellij.openapi.options.ConfigurableProvider 5 | import com.intellij.openapi.options.SearchableConfigurable 6 | import com.intellij.openapi.project.Project 7 | import com.intellij.openapi.ui.DialogPanel 8 | import com.intellij.openapi.util.text.HtmlBuilder 9 | import com.intellij.platform.backend.workspace.workspaceModel 10 | import com.intellij.ui.dsl.builder.bindText 11 | import com.intellij.ui.dsl.builder.panel 12 | import com.jetbrains.rd.platform.util.lifetime 13 | import com.jetbrains.rd.util.reactive.AddRemove 14 | import com.jetbrains.rd.util.reactive.map 15 | import com.jetbrains.rider.model.RdProjectDescriptor 16 | import com.jetbrains.rider.projectView.workspace.findProjects 17 | import me.seclerp.rider.extensions.observables.RdObservableProperty 18 | import me.seclerp.rider.plugins.monogame.MonoGameUiBundle 19 | import me.seclerp.rider.plugins.monogame.ToolDefinition 20 | import me.seclerp.rider.plugins.monogame.mgcb.toolset.MgcbToolsetHost 21 | import java.util.UUID 22 | 23 | class MgcbToolsetConfigurableProvider(private val project: Project) : ConfigurableProvider() { 24 | override fun createConfigurable() = MgcbToolsetConfigurable(project) 25 | 26 | @Suppress("UnstableApiUsage") 27 | class MgcbToolsetConfigurable(private val project: Project) : SearchableConfigurable { 28 | private val lifetime = project.lifetime.createNested() 29 | private val fallbackProperty by lazy { AtomicProperty(MonoGameUiBundle.message("loading.title")) } 30 | private val mgcbToolsetHost by lazy { MgcbToolsetHost.getInstance(project) } 31 | 32 | private val globalToolsetInfo by lazy { 33 | if (project.isDefault) fallbackProperty 34 | else mgcbToolsetHost.globalToolset.editor 35 | .map { HtmlBuilder().append(getPresentableToolsetInfo(it)).wrapWith("code").wrapWith("html").toString() } 36 | .let { RdObservableProperty(it, lifetime) } 37 | } 38 | 39 | private val solutionToolsetInfo by lazy { 40 | if (project.isDefault) fallbackProperty 41 | else mgcbToolsetHost.solutionToolset.editor 42 | .map { HtmlBuilder().append(getPresentableToolsetInfo(it)).wrapWith("code").wrapWith("html").toString() } 43 | .let { RdObservableProperty(it, lifetime) } 44 | } 45 | 46 | private val projectsToolsetInfo by lazy { 47 | if (project.isDefault) fallbackProperty 48 | else { 49 | val toolMap = mutableMapOf>() 50 | val property = AtomicProperty(MonoGameUiBundle.message("loading.title")) 51 | mgcbToolsetHost.projectsToolset 52 | .adviseAddRemove(lifetime) { event, key, value -> 53 | when (event) { 54 | AddRemove.Add -> { 55 | value.editor.view(lifetime) { lifetime, editor -> 56 | val toolsetInfo = RdObservableProperty(value.editor.map(::getPresentableToolsetInfo), lifetime) 57 | toolMap[key] = toolsetInfo 58 | property.set(getPresentableProjectsInfo(toolMap)) 59 | lifetime.onTermination { toolMap.remove(key) } 60 | } 61 | } 62 | 63 | AddRemove.Remove -> { 64 | toolMap.remove(key) 65 | } 66 | } 67 | 68 | property.set(getPresentableProjectsInfo(toolMap)) 69 | } 70 | property 71 | } 72 | } 73 | 74 | @Suppress("DialogTitleCapitalization") 75 | override fun createComponent(): DialogPanel { 76 | return panel { 77 | group(MonoGameUiBundle.message("settings.mgcb.editor.group")) { 78 | row(MonoGameUiBundle.message("settings.mgcb.editor.group.global")) { 79 | label(MonoGameUiBundle.message("loading.title")) 80 | .bindText(globalToolsetInfo) 81 | } 82 | row(MonoGameUiBundle.message("settings.mgcb.editor.group.solution")) { 83 | label(MonoGameUiBundle.message("loading.title")) 84 | .bindText(solutionToolsetInfo) 85 | } 86 | row(MonoGameUiBundle.message("settings.mgcb.editor.group.project")) { 87 | label(MonoGameUiBundle.message("loading.title")) 88 | .bindText(projectsToolsetInfo) 89 | } 90 | } 91 | } 92 | } 93 | 94 | override fun disposeUIResources() { 95 | lifetime.terminate() 96 | } 97 | 98 | override fun isModified(): Boolean { 99 | return false 100 | } 101 | 102 | override fun apply() { 103 | // TODO 104 | } 105 | 106 | override fun getDisplayName() = MonoGameUiBundle.message("settings.mgcb.display.name") 107 | override fun getId() = "monogame.tools.mgcb" 108 | 109 | private fun getPresentableToolsetInfo(editor: ToolDefinition?) = 110 | getPresentableToolInfo("mgcb-editor", editor) 111 | 112 | private fun getPresentableToolInfo(fallbackName: String, tool: ToolDefinition?) = buildString { 113 | val name = tool?.packageId ?: fallbackName 114 | append("$name: ") 115 | if (tool == null) { 116 | append("Not found") 117 | } else { 118 | append(tool.version) 119 | } 120 | } 121 | 122 | private fun getProjectName(id: UUID) = project.workspaceModel 123 | .findProjects() 124 | .mapNotNull { it.descriptor as? RdProjectDescriptor } 125 | .firstOrNull { it.originalGuid == id } 126 | ?.name ?: "Unknown" 127 | 128 | private fun getPresentableProjectsInfo(toolMap: MutableMap>): String = toolMap 129 | .map { "${getProjectName(it.key)}: ${it.value.get()}" } 130 | .joinToString("\n") 131 | .let { HtmlBuilder().append(it).wrapWith("code").wrapWith("html").toString() } 132 | } 133 | } -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/templates/MonoGameProjectTemplateGenerator.kt: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.plugins.monogame.templates 2 | 3 | import com.intellij.icons.AllIcons 4 | import com.intellij.openapi.editor.colors.EditorColorsManager 5 | import com.intellij.openapi.editor.colors.EditorFontType 6 | import com.intellij.openapi.ui.DialogPanel 7 | import com.intellij.ui.AnimatedIcon 8 | import com.intellij.ui.EditorNotificationPanel 9 | import com.intellij.ui.components.fields.ExtendableTextComponent 10 | import com.intellij.ui.components.fields.ExtendableTextField 11 | import com.intellij.ui.components.panels.NonOpaquePanel 12 | import com.intellij.ui.components.panels.VerticalLayout 13 | import com.intellij.ui.dsl.builder.Panel 14 | import com.intellij.ui.dsl.builder.Row 15 | import com.intellij.ui.dsl.builder.TopGap 16 | import com.intellij.ui.dsl.builder.panel 17 | import com.intellij.util.ui.JBUI 18 | import com.jetbrains.rd.util.lifetime.Lifetime 19 | import com.jetbrains.rd.util.reactive.IOptProperty 20 | import com.jetbrains.rider.model.RdProjectTemplate 21 | import com.jetbrains.rider.projectView.projectTemplates.NewProjectDialogContext 22 | import com.jetbrains.rider.projectView.projectTemplates.ProjectTemplatesSharedModel 23 | import com.jetbrains.rider.projectView.projectTemplates.generators.TypeListBasedProjectTemplateGenerator 24 | import me.seclerp.rider.extensions.ClipboardUtil 25 | import me.seclerp.rider.plugins.monogame.MonoGameIcons 26 | import me.seclerp.rider.plugins.monogame.MonoGameUiBundle 27 | import me.seclerp.rider.plugins.monogame.templates.MonoGameTemplateMetadata.Names 28 | import java.awt.BorderLayout 29 | import java.awt.Component 30 | import javax.swing.JLabel 31 | import javax.swing.SwingConstants 32 | 33 | internal class MonoGameProjectTemplateGenerator( 34 | lifetime: Lifetime, 35 | context: NewProjectDialogContext, 36 | sharedModel: ProjectTemplatesSharedModel, 37 | projectTemplates: IOptProperty> 38 | ) : TypeListBasedProjectTemplateGenerator(lifetime, context, sharedModel, projectTemplates) { 39 | init { 40 | context.isReady.afterChange { 41 | val templates = tryGetAcceptableTemplates() 42 | when { 43 | it && templates.isNullOrEmpty() -> setTemplatesMissingState() 44 | it && !templates.isNullOrEmpty() -> setReadyState() 45 | else -> setLoadingState() 46 | } 47 | } 48 | } 49 | 50 | override val defaultName = "MonoGameProject1" 51 | 52 | override fun getPredefinedTypes() = listOf( 53 | TemplateTypeWithIcon(Names.CROSS_PLATFORM_APP, MonoGameIcons.MgcbFile), 54 | TemplateTypeWithIcon(Names.WINDOWS_DESKTOP_APP, MonoGameIcons.MgcbFile), 55 | TemplateTypeWithIcon(Names.ANDROID_APP, MonoGameIcons.MgcbFile), 56 | TemplateTypeWithIcon(Names.IOS_APP, MonoGameIcons.MgcbFile), 57 | TemplateTypeWithIcon(Names.GAME_LIB, MonoGameIcons.MgcbFile), 58 | TemplateTypeWithIcon(Names.CONTENT_PIPELINE_EXTENSION, MonoGameIcons.MgcbFile), 59 | TemplateTypeWithIcon(Names.SHARED_LIB, MonoGameIcons.MgcbFile), 60 | ) 61 | 62 | override fun getType(template: RdProjectTemplate) = template.name 63 | .removePrefix("MonoGame ") 64 | .replace("Application", "App") 65 | 66 | private var myLoadingRow: Row? = null 67 | private var myTemplatesMissingRow: Row? = null 68 | private var myTemplatesRow: Row? = null 69 | 70 | override fun createTemplateSpecificPanelAfterLanguage(): DialogPanel { 71 | return panel { 72 | myLoadingRow = createLoadingRow().apply { visible(false)} 73 | myTemplatesMissingRow = createMissingTemplatesRow().apply { visible(false)} 74 | myTemplatesRow = createReadyRow().apply { visible(false)} 75 | setLoadingState() 76 | } 77 | } 78 | 79 | private fun setLoadingState() { 80 | myTemplatesRow?.visible(false) 81 | myTemplatesMissingRow?.visible(false) 82 | myLoadingRow?.visible(true) 83 | } 84 | 85 | private fun setReadyState() { 86 | myLoadingRow?.visible(false) 87 | myTemplatesMissingRow?.visible(false) 88 | myTemplatesRow?.visible(true) 89 | } 90 | 91 | private fun setTemplatesMissingState() { 92 | myLoadingRow?.visible(false) 93 | myTemplatesRow?.visible(false) 94 | myTemplatesMissingRow?.visible(true) 95 | } 96 | 97 | private fun Panel.createLoadingRow() = row(" ") { 98 | cell(JLabel(MonoGameUiBundle.message("templates.loading.templates"), AnimatedIcon.Default(), SwingConstants.LEFT)) 99 | } 100 | 101 | private fun Panel.createReadyRow() = row { 102 | cell(super.createTemplateSpecificPanelAfterLanguage()) 103 | } 104 | 105 | private fun Panel.createMissingTemplatesRow() = row(" ") { 106 | val installCommand = "dotnet new install MonoGame.Templates.CSharp" 107 | cell(CustomNotificationPanel(EditorNotificationPanel.Status.Warning)) 108 | .applyToComponent { 109 | text(MonoGameUiBundle.message("templates.no.templates.found")) 110 | addBottomItem(JLabel(MonoGameUiBundle.message("templates.no.templates.install.hint")).apply { 111 | border = JBUI.Borders.emptyTop(8) 112 | }) 113 | addBottomItem(CodeSnippetTextField(installCommand, 50)) 114 | } 115 | .resizableColumn() 116 | topGap(TopGap.SMALL) 117 | } 118 | 119 | private class CustomNotificationPanel(status: Status) : EditorNotificationPanel(status) { 120 | private val myBottomPanel = NonOpaquePanel(VerticalLayout(8, VerticalLayout.FILL)) 121 | 122 | init { 123 | add(myBottomPanel, BorderLayout.SOUTH) 124 | } 125 | 126 | fun addBottomItem(comp: Component) { 127 | myBottomPanel.add(comp) 128 | } 129 | } 130 | 131 | private class CodeSnippetTextField(text: String, columns: Int = 20) : ExtendableTextField(text, columns) { 132 | init { 133 | isEditable = false 134 | font = EditorColorsManager.getInstance().globalScheme.getFont(EditorFontType.PLAIN) 135 | addExtension(CopyToClipboardExtension()) 136 | } 137 | 138 | private inner class CopyToClipboardExtension : ExtendableTextComponent.Extension { 139 | override fun getIcon(hovered: Boolean) = AllIcons.General.Copy 140 | 141 | override fun getActionOnClick() = Runnable { 142 | ClipboardUtil.copyToClipboard(this@CodeSnippetTextField.text) 143 | } 144 | } 145 | } 146 | } -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/mgcb/previewer/MgcbEditorPreviewer.kt: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.plugins.monogame.mgcb.previewer 2 | 3 | import com.intellij.openapi.editor.colors.EditorColors 4 | import com.intellij.openapi.editor.colors.EditorColorsManager 5 | import com.intellij.openapi.fileEditor.FileEditor 6 | import com.intellij.openapi.fileEditor.FileEditorLocation 7 | import com.intellij.openapi.fileEditor.FileEditorState 8 | import com.intellij.openapi.project.Project 9 | import com.intellij.openapi.util.UserDataHolderBase 10 | import com.intellij.openapi.vfs.VirtualFile 11 | import com.intellij.psi.PsiManager 12 | import com.intellij.ui.JBColor 13 | import com.intellij.ui.JBSplitter 14 | import com.intellij.ui.components.JBLabel 15 | import com.intellij.ui.components.JBScrollPane 16 | import com.intellij.ui.scale.JBUIScale 17 | import com.intellij.util.ui.JBUI 18 | import com.jetbrains.rider.util.idea.getService 19 | import me.seclerp.rider.plugins.monogame.mgcb.previewer.listeners.MgcbProcessedUpdateListener 20 | import me.seclerp.rider.plugins.monogame.mgcb.previewer.properties.MgcbProperty 21 | import me.seclerp.rider.plugins.monogame.mgcb.previewer.properties.MgcbPropertyTableModel 22 | import me.seclerp.rider.plugins.monogame.mgcb.previewer.properties.MgcbPropertyTable 23 | import me.seclerp.rider.plugins.monogame.mgcb.previewer.services.MgcbAnalyzer 24 | import me.seclerp.rider.plugins.monogame.mgcb.previewer.services.MgcbBuildTreeManager 25 | import me.seclerp.rider.plugins.monogame.mgcb.previewer.tree.MgcbBuildEntryNode 26 | import me.seclerp.rider.plugins.monogame.mgcb.previewer.tree.MgcbTreeNode 27 | import me.seclerp.rider.plugins.monogame.mgcb.psi.MgcbFile 28 | import me.seclerp.rider.plugins.monogame.removeAllRows 29 | import java.beans.PropertyChangeListener 30 | import javax.swing.JPanel 31 | 32 | class MgcbEditorPreviewer( 33 | private val project: Project, 34 | private val currentFile: VirtualFile, 35 | ) : UserDataHolderBase(), FileEditor { 36 | 37 | private val analyzerService = project.getService() 38 | private val mgcbTreeService = project.getService() 39 | 40 | private var model: MgcbModel? = null 41 | private val tree = mgcbTreeService.createEmpty() 42 | 43 | private val propertiesModel = MgcbPropertyTableModel() 44 | private val processorParamsModel = MgcbPropertyTableModel() 45 | 46 | private val connection = project.messageBus.connect() 47 | 48 | private val previewerPanel = lazy { 49 | val root = JBSplitter(false, 0.5f).apply { 50 | dividerWidth = 2 51 | val schema = EditorColorsManager.getInstance().globalScheme 52 | divider.background = JBColor.lazy { 53 | schema.getColor(EditorColors.PREVIEW_BORDER_COLOR) ?: schema.defaultBackground 54 | } 55 | } 56 | 57 | val mgcbFile = PsiManager.getInstance(project).findFile(currentFile) as MgcbFile 58 | model = analyzerService.analyzeFile(mgcbFile) 59 | 60 | val entriesTree = getBuildEntriesTreePanel() 61 | val propertiesPanel = getPropertiesPanel() 62 | 63 | connection.subscribe(MgcbPreviewerTopics.MGCB_PROCESSED_UPDATE_TOPIC, object : MgcbProcessedUpdateListener { 64 | override fun handle(file: VirtualFile, mgcbModel: MgcbModel) { 65 | if (file == currentFile) { 66 | applyMgcbModel(mgcbModel) 67 | } 68 | } 69 | }) 70 | 71 | root.apply { 72 | firstComponent = entriesTree 73 | secondComponent = propertiesPanel 74 | } 75 | } 76 | 77 | private fun applyMgcbModel(mgcbModel: MgcbModel) { 78 | mgcbTreeService.updateTree(mgcbModel, tree) 79 | } 80 | 81 | private fun getBuildEntriesTreePanel(): JPanel { 82 | mgcbTreeService.updateTree(model!!, tree) 83 | 84 | tree.addTreeSelectionListener { 85 | selectedNodeChanged(tree.lastSelectedPathComponent as? MgcbTreeNode) 86 | } 87 | 88 | return JBUI.Panels.simplePanel(tree) 89 | } 90 | 91 | private fun getPropertiesPanel(): JPanel { 92 | val propertiesPanel = createKeyValueTable("Properties", propertiesModel) 93 | val processorParamsPanel = createKeyValueTable("Processor parameters", processorParamsModel) 94 | 95 | return JBSplitter(true, 0.75f).apply { 96 | firstComponent = propertiesPanel 97 | secondComponent = processorParamsPanel 98 | dividerWidth = 2 99 | val schema = EditorColorsManager.getInstance().globalScheme 100 | divider.background = JBColor.lazy { 101 | schema.getColor(EditorColors.PREVIEW_BORDER_COLOR) ?: schema.defaultBackground 102 | } 103 | } 104 | } 105 | 106 | private fun selectedNodeChanged(node: MgcbTreeNode?) { 107 | propertiesModel.removeAllRows() 108 | processorParamsModel.removeAllRows() 109 | 110 | when(node) { 111 | is MgcbBuildEntryNode -> { 112 | propertiesModel.apply { 113 | addRow(MgcbProperty("Name", node.userObject.toString())) 114 | addRow(MgcbProperty("Source Path", node.buildEntry.contentFilepath ?: "")) 115 | addRow(MgcbProperty("Destination Path", node.buildEntry.destinationFilepath ?: node.buildEntry.contentFilepath ?: "")) 116 | addRow(MgcbProperty("Importer", node.buildEntry.importer ?: "")) 117 | addRow(MgcbProperty("Processor", node.buildEntry.processor ?: "")) 118 | } 119 | 120 | processorParamsModel.apply { 121 | node.buildEntry.processorParams.forEach { 122 | addRow(MgcbProperty(it.key, it.value)) 123 | } 124 | } 125 | } 126 | } 127 | } 128 | 129 | private fun createKeyValueTable(label: String, model: MgcbPropertyTableModel): JPanel { 130 | val propertiesTable = MgcbPropertyTable(model).apply { 131 | border = JBUI.Borders.empty(JBUI.insets(JBUIScale.scale(8))) 132 | } 133 | val title = JBLabel(label).apply { 134 | border = JBUI.Borders.empty(JBUI.insets(JBUIScale.scale(8))) 135 | } 136 | 137 | return JBUI.Panels.simplePanel(JBScrollPane(propertiesTable)) 138 | .addToTop(title) 139 | } 140 | 141 | override fun getComponent() = previewerPanel.value 142 | 143 | override fun getPreferredFocusedComponent() = component 144 | override fun getName(): String = "MGCB Preview" 145 | override fun isModified() = false 146 | override fun isValid() = true 147 | 148 | override fun setState(p0: FileEditorState) {} 149 | override fun addPropertyChangeListener(p0: PropertyChangeListener) {} 150 | override fun removePropertyChangeListener(p0: PropertyChangeListener) {} 151 | override fun getCurrentLocation(): FileEditorLocation? = null 152 | 153 | override fun dispose() { 154 | connection.dispose() 155 | } 156 | } -------------------------------------------------------------------------------- /src/rider/main/kotlin/me/seclerp/rider/plugins/monogame/mgcb/previewer/services/MgcbBuildTreeManager.kt: -------------------------------------------------------------------------------- 1 | package me.seclerp.rider.plugins.monogame.mgcb.previewer.services 2 | 3 | import com.intellij.openapi.components.Service 4 | import com.intellij.openapi.util.IconLoader 5 | import me.seclerp.rider.plugins.monogame.mgcb.previewer.BuildEntry 6 | import me.seclerp.rider.plugins.monogame.mgcb.previewer.MgcbModel 7 | import me.seclerp.rider.plugins.monogame.mgcb.previewer.tree.* 8 | import me.seclerp.rider.plugins.monogame.substringAfterLast 9 | import me.seclerp.rider.plugins.monogame.substringBeforeLast 10 | 11 | @Service 12 | class MgcbBuildTreeManager { 13 | fun createEmpty(): MgcbTree { 14 | val tree = MgcbTree() 15 | tree.cellRenderer = MgcbNodeRenderer() 16 | tree.isRootVisible = true 17 | 18 | return tree 19 | } 20 | 21 | fun updateTree(model: MgcbModel, tree: MgcbTree) { 22 | val buildPaths = model.buildEntries 23 | 24 | // 1. Get all parent folders per each path 25 | val parentsPerPath = buildPaths.map { getParentsHierarchy(it) } 26 | 27 | // 2. Merge + distinct 28 | val flatten = parentsPerPath.flatten().distinctBy { it.first } 29 | 30 | // 3. Get full parent paths 31 | val pathsWithParents = flatten.map { Triple(getParentPath(it.first), it.first, it.second) } 32 | 33 | // 4. Add each item to the corresponding parent in dict 34 | val nodeCache = buildNodeCache(pathsWithParents) 35 | 36 | // 5. Process every tree node and create corresponding tree 37 | val topLevelNodes = pathsWithParents 38 | .filter { it.first == "" } 39 | .map { createNodeFrom(it.second, nodeCache) } 40 | 41 | val currentSelection = tree.getSelectedUrl() 42 | val currentExpanded = tree.getExpandedUrls() 43 | 44 | tree.clear() 45 | 46 | for (node in topLevelNodes) { 47 | tree.root.add(node) 48 | } 49 | 50 | tree.reload() 51 | tree.restoreExpandedUrl(currentExpanded) 52 | 53 | if (currentSelection != null) { 54 | tree.restoreSelectedUrl(currentSelection) 55 | } 56 | } 57 | 58 | // Will transform 59 | // a/b/c 60 | // 61 | // To 62 | // a 63 | // a/b 64 | // a/b/c 65 | private fun getParentsHierarchy(entry: BuildEntry) : List> { 66 | val normalizedPath = entry.contentFilepath!!.trim { it.toString().matches(delimiterRegex) } 67 | val pathParts = normalizedPath.split(delimiterRegex).toTypedArray() 68 | 69 | return mutableListOf>().apply { 70 | val aggregatedPath: StringBuilder = StringBuilder(normalizedPath.length) 71 | for (i in 0 until pathParts.count()) { 72 | val part = pathParts[i] 73 | if (i > 0) { 74 | aggregatedPath.append("/") 75 | } 76 | val newParent = aggregatedPath.append(part).toString() 77 | add(Pair(newParent, entry)) 78 | } 79 | } 80 | } 81 | 82 | // Will transform 83 | // a/b/c 84 | // 85 | // To 86 | // 87 | // a/b 88 | private fun getParentPath(path: String): String { 89 | return path.substringBeforeLast(delimiterRegex, "") 90 | } 91 | 92 | private fun buildNodeCache(pathsWithParents: List>): Map>> { 93 | val nodeCache = mutableMapOf>>() 94 | for ((parent, path, entry) in pathsWithParents) { 95 | if (!nodeCache.containsKey(parent)) { 96 | nodeCache[parent] = Pair(null, mutableListOf()) 97 | } 98 | 99 | nodeCache[path] = Pair(entry, mutableListOf()) 100 | nodeCache[parent]!!.second.add(path) 101 | } 102 | 103 | return nodeCache 104 | } 105 | 106 | private fun createNodeFrom(path: String, cache: Map>>): MgcbTreeNode { 107 | if (cache[path] == null) { 108 | // TODO 109 | throw Exception() 110 | } 111 | 112 | val cachedValue = cache[path]!! 113 | if (cachedValue.second.isEmpty()) { 114 | val name = path.substringAfterLast(delimiterRegex) 115 | val icon = IconLoader.findIcon(name, javaClass) 116 | return MgcbBuildEntryNode(path.substringAfterLast(delimiterRegex), icon, cachedValue.first!!) 117 | } 118 | 119 | val children = cachedValue.second.map { createNodeFrom(it, cache) } 120 | val folderNode = MgcbFolderNode(path.substringAfterLast(delimiterRegex)) 121 | for (child in children) { 122 | folderNode.add(child) 123 | } 124 | 125 | return folderNode 126 | } 127 | 128 | // Nodes to remove - exist in old but not in new 129 | // private fun getNodesToRemove(oldModel: MgcbModel, newModel: MgcbModel): List { 130 | // val result = mutableListOf() 131 | // 132 | // val oldCount = oldModel.buildEntries.count() 133 | // 134 | // var oldCounter = 0 135 | // var newCounter = 0 136 | // 137 | // while (oldCounter != oldCount) { 138 | // val oldNode = oldModel.buildEntries[oldCounter] 139 | // val newNode = newModel.buildEntries.getOrNull(newCounter) 140 | // 141 | // if (oldNode.contentFilepath == newNode?.contentFilepath) { 142 | // oldCounter++ 143 | // newCounter++ 144 | // } else { 145 | // // At that point newNode always != oldNode 146 | // result.add(oldNode) 147 | // oldCounter++ 148 | // } 149 | // } 150 | // 151 | // return result 152 | // } 153 | // 154 | // private fun getNodesToAdd(oldModel: MgcbModel, newModel: MgcbModel): List> { 155 | // val result = mutableListOf>() 156 | // 157 | // val newCount = newModel.buildEntries.count() 158 | // 159 | // var oldCounter = 0 160 | // var newCounter = 0 161 | // 162 | // var previousNewNode: BuildEntry? = null 163 | // 164 | // while (newCounter != newCount) { 165 | // val oldNode = oldModel.buildEntries.getOrNull(oldCounter) 166 | // val newNode = newModel.buildEntries[newCounter] 167 | // 168 | // if (oldNode?.contentFilepath == newNode.contentFilepath) { 169 | // oldCounter++ 170 | // newCounter++ 171 | // } else { 172 | // // At that point newNode always != oldNode 173 | // result.add(Pair(previousNewNode, newNode)) 174 | // newCounter++ 175 | // } 176 | // 177 | // previousNewNode = newNode 178 | // } 179 | // 180 | // return result 181 | // } 182 | 183 | companion object { 184 | private val delimiterRegex = Regex("[/\\\\]") 185 | } 186 | } --------------------------------------------------------------------------------