From 95a4f5a1eb5c7d2c7c72c592cf0498fcddc98e4a Mon Sep 17 00:00:00 2001 From: Hansjoerg Petriffer Date: Sat, 26 Sep 2026 17:37:57 +0200 Subject: [PATCH 01/60] [VsIntegration] CPS project system: new ProjectSystemCPS project and WP-1 skeleton First step of the MPFproj -> CPS migration (WP-1: project type registration & skeleton). - New SDK-style project src/VisualStudio/ProjectSystemCPS (net48, XSharp.ProjectSystemCPS.dll), references only XSharpCodeModelXs (never ProjectBase/ProjectPackage). Part of VSIntegration2022.sln and Master.slnx. - XSharpProjectSystemPackage registers a CPS project type for .xsproj with the reserved guidCpsProjectType {AB494DCE-...}, template language "XSharpCps" and the initial capability "XSharpCps". Only compiled with XSHARPCPS (Debug), so the Release VSIX does not register it and MPFproj stays the default project system (side by side via the project type GUID). - XSharpCpsGlobalPropertiesProvider sets XSharpCpsProjectSystem=true for projects loaded by the CPS project type. - X# MSBuild: XSharp.DesignTime.targets revived; it imports VS' Microsoft.Managed.DesignTime.targets (managed project system: Dependencies tree, PackageReference, launch profiles, property pages) and removes the LanguageService/ReferencesFolder capabilities (no Roslyn workspace for X#). It is only imported when XSharpCpsProjectSystem is true, so MPFproj and command line builds are unchanged. New Rules/ProjectItemsSchema.XSharp.xaml (.prg/.prgx/.xs -> Compile, headers -> None). - ProjectPackage2022 ships XSharp.ProjectSystemCPS.dll + pkgdef in its VSIX. - Test bed src/VisualStudio/ProjectSystemCPS.TestBed: SDK-style CpsTestBed.xsproj, CpsTestBed.sln with the CPS project type GUID, StartExp.cmd (starts the VS 2022 experimental instance with the repository X# MSBuild files, without changing the installed X#). Verified from the command line: normal builds unchanged; with the CPS property the managed capabilities and rules are present and the design-time build returns the compiler switches and resolved references. Acceptance test in the VS experimental instance still pending. Co-Authored-By: Claude Opus 5.5 --- src/Common/XSharpConstants.cs | 1 + .../Rules/ProjectItemsSchema.XSharp.xaml | 32 +++++++++++ .../XSharpBuildTask/XSharp.Build.csproj | 3 + .../XSharp.CrossTargeting.targets | 5 ++ .../XSharp.CurrentVersion.targets | 5 ++ .../XSharpBuildTask/XSharp.DesignTime.targets | 43 +++++++++----- src/Master.slnx | 1 + src/VSIntegration2022.sln | 15 +++++ .../ProjectPackage/ProjectPackage2022.csproj | 7 +++ .../source.extension.2022.vsixmanifest | 3 + .../ProjectSystemCPS.TestBed/CpsTestBed.sln | 21 +++++++ .../CpsTestBed.xsproj | 13 +++++ .../ProjectSystemCPS.TestBed/Program.prg | 5 ++ .../ProjectSystemCPS.TestBed/StartExp.cmd | 35 ++++++++++++ .../XSharpCpsGlobalPropertiesProvider.cs | 48 ++++++++++++++++ .../ProjectSystemCPS/ProjectSystemCPS.csproj | 57 +++++++++++++++++++ .../Properties/AssemblyInfo.cs | 8 +++ .../ProjectSystemCPS/XSharpCapabilities.cs | 27 +++++++++ .../XSharpProjectSystemPackage.cs | 53 +++++++++++++++++ 19 files changed, 368 insertions(+), 14 deletions(-) create mode 100644 src/Compiler/src/Compiler/XSharpBuildTask/Rules/ProjectItemsSchema.XSharp.xaml create mode 100644 src/VisualStudio/ProjectSystemCPS.TestBed/CpsTestBed.sln create mode 100644 src/VisualStudio/ProjectSystemCPS.TestBed/CpsTestBed.xsproj create mode 100644 src/VisualStudio/ProjectSystemCPS.TestBed/Program.prg create mode 100644 src/VisualStudio/ProjectSystemCPS.TestBed/StartExp.cmd create mode 100644 src/VisualStudio/ProjectSystemCPS/Evaluation/XSharpCpsGlobalPropertiesProvider.cs create mode 100644 src/VisualStudio/ProjectSystemCPS/ProjectSystemCPS.csproj create mode 100644 src/VisualStudio/ProjectSystemCPS/Properties/AssemblyInfo.cs create mode 100644 src/VisualStudio/ProjectSystemCPS/XSharpCapabilities.cs create mode 100644 src/VisualStudio/ProjectSystemCPS/XSharpProjectSystemPackage.cs diff --git a/src/Common/XSharpConstants.cs b/src/Common/XSharpConstants.cs index ed63d95e2d..8bf74ec7e7 100644 --- a/src/Common/XSharpConstants.cs +++ b/src/Common/XSharpConstants.cs @@ -22,6 +22,7 @@ internal static class XSharpConstants internal const string EditorFactoryGuidString = "B4829761-2BFA-44B7-8F8F-D2625EBCF218"; internal const string guidCpsProjectTypeString = "AB494DCE-A11B-4583-851E-D4A86775B3F0"; internal const string guidProjectSelectorString = "DF89AA3B-6AE6-4971-8F78-1A03B346A7D5"; + internal const string guidXSharpCpsProjectPkgString = "BAE3ADAE-83B3-44CC-BA79-A18F03F0D121"; internal static readonly Guid guidXSharpProjectFactory = new Guid(guidXSharpProjectFactoryString); internal static readonly Guid guidVOMenuEditorFactory = new Guid(guidXSharpVOMenuEditor); internal static readonly Guid guidVODbServerEditorFactory = new Guid(guidXSharpVODbServerEditor); diff --git a/src/Compiler/src/Compiler/XSharpBuildTask/Rules/ProjectItemsSchema.XSharp.xaml b/src/Compiler/src/Compiler/XSharpBuildTask/Rules/ProjectItemsSchema.XSharp.xaml new file mode 100644 index 0000000000..07b181d773 --- /dev/null +++ b/src/Compiler/src/Compiler/XSharpBuildTask/Rules/ProjectItemsSchema.XSharp.xaml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Compiler/src/Compiler/XSharpBuildTask/XSharp.Build.csproj b/src/Compiler/src/Compiler/XSharpBuildTask/XSharp.Build.csproj index 4f7230f281..e33d3937f1 100644 --- a/src/Compiler/src/Compiler/XSharpBuildTask/XSharp.Build.csproj +++ b/src/Compiler/src/Compiler/XSharpBuildTask/XSharp.Build.csproj @@ -73,6 +73,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest diff --git a/src/Compiler/src/Compiler/XSharpBuildTask/XSharp.CrossTargeting.targets b/src/Compiler/src/Compiler/XSharpBuildTask/XSharp.CrossTargeting.targets index 067451c607..f94c2e0cf4 100644 --- a/src/Compiler/src/Compiler/XSharpBuildTask/XSharp.CrossTargeting.targets +++ b/src/Compiler/src/Compiler/XSharpBuildTask/XSharp.CrossTargeting.targets @@ -14,6 +14,11 @@ Copyright (C) XSharp BV. All rights reserved. + + $(MSBuildThisFileDirectory)XSharp.DesignTime.targets + + + diff --git a/src/Compiler/src/Compiler/XSharpBuildTask/XSharp.CurrentVersion.targets b/src/Compiler/src/Compiler/XSharpBuildTask/XSharp.CurrentVersion.targets index 40a8b1d186..1c7d1fa67d 100644 --- a/src/Compiler/src/Compiler/XSharpBuildTask/XSharp.CurrentVersion.targets +++ b/src/Compiler/src/Compiler/XSharpBuildTask/XSharp.CurrentVersion.targets @@ -374,6 +374,11 @@ Copyright (c) XSharp B.V. All Rights Reserved + + $(MSBuildThisFileDirectory)XSharp.DesignTime.targets + + + diff --git a/src/Compiler/src/Compiler/XSharpBuildTask/XSharp.DesignTime.targets b/src/Compiler/src/Compiler/XSharpBuildTask/XSharp.DesignTime.targets index 2ed7abc2f1..24a17ad052 100644 --- a/src/Compiler/src/Compiler/XSharpBuildTask/XSharp.DesignTime.targets +++ b/src/Compiler/src/Compiler/XSharpBuildTask/XSharp.DesignTime.targets @@ -6,14 +6,18 @@ WARNING: DO NOT MODIFY this file unless you are knowledgeable about MSBuild and created a backup copy. Incorrect changes to this file will make it impossible to load or build your projects from the command-line or the IDE. - This file contains Visual Studio and designer-related properties and items for X# projects. - It is based on Microsoft.CSharp.DesignTime.targets - It gets install inside the \MSBuild\Microsoft\VisualStudio\Managed folder. - This folder is also known as "ManagedXamlResourcesDirectory" - We inherit the property pages from Microsoft.Managed.DesignTime.targets and extend some of them - with X# specific properties. Our pages are called .XSharp.Xaml. - The order attribute in the xaml files determines the order in which pages are shown in the UI. + This file contains Visual Studio and designer-related properties and items for X# projects + that are loaded by the CPS based X# project system (XSharp.ProjectSystemCPS). + It is based on Microsoft.CSharp.DesignTime.targets. + It is imported by XSharp.CurrentVersion.targets and XSharp.CrossTargeting.targets, but only when + the property XSharpCpsProjectSystem is 'true'. The CPS project type sets that property as a global + property, so projects loaded by the MPFproj project system and command line builds are not affected. + + It imports Microsoft.Managed.DesignTime.targets from the Visual Studio installation + (\MSBuild\Microsoft\VisualStudio\Managed). That file declares the managed capabilities + (.NET, DependenciesTree, PackageReferences, ...) and the property page rules of the managed project system. + X# specific rules live in the Rules subfolder of this folder. Copyright (c) XSharp B.V. All Rights Reserved @@ -24,19 +28,19 @@ inspired by Microsoft.CSharp.DesignTime.targets - ProjectDebugger - $(MSBuildThisFileDirectory)\Microsoft + $(MSBuildExtensionsPath)\Microsoft\VisualStudio\Managed\Microsoft.Managed.DesignTime.targets + $(MSBuildThisFileDirectory)Rules\ - - + Properties false X# - {694DD9B6-B865-4C5B-AD85-86356E9C88DC} + + {E9B6EE0F-9BFA-4CFF-A60B-51A08BBC5050} XSharp {FAE04EC0-301F-11d3-BF4B-00C04F79EFBC} {FAE04EC1-301F-11d3-BF4B-00C04F79EFBC} @@ -44,10 +48,21 @@ inspired by Microsoft.CSharp.DesignTime.targets Private Camel Declarations - - false + + + + + + diff --git a/src/Master.slnx b/src/Master.slnx index bc59130651..a29ffe6d10 100644 --- a/src/Master.slnx +++ b/src/Master.slnx @@ -289,6 +289,7 @@ + diff --git a/src/VSIntegration2022.sln b/src/VSIntegration2022.sln index bc753ca6fb..b80997753a 100644 --- a/src/VSIntegration2022.sln +++ b/src/VSIntegration2022.sln @@ -51,6 +51,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeDomProvider", "VisualSt EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Support2022", "VisualStudio\Support\Support2022.csproj", "{FB387BE5-A620-46EA-A7A6-1CCB9177F7C1}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ProjectSystemCPS", "VisualStudio\ProjectSystemCPS\ProjectSystemCPS.csproj", "{99708D46-58A9-46B6-B77F-003ADAF15FF6}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -217,6 +219,18 @@ Global {FB387BE5-A620-46EA-A7A6-1CCB9177F7C1}.Release|arm64.Build.0 = Release|Any CPU {FB387BE5-A620-46EA-A7A6-1CCB9177F7C1}.Release|x86.ActiveCfg = Release|Any CPU {FB387BE5-A620-46EA-A7A6-1CCB9177F7C1}.Release|x86.Build.0 = Release|Any CPU + {99708D46-58A9-46B6-B77F-003ADAF15FF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {99708D46-58A9-46B6-B77F-003ADAF15FF6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {99708D46-58A9-46B6-B77F-003ADAF15FF6}.Debug|arm64.ActiveCfg = Debug|Any CPU + {99708D46-58A9-46B6-B77F-003ADAF15FF6}.Debug|arm64.Build.0 = Debug|Any CPU + {99708D46-58A9-46B6-B77F-003ADAF15FF6}.Debug|x86.ActiveCfg = Debug|Any CPU + {99708D46-58A9-46B6-B77F-003ADAF15FF6}.Debug|x86.Build.0 = Debug|Any CPU + {99708D46-58A9-46B6-B77F-003ADAF15FF6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {99708D46-58A9-46B6-B77F-003ADAF15FF6}.Release|Any CPU.Build.0 = Release|Any CPU + {99708D46-58A9-46B6-B77F-003ADAF15FF6}.Release|arm64.ActiveCfg = Release|Any CPU + {99708D46-58A9-46B6-B77F-003ADAF15FF6}.Release|arm64.Build.0 = Release|Any CPU + {99708D46-58A9-46B6-B77F-003ADAF15FF6}.Release|x86.ActiveCfg = Release|Any CPU + {99708D46-58A9-46B6-B77F-003ADAF15FF6}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -234,6 +248,7 @@ Global {960714E8-FF93-4A42-B9BA-610B0F2014DB} = {0D3C69B5-D236-42B3-B70D-2C9ABC9D434C} {7B7D9574-66A7-4719-AA48-C2386D7BF153} = {9F285506-97D3-486E-B59E-7C30C56B95AE} {FB387BE5-A620-46EA-A7A6-1CCB9177F7C1} = {0D3C69B5-D236-42B3-B70D-2C9ABC9D434C} + {99708D46-58A9-46B6-B77F-003ADAF15FF6} = {0D3C69B5-D236-42B3-B70D-2C9ABC9D434C} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {6F61AAF2-9C04-4D27-887F-8A475F079D9C} diff --git a/src/VisualStudio/ProjectPackage/ProjectPackage2022.csproj b/src/VisualStudio/ProjectPackage/ProjectPackage2022.csproj index af5dc2a527..9a6057913c 100644 --- a/src/VisualStudio/ProjectPackage/ProjectPackage2022.csproj +++ b/src/VisualStudio/ProjectPackage/ProjectPackage2022.csproj @@ -743,6 +743,13 @@ BuiltProjectOutputGroup%3bBuiltProjectOutputGroupDependencies%3bGetCopyToOutputDirectoryItems%3bSatelliteDllsProjectOutputGroup%3b DebugSymbolsProjectOutputGroup%3b + + {99708d46-58a9-46b6-b77f-003adaf15ff6} + ProjectSystemCPS + + BuiltProjectOutputGroup%3bPkgdefProjectOutputGroup%3b + DebugSymbolsProjectOutputGroup%3b + {fb387be5-a620-46ea-a7a6-1ccb9177f7c1} Support2022 diff --git a/src/VisualStudio/ProjectPackage/source.extension.2022.vsixmanifest b/src/VisualStudio/ProjectPackage/source.extension.2022.vsixmanifest index 1563305183..accf2669c8 100644 --- a/src/VisualStudio/ProjectPackage/source.extension.2022.vsixmanifest +++ b/src/VisualStudio/ProjectPackage/source.extension.2022.vsixmanifest @@ -41,6 +41,9 @@ Includes a Project System, Language Service , Debugger Support, Custom Editors e + + + ProjectTemplates diff --git a/src/VisualStudio/ProjectSystemCPS.TestBed/CpsTestBed.sln b/src/VisualStudio/ProjectSystemCPS.TestBed/CpsTestBed.sln new file mode 100644 index 0000000000..c5e6e21443 --- /dev/null +++ b/src/VisualStudio/ProjectSystemCPS.TestBed/CpsTestBed.sln @@ -0,0 +1,21 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.14.37111.16 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{AB494DCE-A11B-4583-851E-D4A86775B3F0}") = "CpsTestBed", "CpsTestBed.xsproj", "{E89C628F-2E33-47E2-AA05-0BC29B9C2DD2}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {E89C628F-2E33-47E2-AA05-0BC29B9C2DD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E89C628F-2E33-47E2-AA05-0BC29B9C2DD2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E89C628F-2E33-47E2-AA05-0BC29B9C2DD2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E89C628F-2E33-47E2-AA05-0BC29B9C2DD2}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/src/VisualStudio/ProjectSystemCPS.TestBed/CpsTestBed.xsproj b/src/VisualStudio/ProjectSystemCPS.TestBed/CpsTestBed.xsproj new file mode 100644 index 0000000000..38a07adee6 --- /dev/null +++ b/src/VisualStudio/ProjectSystemCPS.TestBed/CpsTestBed.xsproj @@ -0,0 +1,13 @@ + + + + Exe + net48 + Core + CpsTestBed + + \ No newline at end of file diff --git a/src/VisualStudio/ProjectSystemCPS.TestBed/Program.prg b/src/VisualStudio/ProjectSystemCPS.TestBed/Program.prg new file mode 100644 index 0000000000..92f7015d12 --- /dev/null +++ b/src/VisualStudio/ProjectSystemCPS.TestBed/Program.prg @@ -0,0 +1,5 @@ +using System + +function Start() as void strict + Console.WriteLine("Hello from the X# CPS project system") + return diff --git a/src/VisualStudio/ProjectSystemCPS.TestBed/StartExp.cmd b/src/VisualStudio/ProjectSystemCPS.TestBed/StartExp.cmd new file mode 100644 index 0000000000..944c034570 --- /dev/null +++ b/src/VisualStudio/ProjectSystemCPS.TestBed/StartExp.cmd @@ -0,0 +1,35 @@ +@echo off +rem Starts the Visual Studio 2022 experimental instance for testing the CPS based X# project system (WP-1). +rem +rem The CPS project type needs the X# MSBuild files of this repository (XSharp.DesignTime.targets and the +rem XSharpCpsProjectSystem import in XSharp.CurrentVersion.targets). They are not installed yet, so this script +rem copies the installed X# MsBuild folder to Artifacts\CpsMsBuild, overlays the changed files from the +rem repository and starts devenv with XSharpMsBuildDir pointing to that folder. The installed X# is not changed. +rem +rem Prerequisite: a Debug build of VisualStudio\ProjectPackage\ProjectPackage2022.csproj, which deploys the +rem VSIX (including XSharp.ProjectSystemCPS) to the experimental instance. +setlocal +for %%i in ("%~dp0..\..\..") do set REPO=%%~fi +set BUILDTASK=%REPO%\src\Compiler\src\Compiler\XSharpBuildTask +set OVERLAY=%REPO%\Artifacts\CpsMsBuild + +if not defined XSharpMsBuildDir ( + echo XSharpMsBuildDir is not set. Is X# installed? + exit /b 1 +) + +xcopy /y /e /q /i "%XSharpMsBuildDir%" "%OVERLAY%" >nul +copy /y "%BUILDTASK%\XSharp.CurrentVersion.targets" "%OVERLAY%" >nul +copy /y "%BUILDTASK%\XSharp.CrossTargeting.targets" "%OVERLAY%" >nul +copy /y "%BUILDTASK%\XSharp.DesignTime.targets" "%OVERLAY%" >nul +xcopy /y /e /q /i "%BUILDTASK%\Rules" "%OVERLAY%\Rules" >nul + +for /f "usebackq delims=" %%i in (`"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" -version [17.0^,18.0^) -latest -property productPath`) do set DEVENV=%%i +if not defined DEVENV ( + echo Visual Studio 2022 not found. + exit /b 1 +) + +set XSharpMsBuildDir=%OVERLAY% +echo XSharpMsBuildDir=%XSharpMsBuildDir% +start "" "%DEVENV%" /rootSuffix Exp "%~dp0CpsTestBed.sln" diff --git a/src/VisualStudio/ProjectSystemCPS/Evaluation/XSharpCpsGlobalPropertiesProvider.cs b/src/VisualStudio/ProjectSystemCPS/Evaluation/XSharpCpsGlobalPropertiesProvider.cs new file mode 100644 index 0000000000..5bf4419f62 --- /dev/null +++ b/src/VisualStudio/ProjectSystemCPS/Evaluation/XSharpCpsGlobalPropertiesProvider.cs @@ -0,0 +1,48 @@ +// +// Copyright (c) XSharp B.V. All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +// + +using System; +using System.Collections.Immutable; +using System.ComponentModel.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.VisualStudio.ProjectSystem; +using Microsoft.VisualStudio.ProjectSystem.Build; + +namespace XSharp.ProjectSystem.Evaluation +{ + /// + /// Adds the global property XSharpCpsProjectSystem=true to the MSBuild evaluation of projects that are + /// loaded by the X# CPS project type. + /// + /// + /// The X# targets (XSharp.CurrentVersion.targets) only import XSharp.DesignTime.targets, and with it the + /// managed design-time targets of Visual Studio, when this property is set. Projects loaded by the MPFproj + /// project system and command line builds are therefore not affected. + /// The provider applies to the initial capability of the project type, so it is active for the first evaluation. + /// + [Export(typeof(IProjectGlobalPropertiesProvider))] + [AppliesTo(XSharpCapabilities.XSharpCps)] + internal sealed class XSharpCpsGlobalPropertiesProvider : StaticGlobalPropertiesProviderBase + { + internal const string PropertyName = "XSharpCpsProjectSystem"; + + private static readonly Task> properties = + Task.FromResult>( + ImmutableDictionary.Create(StringComparer.OrdinalIgnoreCase).Add(PropertyName, "true")); + + [ImportingConstructor] + public XSharpCpsGlobalPropertiesProvider(IProjectService projectService) + : base(projectService.Services) + { + } + + public override Task> GetGlobalPropertiesAsync(CancellationToken cancellationToken) + { + return properties; + } + } +} diff --git a/src/VisualStudio/ProjectSystemCPS/ProjectSystemCPS.csproj b/src/VisualStudio/ProjectSystemCPS/ProjectSystemCPS.csproj new file mode 100644 index 0000000000..d09ce84ebb --- /dev/null +++ b/src/VisualStudio/ProjectSystemCPS/ProjectSystemCPS.csproj @@ -0,0 +1,57 @@ + + + + + + net48 + XSharp.ProjectSystemCPS + XSharp.ProjectSystem + AnyCPU + + false + false + false + true + $(NoWarn);NU1903 + + + + true + true + false + false + + + + TRACE;DEBUG;XSHARP;DEV17;XSHARPCPS + full + + + TRACE;XSHARP;DEV17 + pdbonly + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + \ No newline at end of file diff --git a/src/VisualStudio/ProjectSystemCPS/Properties/AssemblyInfo.cs b/src/VisualStudio/ProjectSystemCPS/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..9369eab5d4 --- /dev/null +++ b/src/VisualStudio/ProjectSystemCPS/Properties/AssemblyInfo.cs @@ -0,0 +1,8 @@ +using System.Reflection; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. + +[assembly: AssemblyTitle("XSharp.ProjectSystemCPS")] +[assembly: AssemblyDescription("X# project system based on the Visual Studio Common Project System (CPS)")] diff --git a/src/VisualStudio/ProjectSystemCPS/XSharpCapabilities.cs b/src/VisualStudio/ProjectSystemCPS/XSharpCapabilities.cs new file mode 100644 index 0000000000..1bff657bab --- /dev/null +++ b/src/VisualStudio/ProjectSystemCPS/XSharpCapabilities.cs @@ -0,0 +1,27 @@ +// +// Copyright (c) XSharp B.V. All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +// + +namespace XSharp.ProjectSystem +{ + /// + /// Project capabilities used to scope the X# CPS MEF parts with [AppliesTo(...)]. + /// + internal static class XSharpCapabilities + { + /// + /// Capability that marks a project as an X# project. Gate for all X# specific MEF extensions. + /// Declared by the X# MSBuild targets, so it is also present for projects loaded by the MPFproj project system. + /// + public const string XSharp = "XSharp"; + + /// + /// Initial capability of the X# CPS project type (see ). + /// CPS passes it to the project before the first MSBuild evaluation, so it is available to + /// UnconfiguredProject scoped parts that influence that evaluation. Never declared in MSBuild. + /// + public const string XSharpCps = "XSharpCps"; + } +} diff --git a/src/VisualStudio/ProjectSystemCPS/XSharpProjectSystemPackage.cs b/src/VisualStudio/ProjectSystemCPS/XSharpProjectSystemPackage.cs new file mode 100644 index 0000000000..74d4f46f2d --- /dev/null +++ b/src/VisualStudio/ProjectSystemCPS/XSharpProjectSystemPackage.cs @@ -0,0 +1,53 @@ +// +// Copyright (c) XSharp B.V. All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +// + +using System; +using System.Runtime.InteropServices; +using System.Threading; +using Microsoft.VisualStudio.Shell; +#if XSHARPCPS +using Microsoft.VisualStudio.ProjectSystem.VS; +#endif +using Task = System.Threading.Tasks.Task; + +namespace XSharp.ProjectSystem +{ + /// + /// Package for the CPS based X# project system. + /// + /// + /// The CPS project type is only registered when XSHARPCPS is defined (Debug builds). The MPFproj project + /// system registers the same .xsproj extension, so during the side-by-side phase the CPS project type uses + /// its own project type GUID and its own template language: a solution selects it explicitly through the + /// project type GUID, and the existing X# project templates keep creating MPFproj projects. + /// + [PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)] + [Guid(XSharpConstants.guidXSharpCpsProjectPkgString)] +#if XSHARPCPS + [ProjectTypeRegistration( + projectTypeGuid: XSharpConstants.guidCpsProjectTypeString, + displayName: "XSharp (CPS)", + displayProjectFileExtensions: "XSharp CPS Project Files (*." + XSharpConstants.ProjectExtension + ");*." + XSharpConstants.ProjectExtension, + defaultProjectExtension: XSharpConstants.ProjectExtension, + language: LanguageVsTemplate, + resourcePackageGuid: XSharpConstants.guidXSharpCpsProjectPkgString, + Capabilities = XSharpCapabilities.XSharpCps, + PossibleProjectExtensions = XSharpConstants.ProjectExtension)] +#endif + public sealed class XSharpProjectSystemPackage : AsyncPackage + { + /// + /// Template language of the CPS project type. Deliberately different from the MPFproj "XSharp" language, + /// so that X# project templates are not matched to both project types. + /// + internal const string LanguageVsTemplate = "XSharpCps"; + + protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress progress) + { + await base.InitializeAsync(cancellationToken, progress); + } + } +} From 6a502f99f37a308ace3a8ef2c8cf036fd5234010 Mon Sep 17 00:00:00 2001 From: Hansjoerg Petriffer Date: Sat, 26 Sep 2026 17:48:14 +0200 Subject: [PATCH 02/60] [VsIntegration] CPS project system WP-5: XSharpProjectAdapter feeds the code model XSharpProjectAdapter (IProjectDynamicLoadComponent + IXSharpProject, one per UnconfiguredProject, active configuration) creates the unchanged XProject and feeds it from CPS instead of MPFproj hierarchy walking and .rsp parsing: - source items (evaluation) -> XProject.AddFile/RemoveFile, ModelWalker.AddProject - XSharpProjectProperties + EvaluatedProjectReference (evaluation) -> RootNamespace, output paths, NS, provisional dialect, X# project references (AddProjectReference/RemoveProjectReference) - XSharpCompilerCommandLineArgs (design-time build, CompileDesignTime without running xsc) -> XParseOptions.FromVsValues/ResetParseOptions and RefreshReferences/ResolveReferences, so parse options and references are correct before the first real build. XSharpCommandLine maps the Xsc switches to the FromVsValues format (as XSharpProjectOptions does for MPFproj). IntellisenseErrorStore keeps the code model errors per file (no Error List yet). If the X# solution model is not open yet, the adapter loads the X# project package by GUID. New rules Rules/XSharpCompilerCommandLineArgs.xaml and Rules/XSharpProjectProperties.xaml (Context ProjectSubscriptionService), registered in XSharp.DesignTime.targets. Verified from the command line with the design-time output of a VO dialect project (dialect, /ns, defines, include path, references). Acceptance test in VS pending. Co-Authored-By: Claude Opus 5.5 --- .../Rules/XSharpCompilerCommandLineArgs.xaml | 24 ++ .../Rules/XSharpProjectProperties.xaml | 19 + .../XSharpBuildTask/XSharp.Build.csproj | 6 + .../XSharpBuildTask/XSharp.DesignTime.targets | 4 + .../LanguageService/IntellisenseErrorStore.cs | 84 ++++ .../LanguageService/XSharpCommandLine.cs | 133 +++++++ .../LanguageService/XSharpProjectAdapter.cs | 367 ++++++++++++++++++ .../ProjectSystemCPS/ProjectSystemCPS.csproj | 7 + 8 files changed, 644 insertions(+) create mode 100644 src/Compiler/src/Compiler/XSharpBuildTask/Rules/XSharpCompilerCommandLineArgs.xaml create mode 100644 src/Compiler/src/Compiler/XSharpBuildTask/Rules/XSharpProjectProperties.xaml create mode 100644 src/VisualStudio/ProjectSystemCPS/LanguageService/IntellisenseErrorStore.cs create mode 100644 src/VisualStudio/ProjectSystemCPS/LanguageService/XSharpCommandLine.cs create mode 100644 src/VisualStudio/ProjectSystemCPS/LanguageService/XSharpProjectAdapter.cs diff --git a/src/Compiler/src/Compiler/XSharpBuildTask/Rules/XSharpCompilerCommandLineArgs.xaml b/src/Compiler/src/Compiler/XSharpBuildTask/Rules/XSharpCompilerCommandLineArgs.xaml new file mode 100644 index 0000000000..da8ca1a8fd --- /dev/null +++ b/src/Compiler/src/Compiler/XSharpBuildTask/Rules/XSharpCompilerCommandLineArgs.xaml @@ -0,0 +1,24 @@ + + + + + + + + \ No newline at end of file diff --git a/src/Compiler/src/Compiler/XSharpBuildTask/Rules/XSharpProjectProperties.xaml b/src/Compiler/src/Compiler/XSharpBuildTask/Rules/XSharpProjectProperties.xaml new file mode 100644 index 0000000000..c598ad3125 --- /dev/null +++ b/src/Compiler/src/Compiler/XSharpBuildTask/Rules/XSharpProjectProperties.xaml @@ -0,0 +1,19 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Compiler/src/Compiler/XSharpBuildTask/XSharp.Build.csproj b/src/Compiler/src/Compiler/XSharpBuildTask/XSharp.Build.csproj index e33d3937f1..135f9f0741 100644 --- a/src/Compiler/src/Compiler/XSharpBuildTask/XSharp.Build.csproj +++ b/src/Compiler/src/Compiler/XSharpBuildTask/XSharp.Build.csproj @@ -76,6 +76,12 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + PreserveNewest diff --git a/src/Compiler/src/Compiler/XSharpBuildTask/XSharp.DesignTime.targets b/src/Compiler/src/Compiler/XSharpBuildTask/XSharp.DesignTime.targets index 24a17ad052..1c88c6541e 100644 --- a/src/Compiler/src/Compiler/XSharpBuildTask/XSharp.DesignTime.targets +++ b/src/Compiler/src/Compiler/XSharpBuildTask/XSharp.DesignTime.targets @@ -52,6 +52,10 @@ inspired by Microsoft.CSharp.DesignTime.targets + + + ProjectSubscriptionService + , + /// the assembly references for and the source files. + /// + /// + /// XParseOptions.FromVsValues expects the switches without the leading '/' and the defines as "d:", + /// the same format that XSharpProjectOptions.BuildCommandLine (MPFproj) produces. + /// Switches that are not X# parse options are ignored by FromVsValues. + /// + internal sealed class XSharpCommandLine + { + public IList ParseOptions { get; } + public IList References { get; } + public IList SourceFiles { get; } + + private XSharpCommandLine(IList parseOptions, IList references, IList sourceFiles) + { + ParseOptions = parseOptions; + References = references; + SourceFiles = sourceFiles; + } + + public static XSharpCommandLine Parse(IEnumerable arguments, string projectFolder) + { + var options = new List(); + var references = new List(); + var sources = new List(); + var includes = new List(); + foreach (var argument in arguments) + { + if (string.IsNullOrWhiteSpace(argument)) + continue; + var arg = argument.Trim(); + if (arg[0] != '/' && arg[0] != '-') + { + sources.Add(MakeFullPath(Unquote(arg), projectFolder)); + continue; + } + arg = arg.Substring(1); + string name, value; + var pos = arg.IndexOf(':'); + if (pos > 0) + { + name = arg.Substring(0, pos); + value = Unquote(arg.Substring(pos + 1)); + } + else + { + name = arg; + value = ""; + } + switch (name.ToLowerInvariant()) + { + case "reference": + case "r": + var reference = StripAlias(value); + if (reference.Length > 0) + references.Add(MakeFullPath(reference, projectFolder)); + break; + case "define": + case "d": + if (value.Length > 0) + options.Add("d:" + value); + break; + case "i": + if (value.Length > 0) + includes.Add(value); + break; + case "analyzer": + case "analyzerconfig": + case "additionalfile": + // paths for the Roslyn analyzers, not relevant for the code model + break; + default: + options.Add(arg); + break; + } + } + // Like MPFproj: the default include folder is always part of the include path + includes.Add(XParseOptions.DefaultIncludeDir); + options.Add("i:" + string.Join(";", includes.Where(i => !string.IsNullOrEmpty(i)))); + return new XSharpCommandLine(options, references, sources); + } + + private static string Unquote(string value) + { + value = value.Trim(); + if (value.Length >= 2 && value[0] == '"' && value[value.Length - 1] == '"') + value = value.Substring(1, value.Length - 2); + return value; + } + + /// + /// /reference:alias=path.dll -> path.dll + /// + private static string StripAlias(string value) + { + var pos = value.IndexOf('='); + if (pos > 0 && value.IndexOfAny(new[] { '\\', '/', ':' }) > pos) + value = value.Substring(pos + 1); + return Unquote(value); + } + + internal static string MakeFullPath(string path, string projectFolder) + { + try + { + if (!Path.IsPathRooted(path)) + path = Path.Combine(projectFolder, path); + return Path.GetFullPath(path); + } + catch (Exception) + { + return path; + } + } + } +} diff --git a/src/VisualStudio/ProjectSystemCPS/LanguageService/XSharpProjectAdapter.cs b/src/VisualStudio/ProjectSystemCPS/LanguageService/XSharpProjectAdapter.cs new file mode 100644 index 0000000000..68861c2cc7 --- /dev/null +++ b/src/VisualStudio/ProjectSystemCPS/LanguageService/XSharpProjectAdapter.cs @@ -0,0 +1,367 @@ +// +// Copyright (c) XSharp B.V. All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +// + +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using System.Threading.Tasks.Dataflow; +using Microsoft.VisualStudio.ProjectSystem; +using Microsoft.VisualStudio.ProjectSystem.Properties; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudio.Threading; +using XSharp.Settings; +using XSharpModel; + +namespace XSharp.ProjectSystem.LanguageService +{ + /// + /// Feeds the X# code model () of a project that is loaded by the CPS project system. + /// It replaces the data feeding that XSharpProjectNode (MPFproj) performs: hierarchy walking and the parsing + /// of the response file after a real build (spec WP-5, Part B). + /// + /// + /// One instance per UnconfiguredProject. The data comes from the active configuration + /// (IActiveConfiguredProjectSubscriptionService), so there is one XProject per project, as with MPFproj. + /// Three subscriptions: + /// + /// source items (evaluation) -> XProject.AddFile / RemoveFile + /// XSharpProjectProperties + EvaluatedProjectReference (evaluation) -> RootNamespace, output paths, + /// provisional parse options (dialect) and X# project references + /// XSharpCompilerCommandLineArgs (design-time build) -> XParseOptions and assembly references + /// + /// Because the design-time build does not run the compiler, parse options and references are available + /// before the first real build. + /// + [Export(ExportContractNames.Scopes.UnconfiguredProject, typeof(IProjectDynamicLoadComponent))] + [AppliesTo(XSharpCapabilities.XSharpCps)] + internal sealed class XSharpProjectAdapter : IProjectDynamicLoadComponent, IXSharpProject + { + private const string ProjectPropertiesRule = "XSharpProjectProperties"; + private const string ProjectReferenceRule = "EvaluatedProjectReference"; + private const string CommandLineRule = "XSharpCompilerCommandLineArgs"; + + /// + /// Item types whose items are registered with the code model. XProject.AddFile determines the file type + /// from the extension, non source files end up in the "other files" of the project, as with MPFproj. + /// + private static readonly HashSet FileItemTypes = new HashSet(StringComparer.OrdinalIgnoreCase) + { + "Compile", "None", "Content", "EmbeddedResource", "Page", "ApplicationDefinition", "Resource", "VOBinary", "NativeResource", + }; + + private readonly UnconfiguredProject project; + private readonly IActiveConfiguredProjectSubscriptionService subscriptions; + private readonly IProjectThreadingService threading; + private readonly IntellisenseErrorStore errors = new IntellisenseErrorStore(); + private readonly HashSet files = new HashSet(StringComparer.OrdinalIgnoreCase); + private readonly HashSet projectReferences = new HashSet(StringComparer.OrdinalIgnoreCase); + private readonly object gate = new object(); + + private XProject model; + private List links; + private XParseOptions parseOptions = XParseOptions.Default; + private bool parseOptionsFromCommandLine; + private string rootNamespace = ""; + private string intermediateOutputPath = ""; + private string outputFile = ""; + private bool prefixClassesWithDefaultNamespace; + + [ImportingConstructor] + public XSharpProjectAdapter( + UnconfiguredProject project, + IActiveConfiguredProjectSubscriptionService subscriptions, + IProjectThreadingService threading) + { + this.project = project; + this.subscriptions = subscriptions; + this.threading = threading; + } + + internal XProject Model => model; + + private string ProjectFolder => Path.GetDirectoryName(project.FullPath); + + #region IProjectDynamicLoadComponent + + public async Task LoadAsync() + { + if (!await EnsureSolutionIsOpenAsync()) + { + XSettings.Information("XSharpProjectAdapter: X# solution model is not open, no code model for " + project.FullPath); + return; + } + lock (gate) + { + if (model != null) + return; + model = new XProject(this); + } + XSettings.Information("XSharpProjectAdapter: created code model for " + project.FullPath); + + var linkOptions = new DataflowLinkOptions { PropagateCompletion = true }; + links = new List + { + subscriptions.SourceItemsRuleSource.SourceBlock.LinkTo( + DataflowBlockSlim.CreateActionBlock>(OnSourceItemsChanged), + linkOptions), + subscriptions.ProjectRuleSource.SourceBlock.LinkTo( + DataflowBlockSlim.CreateActionBlock>(OnEvaluationChanged), + linkOptions, + ruleNames: new[] { ProjectPropertiesRule, ProjectReferenceRule }), + subscriptions.ProjectBuildRuleSource.SourceBlock.LinkTo( + DataflowBlockSlim.CreateActionBlock>(OnDesignTimeBuildChanged), + linkOptions, + ruleNames: new[] { CommandLineRule }), + }; + } + + public Task UnloadAsync() + { + XProject oldModel; + lock (gate) + { + if (links != null) + { + foreach (var link in links) + link.Dispose(); + links = null; + } + oldModel = model; + model = null; + files.Clear(); + projectReferences.Clear(); + } + oldModel?.Close(); + return Task.CompletedTask; + } + + /// + /// The X# solution model (database) is opened by the X# packages when a solution opens. When a CPS + /// project loads before these packages have been initialized, load the X# project package and wait. + /// + private async Task EnsureSolutionIsOpenAsync() + { + if (XSolution.IsOpen) + return true; + await threading.JoinableTaskFactory.SwitchToMainThreadAsync(); + if (ServiceProvider.GlobalProvider.GetService(typeof(SVsShell)) is IVsShell shell) + { + var packageGuid = new Guid(XSharpConstants.guidXSharpProjectPkgString); + shell.LoadPackage(ref packageGuid, out _); + } + await TaskScheduler.Default; + for (int i = 0; i < 100 && !XSolution.IsOpen; i++) + { + await Task.Delay(100); + } + return XSolution.IsOpen; + } + + #endregion + + #region Subscriptions + + private void OnSourceItemsChanged(IProjectVersionedValue update) + { + try + { + var added = new List(); + var removed = new List(); + lock (gate) + { + if (model == null) + return; + foreach (var change in update.Value.ProjectChanges) + { + if (!FileItemTypes.Contains(change.Key) || !change.Value.Difference.AnyChanges) + continue; + var diff = change.Value.Difference; + foreach (var item in diff.RemovedItems) + removed.Add(XSharpCommandLine.MakeFullPath(item, ProjectFolder)); + foreach (var item in diff.AddedItems) + added.Add(XSharpCommandLine.MakeFullPath(item, ProjectFolder)); + foreach (var rename in diff.RenamedItems) + { + removed.Add(XSharpCommandLine.MakeFullPath(rename.Key, ProjectFolder)); + added.Add(XSharpCommandLine.MakeFullPath(rename.Value, ProjectFolder)); + } + } + foreach (var file in removed) + { + if (files.Remove(file)) + model.RemoveFile(file); + } + foreach (var file in added) + { + if (files.Add(file)) + model.AddFile(file); + } + } + if (added.Count > 0 || removed.Count > 0) + { + XSettings.Information($"XSharpProjectAdapter: {project.FullPath}: {added.Count} files added, {removed.Count} files removed"); + WalkProject(); + } + } + catch (Exception e) + { + XSettings.Exception(e); + } + } + + private void OnEvaluationChanged(IProjectVersionedValue update) + { + try + { + var changes = update.Value.ProjectChanges; + lock (gate) + { + if (model == null) + return; + if (changes.TryGetValue(ProjectPropertiesRule, out var properties) && properties.Difference.AnyChanges) + { + ReadProjectProperties(properties.After); + } + if (changes.TryGetValue(ProjectReferenceRule, out var references) && references.Difference.AnyChanges) + { + UpdateProjectReferences(references.After); + } + } + } + catch (Exception e) + { + XSettings.Exception(e); + } + } + + private void OnDesignTimeBuildChanged(IProjectVersionedValue update) + { + try + { + if (!update.Value.ProjectChanges.TryGetValue(CommandLineRule, out var change) || !change.Difference.AnyChanges) + return; + var arguments = change.After.Items.Keys.ToList(); + if (arguments.Count == 0) + { + // A failed design-time build returns no arguments. Keep the previous options and references. + return; + } + var commandLine = XSharpCommandLine.Parse(arguments, ProjectFolder); + var options = XParseOptions.FromVsValues(commandLine.ParseOptions); + lock (gate) + { + if (model == null) + return; + parseOptions = options; + parseOptionsFromCommandLine = true; + model.ResetParseOptions(options); + model.RefreshReferences(commandLine.References); + foreach (var reference in projectReferences) + model.AddProjectReference(reference); + model.ResolveReferences(); + } + XSettings.Information($"XSharpProjectAdapter: {project.FullPath}: design-time build, {commandLine.ParseOptions.Count} options, {commandLine.References.Count} references, dialect {options.Dialect}"); + WalkProject(); + } + catch (Exception e) + { + XSettings.Exception(e); + } + } + + private void ReadProjectProperties(IProjectRuleSnapshot snapshot) + { + rootNamespace = GetProperty(snapshot, "RootNamespace"); + intermediateOutputPath = GetProperty(snapshot, "IntermediateOutputPath"); + if (intermediateOutputPath.Length > 0) + intermediateOutputPath = XSharpCommandLine.MakeFullPath(intermediateOutputPath, ProjectFolder); + outputFile = GetProperty(snapshot, "TargetPath"); + prefixClassesWithDefaultNamespace = string.Equals(GetProperty(snapshot, "NS"), "true", StringComparison.OrdinalIgnoreCase); + if (!parseOptionsFromCommandLine) + { + // Provisional options until the first design-time build has returned the command line + var dialect = GetProperty(snapshot, "Dialect"); + var options = new List { "dialect:" + (dialect.Length > 0 ? dialect : "Core"), "i:" + XParseOptions.DefaultIncludeDir }; + parseOptions = XParseOptions.FromVsValues(options); + model.ResetParseOptions(parseOptions); + } + } + + private void UpdateProjectReferences(IProjectRuleSnapshot snapshot) + { + // X# -> X# project references: the code model reads the types from the referenced XProject (source). + // References to other projects arrive as /reference: to their output assembly in the command line. + var current = new HashSet( + snapshot.Items.Keys + .Select(item => XSharpCommandLine.MakeFullPath(item, ProjectFolder)) + .Where(XProject.IsXSharpProject), + StringComparer.OrdinalIgnoreCase); + foreach (var removed in projectReferences.Where(r => !current.Contains(r)).ToList()) + { + projectReferences.Remove(removed); + model.RemoveProjectReference(removed); + } + foreach (var added in current.Where(r => !projectReferences.Contains(r)).ToList()) + { + projectReferences.Add(added); + model.AddProjectReference(added); + } + } + + private static string GetProperty(IProjectRuleSnapshot snapshot, string name) + { + return snapshot.Properties.TryGetValue(name, out var value) && value != null ? value : ""; + } + + private void WalkProject() + { + var current = model; + if (current != null) + ModelWalker.AddProject(current); + } + + #endregion + + #region IXSharpProject + + public string DisplayName => Path.GetFileNameWithoutExtension(project.FullPath); + public string IntermediateOutputPath => intermediateOutputPath; + public string OutputFile => outputFile; + public XParseOptions ParseOptions => parseOptions; + public bool PrefixClassesWithDefaultNamespace => prefixClassesWithDefaultNamespace; + public string RootNameSpace => rootNamespace; + public string Url => project.FullPath; + + public bool HasFileNode(string fileName) + { + lock (gate) + { + return files.Contains(fileName); + } + } + + public void AddFileNode(string fileName) + { + // Called by the code model for files that it wants to see in the project (e.g. generated files). + // Under CPS the project file is the source of truth; adding items is done through CPS (WP-6). + } + + public void DeleteFileNode(string fileName) + { + // See AddFileNode + } + + public void ClearIntellisenseErrors(string fileName) => errors.Clear(fileName); + public List GetIntellisenseErrors(string filename) => errors.Get(filename); + public void AddIntellisenseError(XError error) => errors.Add(error); + + #endregion + } +} diff --git a/src/VisualStudio/ProjectSystemCPS/ProjectSystemCPS.csproj b/src/VisualStudio/ProjectSystemCPS/ProjectSystemCPS.csproj index d09ce84ebb..50174668ab 100644 --- a/src/VisualStudio/ProjectSystemCPS/ProjectSystemCPS.csproj +++ b/src/VisualStudio/ProjectSystemCPS/ProjectSystemCPS.csproj @@ -48,6 +48,13 @@ all + + + + $(VsExternalDir)XSharp.Vsparser.dll + false + + From 2a023a7a91c4465c79813cb3814bcb3e8bdfb443 Mon Sep 17 00:00:00 2001 From: Hansjoerg Petriffer Date: Sat, 26 Sep 2026 17:53:12 +0200 Subject: [PATCH 03/60] [VsIntegration] CPS project system WP-2: project capabilities and X# icons - Initial capabilities of the CPS project type (ProjectTypeRegistration.Capabilities), modelled on the managed C# project type: XSharpCps; AppDesigner; HandlesOwnReload; OpenProjectFile; PreserveFormatting; ProjectConfigurationsDeclaredDimensions; .NET; UseProjectEvaluationCache (without LanguageService, CSharp, SharedImports and EditAndContinue). - XSharp.DesignTime.targets adds ProjectPropertiesEditor (new property editor for the XAML property pages of WP-4) to the managed capability set. - The managed project system only provides project icons for C#/VB/F# (internal IProjectImageProvider): XSharpProjectTreePropertiesProvider sets the X# project icon and the X# document icon for .prg/.prgx/.xs files via IProjectTreePropertiesProvider. New XSharp.ProjectSystemCPS.imagemanifest (images as WPF resources in the assembly), shipped as ImageManifest asset in the ProjectPackage2022 VSIX. Co-Authored-By: Claude Opus 5.5 --- .../XSharpBuildTask/XSharp.DesignTime.targets | 3 +- .../ProjectPackage/ProjectPackage2022.csproj | 4 ++ .../source.extension.2022.vsixmanifest | 1 + .../ProjectSystemCPS/Imaging/XSharpImages.cs | 22 ++++++++ .../XSharpProjectTreePropertiesProvider.cs | 48 ++++++++++++++++++ .../ProjectSystemCPS/ProjectSystemCPS.csproj | 8 +++ .../Resources/XSharpDocument.png | Bin 0 -> 1597 bytes .../Resources/XSharpProject.png | Bin 0 -> 1082 bytes .../XSharp.ProjectSystemCPS.imagemanifest | 19 +++++++ .../ProjectSystemCPS/XSharpCapabilities.cs | 12 +++++ .../XSharpProjectSystemPackage.cs | 2 +- 11 files changed, 117 insertions(+), 2 deletions(-) create mode 100644 src/VisualStudio/ProjectSystemCPS/Imaging/XSharpImages.cs create mode 100644 src/VisualStudio/ProjectSystemCPS/Imaging/XSharpProjectTreePropertiesProvider.cs create mode 100644 src/VisualStudio/ProjectSystemCPS/Resources/XSharpDocument.png create mode 100644 src/VisualStudio/ProjectSystemCPS/Resources/XSharpProject.png create mode 100644 src/VisualStudio/ProjectSystemCPS/XSharp.ProjectSystemCPS.imagemanifest diff --git a/src/Compiler/src/Compiler/XSharpBuildTask/XSharp.DesignTime.targets b/src/Compiler/src/Compiler/XSharpBuildTask/XSharp.DesignTime.targets index 1c88c6541e..78bb77a464 100644 --- a/src/Compiler/src/Compiler/XSharpBuildTask/XSharp.DesignTime.targets +++ b/src/Compiler/src/Compiler/XSharpBuildTask/XSharp.DesignTime.targets @@ -59,7 +59,8 @@ inspired by Microsoft.CSharp.DesignTime.targets + SingleTargetBuildForStartupProjects; + ProjectPropertiesEditor;" /> + ProjectTemplates diff --git a/src/VisualStudio/ProjectSystemCPS/Imaging/XSharpImages.cs b/src/VisualStudio/ProjectSystemCPS/Imaging/XSharpImages.cs new file mode 100644 index 0000000000..231cca5642 --- /dev/null +++ b/src/VisualStudio/ProjectSystemCPS/Imaging/XSharpImages.cs @@ -0,0 +1,22 @@ +// +// Copyright (c) XSharp B.V. All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +// + +using System; +using Microsoft.VisualStudio.ProjectSystem; + +namespace XSharp.ProjectSystem.Imaging +{ + /// + /// Image monikers of XSharp.ProjectSystemCPS.imagemanifest. + /// + internal static class XSharpImages + { + public static readonly Guid ImageCatalogGuid = new Guid("D9769BF1-DF1F-48B6-9642-949FB27BF46D"); + + public static readonly ProjectImageMoniker Project = new ProjectImageMoniker(ImageCatalogGuid, 1); + public static readonly ProjectImageMoniker Document = new ProjectImageMoniker(ImageCatalogGuid, 2); + } +} diff --git a/src/VisualStudio/ProjectSystemCPS/Imaging/XSharpProjectTreePropertiesProvider.cs b/src/VisualStudio/ProjectSystemCPS/Imaging/XSharpProjectTreePropertiesProvider.cs new file mode 100644 index 0000000000..ca88606664 --- /dev/null +++ b/src/VisualStudio/ProjectSystemCPS/Imaging/XSharpProjectTreePropertiesProvider.cs @@ -0,0 +1,48 @@ +// +// Copyright (c) XSharp B.V. All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +// + +using System; +using System.ComponentModel.Composition; +using System.IO; +using Microsoft.VisualStudio.ProjectSystem; + +namespace XSharp.ProjectSystem.Imaging +{ + /// + /// Sets the X# icons in Solution Explorer: the project node and the X# source files. + /// + /// + /// The managed project system provides the project icon through IProjectImageProvider, which is internal + /// to it and only implemented for C#, VB and F#. IProjectTreePropertiesProvider is the public CPS way. + /// + [Export(typeof(IProjectTreePropertiesProvider))] + [AppliesTo(XSharpCapabilities.XSharpCps)] + [Order(1000)] + internal sealed class XSharpProjectTreePropertiesProvider : IProjectTreePropertiesProvider + { + public void CalculatePropertyValues(IProjectTreeCustomizablePropertyContext propertyContext, IProjectTreeCustomizablePropertyValues propertyValues) + { + if (propertyValues.Flags.Contains(ProjectTreeFlags.ProjectRoot)) + { + propertyValues.Icon = XSharpImages.Project; + } + else if (!propertyValues.Flags.Contains(ProjectTreeFlags.Folder) && IsXSharpSourceFile(propertyContext.ItemName)) + { + propertyValues.Icon = XSharpImages.Document; + } + } + + private static bool IsXSharpSourceFile(string itemName) + { + if (string.IsNullOrEmpty(itemName)) + return false; + var extension = Path.GetExtension(itemName); + return string.Equals(extension, ".prg", StringComparison.OrdinalIgnoreCase) + || string.Equals(extension, ".prgx", StringComparison.OrdinalIgnoreCase) + || string.Equals(extension, ".xs", StringComparison.OrdinalIgnoreCase); + } + } +} diff --git a/src/VisualStudio/ProjectSystemCPS/ProjectSystemCPS.csproj b/src/VisualStudio/ProjectSystemCPS/ProjectSystemCPS.csproj index 50174668ab..c58a3c4bef 100644 --- a/src/VisualStudio/ProjectSystemCPS/ProjectSystemCPS.csproj +++ b/src/VisualStudio/ProjectSystemCPS/ProjectSystemCPS.csproj @@ -16,6 +16,8 @@ false false false + + true true $(NoWarn);NU1903 @@ -40,6 +42,12 @@ + + + + + + diff --git a/src/VisualStudio/ProjectSystemCPS/Resources/XSharpDocument.png b/src/VisualStudio/ProjectSystemCPS/Resources/XSharpDocument.png new file mode 100644 index 0000000000000000000000000000000000000000..90808762f31481976b5701ab0c5e981814c1ada4 GIT binary patch literal 1597 zcmV-D2EzG?P)N2bZe?^J zG%heMHvEiZ-v9swyGcYrRA@u(ntM=GRUF5^cVED=un)jh7r_SurJ^N_4%Xx!#nb|$ z?bIYWBWfdmRQ58b$q`DkGBw3?iV}MWGiWeM6r%aaa?}!SW&A@K9}y%ZDK5M08+NyQ z&MrF3pzghQ$Hf^wGk5Oq{LVR-^F4ds2gb+Z@p#bJ*AKwQK9M6jIyzv54&!#aeKwmN z48sImkwdAB4_KkYn9XMPHvmqaJc0W9dRVPiknmuLzI%xz@#JG(Z}0~~$xBcwmB`P} zhhDFzVXxN9Styr<*ZJI#{0|NLGA0H2k#R7xDfTE&Bh>KGM2_WAd zToF`2U0oe2D|b>yTwENIlaoWLvB3xxK>C2uXhc?47D`G=Ll;m}Q-fW*s%heiiXG5s zG=XUDkalb~TLK|&tX=!TT?Gt9P;6|h;#T2FLHImE^KvCf0FPD^aqj;6JOGRch%_St zA`K@2Hk%FXyg|o#^0wQZffzpyha)JSq<1=9G;Fc-QoFa;8nA=&@`aOt{(cYEZaRqN zrF*e@;{jHHgI&e;(KzyUx?HGe*@O+vE3x}>1x-&1sxK_ZmamS&#m2ASdI*N1?QpyO z`Gmqr0GVtanK~9Hnp$z-$az%OG|^foYba8?M>`tvEBiFxIE4&#Ce34a^x>w-4tb1> zhC92=kVQ$U9S%76fraxQ!;ER;sQumUQ~0~R+utmlJ*c_-HT7pD&Blzx9CThYIz=Kb=9;S<|J%8;)Z$DeRq9_!l zze>~XX=ui$UmRgAANC(UkJ5Gf&~~*O|8(5M>J9s78lmtKkf4%b$&0h7Z9I1s<$D@% zsN+W(o;P6ul48bC`?aNWP_q0P)?%=6jRB3jUxrj7!94wRG*rJr(+Gu^0G)0u7QQeO z6H+u_L_X9t{S1#w1Z|8C1*!7`sr+=CZ1kO=mcwRu!QJP9hPB1w@GQJ|2_PgUs}hr7we2P%w%*8EK9qfV6stb^iP}ewwP17kFEou%cncs% z#$1OY)884b7j$s8YpFfJ#MA^t$t1MDOrM&Jq(lYvXQXM7J}HUXLg6i-+jJA(wtNP~ zq#Nw!RSZX45;m|M+UdGIzLRD)TB~H6i~@@bXCWth0_$b)T*0GwHgASMkwAD0Xl$!R zr@b8_c1WN9xE?;Q2p#OOxufd$Kng#m9y>HDx!*QB=v&Q(WwP+h1-t}YxpoP4tyR>1 zV)WA}E|`niGfC|N)qDTM#Vh|&o3O5QF3LVMKq{4>Aa@GttBRo;qq^r-F;qBz#boM1 z<;Bn8@;VVKQJ^qwDeE)gon=oVCRz%Q$BU1*9K3bHF?y5=DdV(|%jM8&6Of*!qkG+O z!CAn$?$bDP{W!IspST3>-hPZ9pNg0$ABq>|Q2&LCUHJZov($FG-E_Gz0H4#2yTMt2 z@y1DHsHS4ps5!_THy3GXlc+y6H3fx+Eac9~!1O6PG#T6I0T)?6kz-4{-A>bm8_oh= zdiX88pS=~YXTHaFMs>jJN25_;+xi9AzPS+JY+DSqT1ovx1ZhI#2vf+p(NJ&}FmME- zQuzxv6eNBqVq@izZ$iV35do2AL_oOUdmub41n@09VUU2^Lmc_&{d$BDB*5)*~6Os;Vk>G3Y^db~aY6DhcU=g9^GE^l?7b<5qiZmKwe%xii?XwyP+X}Fehg=CQqIma77kU4syvCDvZTq^?AJ>x_)nI zX<=85{!;{gkU@a|FCdjlk&*Ebz3d}3AIiRgVK*Q?QfRUf87_!K3|+m7#o~bH&d`4~ vAVwrdpTTfhvECB!pfW6GVj+wOh%CVWTPBj@<~4fi00000NkvXXu0mjfS#0QL literal 0 HcmV?d00001 diff --git a/src/VisualStudio/ProjectSystemCPS/Resources/XSharpProject.png b/src/VisualStudio/ProjectSystemCPS/Resources/XSharpProject.png new file mode 100644 index 0000000000000000000000000000000000000000..c016270a1330e664fa7184c8977fb821132c67fc GIT binary patch literal 1082 zcmV-A1jYM_P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGizW@LZzX3P}QzQTY1J6lBK~!i%?U%o6 zR6!8OcQ>tnfMt?1VquYph=qlg<{}ph(?>)*Ed)hWMEnJUh}wymJ}JzRTr3r}5D|@F zA!>eszI6wXt&xYnF9;+%yrU5*_EC!KD zctBO>!vFv5-@_tRouFyxFj`CWT@-Pw8AanPeU*L+**oCqYGllch}Ip+^9C5Kk3Z)| zCXY-svr=n3g&c&;NK3FCJ5YI1J?f0x1v&G5Cewh`6OA9%&=u_(veEQVy?RQ!35M&{ zOFZ$kc9+*{OQ%&1;>hihw#&=p%ptVid0FJfz*?;_M`WHrhF8egd-9+Qcr7w?9Ws0V z53g=Y9;AT(^MJb{cYB$~*@rfGw7Mu1#A}m_=eb#i%r8{*m~_x|_L<-I0b?&>_vAqe zSzhLG##&P6(F&2Mh0_`w>O&q{HWzn&wgb+z&)_ zMxF&ALwB)j{wHw)J;zsDhYHh{J%&qhn@|<5enpj(IQzoh8j#CTWt%UBoQCTKcdW`=NK58uP#|@ zk82l`v@@b+kTA#zA|t7#rrGM-x?q-OK&BG4Z!yDm3aX&wwSLssgY3c_-x99 zRoF{NY6<)`%A)L0{F?MXKMtG{*Z-Gm6g4{**y_<8zICp(8K+9SEn_pV0M8OBVBWz?oh(d|T?nle-<`!^qG7X5A$N&i} ztIC9N@Sw>20feBJ2TJ?vj2)5mGRe0AzmMChFqsB$x_{`;HO_3_*Aa}17kmhMsrwc0 z<;X?}FO#)<(Q+yNUK9mM4PsxD$OFBHRnEl+DrDR&>$OGk?q&%EaR>N=ihW6FeYrv0 z@012?H>CmF&5k!9kq1=lTQc)NNl8gbNpBeQ50`=5TXa_KD*ylh07*qoM6N<$g5j?5 AF8}}l literal 0 HcmV?d00001 diff --git a/src/VisualStudio/ProjectSystemCPS/XSharp.ProjectSystemCPS.imagemanifest b/src/VisualStudio/ProjectSystemCPS/XSharp.ProjectSystemCPS.imagemanifest new file mode 100644 index 0000000000..e95b4c8210 --- /dev/null +++ b/src/VisualStudio/ProjectSystemCPS/XSharp.ProjectSystemCPS.imagemanifest @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/VisualStudio/ProjectSystemCPS/XSharpCapabilities.cs b/src/VisualStudio/ProjectSystemCPS/XSharpCapabilities.cs index 1bff657bab..c2b8dc3aaf 100644 --- a/src/VisualStudio/ProjectSystemCPS/XSharpCapabilities.cs +++ b/src/VisualStudio/ProjectSystemCPS/XSharpCapabilities.cs @@ -23,5 +23,17 @@ internal static class XSharpCapabilities /// UnconfiguredProject scoped parts that influence that evaluation. Never declared in MSBuild. /// public const string XSharpCps = "XSharpCps"; + + /// + /// Initial capabilities of the X# CPS project type (ProjectTypeRegistration.Capabilities). + /// Modelled on the registration of the C# project type of the managed project system, without + /// LanguageService (X# has its own language service, see XSharp.DesignTime.targets), CSharp, + /// SharedImports (VB) and EditAndContinue (not supported by the X# debugger integration). + /// AppDesigner: "Properties" folder; OpenProjectFile: "Edit Project File"; + /// HandlesOwnReload: the project system reloads the project file itself after external changes; + /// ProjectConfigurationsDeclaredDimensions: configurations/platforms/target frameworks from the project file. + /// + public const string ProjectTypeCapabilities = + XSharpCps + "; AppDesigner; HandlesOwnReload; OpenProjectFile; PreserveFormatting; ProjectConfigurationsDeclaredDimensions; .NET; UseProjectEvaluationCache"; } } diff --git a/src/VisualStudio/ProjectSystemCPS/XSharpProjectSystemPackage.cs b/src/VisualStudio/ProjectSystemCPS/XSharpProjectSystemPackage.cs index 74d4f46f2d..13ca60cb73 100644 --- a/src/VisualStudio/ProjectSystemCPS/XSharpProjectSystemPackage.cs +++ b/src/VisualStudio/ProjectSystemCPS/XSharpProjectSystemPackage.cs @@ -34,7 +34,7 @@ namespace XSharp.ProjectSystem defaultProjectExtension: XSharpConstants.ProjectExtension, language: LanguageVsTemplate, resourcePackageGuid: XSharpConstants.guidXSharpCpsProjectPkgString, - Capabilities = XSharpCapabilities.XSharpCps, + Capabilities = XSharpCapabilities.ProjectTypeCapabilities, PossibleProjectExtensions = XSharpConstants.ProjectExtension)] #endif public sealed class XSharpProjectSystemPackage : AsyncPackage From 5b9514fdbddab0e983154d4bab4a1660a4652dec Mon Sep 17 00:00:00 2001 From: Hansjoerg Petriffer Date: Sat, 26 Sep 2026 17:55:29 +0200 Subject: [PATCH 04/60] [VsIntegration] CPS project system WP-3: X# item types and item templates - ProjectItemsSchema.XSharp.xaml maps .rc to NativeResource and the VO binaries (.xsfrm/.vnfrm/.xsdbs/.vndbs/.xsmnu/.vnmnu/.xsfs/.vnfs/.xssql/.xsrep) to VOBinary. - NativeResource/VOBinary File and BrowseObject rules restored from 58cca0eaeb^ and registered in XSharp.DesignTime.targets; fixed CustomTool/SubType data sources that still used ItemType None. - AddItemTemplatesGuid, GeneratorsTypeGuid and CmdUIContextGuid now use the X# project factory GUID {AA6C8D78-...} instead of the C# values, so CPS projects get the X# item templates and single file generators that the X# project package already registers there. Co-Authored-By: Claude Opus 5.5 --- .../Rules/NativeResource.BrowseObject.xaml | 162 ++++++++++++++++++ .../XSharpBuildTask/Rules/NativeResource.xaml | 93 ++++++++++ .../Rules/ProjectItemsSchema.XSharp.xaml | 28 ++- .../Rules/VOBinary.BrowseObject.xaml | 162 ++++++++++++++++++ .../XSharpBuildTask/Rules/VOBinary.xaml | 94 ++++++++++ .../XSharpBuildTask/XSharp.Build.csproj | 12 ++ .../XSharpBuildTask/XSharp.DesignTime.targets | 18 +- 7 files changed, 565 insertions(+), 4 deletions(-) create mode 100644 src/Compiler/src/Compiler/XSharpBuildTask/Rules/NativeResource.BrowseObject.xaml create mode 100644 src/Compiler/src/Compiler/XSharpBuildTask/Rules/NativeResource.xaml create mode 100644 src/Compiler/src/Compiler/XSharpBuildTask/Rules/VOBinary.BrowseObject.xaml create mode 100644 src/Compiler/src/Compiler/XSharpBuildTask/Rules/VOBinary.xaml diff --git a/src/Compiler/src/Compiler/XSharpBuildTask/Rules/NativeResource.BrowseObject.xaml b/src/Compiler/src/Compiler/XSharpBuildTask/Rules/NativeResource.BrowseObject.xaml new file mode 100644 index 0000000000..bcba14d58e --- /dev/null +++ b/src/Compiler/src/Compiler/XSharpBuildTask/Rules/NativeResource.BrowseObject.xaml @@ -0,0 +1,162 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Compiler/src/Compiler/XSharpBuildTask/Rules/NativeResource.xaml b/src/Compiler/src/Compiler/XSharpBuildTask/Rules/NativeResource.xaml new file mode 100644 index 0000000000..785ec2488e --- /dev/null +++ b/src/Compiler/src/Compiler/XSharpBuildTask/Rules/NativeResource.xaml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Compiler/src/Compiler/XSharpBuildTask/Rules/ProjectItemsSchema.XSharp.xaml b/src/Compiler/src/Compiler/XSharpBuildTask/Rules/ProjectItemsSchema.XSharp.xaml index 07b181d773..a066e89f27 100644 --- a/src/Compiler/src/Compiler/XSharpBuildTask/Rules/ProjectItemsSchema.XSharp.xaml +++ b/src/Compiler/src/Compiler/XSharpBuildTask/Rules/ProjectItemsSchema.XSharp.xaml @@ -2,7 +2,8 @@ @@ -19,9 +20,23 @@ ItemType="None"> + + + + + + + + @@ -29,4 +44,15 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/Compiler/src/Compiler/XSharpBuildTask/Rules/VOBinary.BrowseObject.xaml b/src/Compiler/src/Compiler/XSharpBuildTask/Rules/VOBinary.BrowseObject.xaml new file mode 100644 index 0000000000..19446f9949 --- /dev/null +++ b/src/Compiler/src/Compiler/XSharpBuildTask/Rules/VOBinary.BrowseObject.xaml @@ -0,0 +1,162 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Compiler/src/Compiler/XSharpBuildTask/Rules/VOBinary.xaml b/src/Compiler/src/Compiler/XSharpBuildTask/Rules/VOBinary.xaml new file mode 100644 index 0000000000..467cce8ab9 --- /dev/null +++ b/src/Compiler/src/Compiler/XSharpBuildTask/Rules/VOBinary.xaml @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Compiler/src/Compiler/XSharpBuildTask/XSharp.Build.csproj b/src/Compiler/src/Compiler/XSharpBuildTask/XSharp.Build.csproj index 135f9f0741..a17412db5e 100644 --- a/src/Compiler/src/Compiler/XSharpBuildTask/XSharp.Build.csproj +++ b/src/Compiler/src/Compiler/XSharpBuildTask/XSharp.Build.csproj @@ -82,6 +82,18 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + PreserveNewest diff --git a/src/Compiler/src/Compiler/XSharpBuildTask/XSharp.DesignTime.targets b/src/Compiler/src/Compiler/XSharpBuildTask/XSharp.DesignTime.targets index 78bb77a464..bf89fd6a27 100644 --- a/src/Compiler/src/Compiler/XSharpBuildTask/XSharp.DesignTime.targets +++ b/src/Compiler/src/Compiler/XSharpBuildTask/XSharp.DesignTime.targets @@ -42,9 +42,14 @@ inspired by Microsoft.CSharp.DesignTime.targets {E9B6EE0F-9BFA-4CFF-A60B-51A08BBC5050} XSharp - {FAE04EC0-301F-11d3-BF4B-00C04F79EFBC} - {FAE04EC1-301F-11d3-BF4B-00C04F79EFBC} - {FAE04EC1-301F-11d3-BF4B-00C04F79EFBC} + + {AA6C8D78-22FF-423A-9C7C-5F2393824E04} + {AA6C8D78-22FF-423A-9C7C-5F2393824E04} + {AA6C8D78-22FF-423A-9C7C-5F2393824E04} Private Camel Declarations @@ -52,6 +57,13 @@ inspired by Microsoft.CSharp.DesignTime.targets + + + File + + + BrowseObject + ProjectSubscriptionService From 4e2e45bb12ead10af294b719d54b46d9c98fce13 Mon Sep 17 00:00:00 2001 From: Hansjoerg Petriffer Date: Sat, 26 Sep 2026 17:59:39 +0200 Subject: [PATCH 05/60] [VsIntegration] CPS project system WP-4: X# property pages as XAML rules Property pages for the new property editor (capability ProjectPropertiesEditor), restored from 58cca0eaeb^ and registered with Context Project in XSharp.DesignTime.targets: - LanguagePage.XSharp.xaml (Language) and DialectPage.XSharp.xaml (Dialect) - BuildPropertyPage.XSharp.xaml and ApplicationPropertyPage.XSharp.xaml extend the managed Build/Application pages (OverrideMode="Extend") - ReferencesPage.XSharp.xaml (Global Usings) Corrected against the property names read by the Xsc task and persisted by MPFproj: Stddefs -> StandardDefs, OtherFlags -> CommandLineOption, UseShared -> UseSharedCompilation, INS as bool; added the missing VO17 and FOX3 dialect options. Co-Authored-By: Claude Opus 5.5 --- .../Rules/ApplicationPropertyPage.XSharp.xaml | 133 +++++++++++++++ .../Rules/BuildPropertyPage.XSharp.xaml | 53 ++++++ .../Rules/DialectPage.XSharp.xaml | 152 ++++++++++++++++++ .../Rules/LanguagePage.XSharp.xaml | 88 ++++++++++ .../Rules/ReferencesPage.XSharp.xaml | 57 +++++++ .../XSharpBuildTask/XSharp.Build.csproj | 15 ++ .../XSharpBuildTask/XSharp.DesignTime.targets | 8 + 7 files changed, 506 insertions(+) create mode 100644 src/Compiler/src/Compiler/XSharpBuildTask/Rules/ApplicationPropertyPage.XSharp.xaml create mode 100644 src/Compiler/src/Compiler/XSharpBuildTask/Rules/BuildPropertyPage.XSharp.xaml create mode 100644 src/Compiler/src/Compiler/XSharpBuildTask/Rules/DialectPage.XSharp.xaml create mode 100644 src/Compiler/src/Compiler/XSharpBuildTask/Rules/LanguagePage.XSharp.xaml create mode 100644 src/Compiler/src/Compiler/XSharpBuildTask/Rules/ReferencesPage.XSharp.xaml diff --git a/src/Compiler/src/Compiler/XSharpBuildTask/Rules/ApplicationPropertyPage.XSharp.xaml b/src/Compiler/src/Compiler/XSharpBuildTask/Rules/ApplicationPropertyPage.XSharp.xaml new file mode 100644 index 0000000000..36560fbbca --- /dev/null +++ b/src/Compiler/src/Compiler/XSharpBuildTask/Rules/ApplicationPropertyPage.XSharp.xaml @@ -0,0 +1,133 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + (has-evaluated-value "Application" "ResourceSpecificationKind" "IconAndManifest") + + + + + + + + + + + + + + + + (and + (has-evaluated-value "Application" "ApplicationManifestKind" "CustomManifest") + (has-evaluated-value "Application" "ResourceSpecificationKind" "IconAndManifest")) + + + + + + + + + + + + + + + + + (has-evaluated-value "Application" "ResourceSpecificationKind" "ResourceFile") + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Compiler/src/Compiler/XSharpBuildTask/Rules/BuildPropertyPage.XSharp.xaml b/src/Compiler/src/Compiler/XSharpBuildTask/Rules/BuildPropertyPage.XSharp.xaml new file mode 100644 index 0000000000..bdc28b430c --- /dev/null +++ b/src/Compiler/src/Compiler/XSharpBuildTask/Rules/BuildPropertyPage.XSharp.xaml @@ -0,0 +1,53 @@ + + + + + + + + + + + (has-evaluated-value "Build" "WarningLevelOverridden" false) + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Compiler/src/Compiler/XSharpBuildTask/Rules/DialectPage.XSharp.xaml b/src/Compiler/src/Compiler/XSharpBuildTask/Rules/DialectPage.XSharp.xaml new file mode 100644 index 0000000000..648e6635a8 --- /dev/null +++ b/src/Compiler/src/Compiler/XSharpBuildTask/Rules/DialectPage.XSharp.xaml @@ -0,0 +1,152 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + (not (has-evaluated-value "Language" "Dialect" "Core")) + + + + + + + + (not (has-evaluated-value "Language" "Dialect" "Core")) + + + + + + + + (not (has-evaluated-value "Language" "Dialect" "Core")) + + + + + + + + (not (has-evaluated-value "Language" "Dialect" "Core")) + + + + + + + + (not (has-evaluated-value "Language" "Dialect" "Core")) + + + + + + + + (not (has-evaluated-value "Language" "Dialect" "Core")) + + + + + + + + (not (has-evaluated-value "Language" "Dialect" "Core")) + + + + + + + + (not (has-evaluated-value "Language" "Dialect" "Core")) + + + + + + + + (not (has-evaluated-value "Language" "Dialect" "Core")) + + + + + + + + (not (has-evaluated-value "Language" "Dialect" "Core")) + + + + + + + + + (has-evaluated-value "Language" "Dialect" "XPP") + + + + + + + + + (has-evaluated-value "Language" "Dialect" "FoxPro") + + + + + + + + (not (has-evaluated-value "Language" "Dialect" "Core")) + + + + + + + + (has-evaluated-value "Language" "Dialect" "FoxPro") + + + + + + diff --git a/src/Compiler/src/Compiler/XSharpBuildTask/Rules/LanguagePage.XSharp.xaml b/src/Compiler/src/Compiler/XSharpBuildTask/Rules/LanguagePage.XSharp.xaml new file mode 100644 index 0000000000..22ee8e4d55 --- /dev/null +++ b/src/Compiler/src/Compiler/XSharpBuildTask/Rules/LanguagePage.XSharp.xaml @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + (not (has-evaluated-value "Language" "Dialect" "Core")) + + + + + + + + (has-evaluated-value "Language" "MemVar" true) + + + + + + + + + + + + + + + + diff --git a/src/Compiler/src/Compiler/XSharpBuildTask/Rules/ReferencesPage.XSharp.xaml b/src/Compiler/src/Compiler/XSharpBuildTask/Rules/ReferencesPage.XSharp.xaml new file mode 100644 index 0000000000..8d70f51f6d --- /dev/null +++ b/src/Compiler/src/Compiler/XSharpBuildTask/Rules/ReferencesPage.XSharp.xaml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + true + + + + + + + + + + + + + + (has-evaluated-value "References" "ImplicitUsings" true) + + + + + + diff --git a/src/Compiler/src/Compiler/XSharpBuildTask/XSharp.Build.csproj b/src/Compiler/src/Compiler/XSharpBuildTask/XSharp.Build.csproj index a17412db5e..204f4fa28b 100644 --- a/src/Compiler/src/Compiler/XSharpBuildTask/XSharp.Build.csproj +++ b/src/Compiler/src/Compiler/XSharpBuildTask/XSharp.Build.csproj @@ -94,6 +94,21 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + PreserveNewest diff --git a/src/Compiler/src/Compiler/XSharpBuildTask/XSharp.DesignTime.targets b/src/Compiler/src/Compiler/XSharpBuildTask/XSharp.DesignTime.targets index bf89fd6a27..280889140b 100644 --- a/src/Compiler/src/Compiler/XSharpBuildTask/XSharp.DesignTime.targets +++ b/src/Compiler/src/Compiler/XSharpBuildTask/XSharp.DesignTime.targets @@ -57,6 +57,14 @@ inspired by Microsoft.CSharp.DesignTime.targets + + + Project + File From bd20c48452d59f3b555acdd6ddfe2b3a9a755ff0 Mon Sep 17 00:00:00 2001 From: Hansjoerg Petriffer Date: Sat, 26 Sep 2026 18:05:07 +0200 Subject: [PATCH 06/60] [VsIntegration] CPS project system WP-6: commands side by side, Error List and Task List MPFproj commands (ProjectPackage) with X# projects loaded by CPS, where XProject.ProjectNode is the XSharpProjectAdapter instead of an XSharpProjectNode: - CommandGenerateWinForm: hard cast replaced by a type check (would throw InvalidCastException) - CommandAddDesignerFile: only visible for MPFproj projects (needs the MPFproj file node) - CommandEditProjectFile: only visible for MPFproj projects (CPS has its own Edit Project File) - new helper Commands.ProjectIsMpfProjectAsync() ProjectSystemCPS replacements for the MPFproj ErrorListManager/TaskListManager: - IntellisenseErrorList shows the code model errors of the project in the Error List - CommentTaskList shows the comment tasks after project/file walks in the Task List Both refresh coalesced on the UI thread. Co-Authored-By: Claude Opus 5.5 --- .../Commands/CommandAddDesignerFile.cs | 6 + .../Commands/CommandEditProjectFile.cs | 3 +- .../Commands/CommandGenerateWinForm.cs | 21 +-- .../Commands/XSharpBaseCommand.cs | 9 ++ .../LanguageService/CommentTaskList.cs | 110 +++++++++++++++ .../LanguageService/IntellisenseErrorList.cs | 125 ++++++++++++++++++ .../LanguageService/IntellisenseErrorStore.cs | 28 +++- .../LanguageService/XSharpProjectAdapter.cs | 37 ++++++ 8 files changed, 326 insertions(+), 13 deletions(-) create mode 100644 src/VisualStudio/ProjectSystemCPS/LanguageService/CommentTaskList.cs create mode 100644 src/VisualStudio/ProjectSystemCPS/LanguageService/IntellisenseErrorList.cs diff --git a/src/VisualStudio/ProjectPackage/Commands/CommandAddDesignerFile.cs b/src/VisualStudio/ProjectPackage/Commands/CommandAddDesignerFile.cs index 5cead49a07..14766c0684 100644 --- a/src/VisualStudio/ProjectPackage/Commands/CommandAddDesignerFile.cs +++ b/src/VisualStudio/ProjectPackage/Commands/CommandAddDesignerFile.cs @@ -49,6 +49,12 @@ private async Task CheckAvailabilityAsync() } } } + // The designer file is generated from the MPFproj file node (XSharpFileNode) + if (visible && !await Commands.ProjectIsMpfProjectAsync()) + { + visible = false; + currentFile = null; + } Command.Visible = visible; if (items.Count() != 1) { diff --git a/src/VisualStudio/ProjectPackage/Commands/CommandEditProjectFile.cs b/src/VisualStudio/ProjectPackage/Commands/CommandEditProjectFile.cs index 7611ad3f8e..b1536c5349 100644 --- a/src/VisualStudio/ProjectPackage/Commands/CommandEditProjectFile.cs +++ b/src/VisualStudio/ProjectPackage/Commands/CommandEditProjectFile.cs @@ -36,7 +36,8 @@ protected override void BeforeQueryStatus(EventArgs e) } private async Task CheckAvailabilityAsync() { - Command.Visible = await Commands.ProjectIsXSharpProjectAsync(); + // CPS projects have their own "Edit Project File" command (capability OpenProjectFile) + Command.Visible = await Commands.ProjectIsMpfProjectAsync(); } protected override async Task ExecuteAsync(OleMenuCmdEventArgs e) { diff --git a/src/VisualStudio/ProjectPackage/Commands/CommandGenerateWinForm.cs b/src/VisualStudio/ProjectPackage/Commands/CommandGenerateWinForm.cs index 57169be525..54c876907c 100644 --- a/src/VisualStudio/ProjectPackage/Commands/CommandGenerateWinForm.cs +++ b/src/VisualStudio/ProjectPackage/Commands/CommandGenerateWinForm.cs @@ -86,16 +86,19 @@ protected override async Task ExecuteAsync(OleMenuCmdEventArgs e) } var newdesignerform = Path.ChangeExtension(newform, ".designer.prg"); var x = await project.AddExistingFilesAsync(new[] { newform, newdesignerform }); - var prjNode = (XSharpProjectNode) xproject.ProjectNode; - var file = prjNode.FindChild(newform); - if (file is XSharpFileNode fileNode && fileNode.IsLink) + // MPFproj only: remove the Link metadata. CPS projects are not an XSharpProjectNode. + if (xproject.ProjectNode is XSharpProjectNode prjNode) { - file.ItemNode.SetMetadata("Link", null); - } - file = prjNode.FindChild(newdesignerform); - if (file is XSharpFileNode designerfileNode && designerfileNode.IsLink) - { - file.ItemNode.SetMetadata("Link", null); + var file = prjNode.FindChild(newform); + if (file is XSharpFileNode fileNode && fileNode.IsLink) + { + file.ItemNode.SetMetadata("Link", null); + } + file = prjNode.FindChild(newdesignerform); + if (file is XSharpFileNode designerfileNode && designerfileNode.IsLink) + { + file.ItemNode.SetMetadata("Link", null); + } } diff --git a/src/VisualStudio/ProjectPackage/Commands/XSharpBaseCommand.cs b/src/VisualStudio/ProjectPackage/Commands/XSharpBaseCommand.cs index c14aa42f82..9644c26d6d 100644 --- a/src/VisualStudio/ProjectPackage/Commands/XSharpBaseCommand.cs +++ b/src/VisualStudio/ProjectPackage/Commands/XSharpBaseCommand.cs @@ -29,6 +29,15 @@ internal async static Task ProjectIsXSharpProjectAsync() var project = await VS.Solutions.GetActiveProjectAsync(); return project.IsXSharp(); } + /// + /// True when the active project is an X# project that is loaded by the MPFproj project system. + /// X# projects that are loaded by the CPS project system (XSharp.ProjectSystemCPS) have no XSharpProjectNode. + /// + internal async static Task ProjectIsMpfProjectAsync() + { + var project = await VS.Solutions.GetActiveProjectAsync(); + return project != null && XSharpProjectNode.FindProject(project.FullPath) != null; + } internal async static Task ProjectIsXSharpSdkProjectAsync() { var project = await VS.Solutions.GetActiveProjectAsync(); diff --git a/src/VisualStudio/ProjectSystemCPS/LanguageService/CommentTaskList.cs b/src/VisualStudio/ProjectSystemCPS/LanguageService/CommentTaskList.cs new file mode 100644 index 0000000000..e22f6ae687 --- /dev/null +++ b/src/VisualStudio/ProjectSystemCPS/LanguageService/CommentTaskList.cs @@ -0,0 +1,110 @@ +// +// Copyright (c) XSharp B.V. All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +// + +using System; +using System.Collections.Generic; +using System.Threading; +using Microsoft.VisualStudio; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Threading; +using XSharpModel; + +namespace XSharp.ProjectSystem.LanguageService +{ + /// + /// Shows the comment tasks (TODO, HACK etc.) of a CPS project in the Task List. + /// + /// + /// Replaces the TaskListManager of MPFproj (ProjectPackage), which XSharpProjectNode fills after each + /// project or file walk. Updates are coalesced and applied on the UI thread. + /// + internal sealed class CommentTaskList : IDisposable + { + private readonly JoinableTaskFactory joinableTaskFactory; + private readonly string projectName; + private TaskProvider provider; + private IList pending; + private int refreshScheduled; + private bool disposed; + + public CommentTaskList(JoinableTaskFactory joinableTaskFactory, string projectName) + { + this.joinableTaskFactory = joinableTaskFactory; + this.projectName = projectName; + } + + public void Update(IList tasks) + { + Volatile.Write(ref pending, tasks); + if (Interlocked.Exchange(ref refreshScheduled, 1) == 0) + { + joinableTaskFactory.RunAsync(RefreshAsync).FileAndForget("XSharp/ProjectSystemCPS/TaskList"); + } + } + + private async System.Threading.Tasks.Task RefreshAsync() + { + await joinableTaskFactory.SwitchToMainThreadAsync(); + Interlocked.Exchange(ref refreshScheduled, 0); + if (disposed) + return; + var tasks = Volatile.Read(ref pending) ?? Array.Empty(); + if (provider == null) + { + provider = new TaskProvider(ServiceProvider.GlobalProvider) + { + ProviderName = "X# Comments (" + projectName + ")", + }; + } + provider.SuspendRefresh(); + try + { + provider.Tasks.Clear(); + foreach (var commentTask in tasks) + { + if (commentTask?.File == null) + continue; + var task = new TaskListItem + { + Document = commentTask.File.FullPath, + // XCommentTask positions are 1-based, the task list is 0-based + Line = Math.Max(0, commentTask.Line - 1), + Column = Math.Max(0, commentTask.Column - 1), + Text = commentTask.Comment, + Priority = (TaskPriority)commentTask.Priority, + Category = TaskCategory.Comments, + }; + task.Navigate += (sender, e) => + { + ThreadHelper.ThrowIfNotOnUIThread(); + provider?.Navigate((TaskListItem)sender, VSConstants.LOGVIEWID.TextView_guid); + }; + provider.Tasks.Add(task); + } + } + finally + { + provider.ResumeRefresh(); + } + } + + public void Dispose() + { + disposed = true; + var old = provider; + provider = null; + if (old != null) + { + joinableTaskFactory.RunAsync(async () => + { + await joinableTaskFactory.SwitchToMainThreadAsync(); + old.Tasks.Clear(); + old.Dispose(); + }).FileAndForget("XSharp/ProjectSystemCPS/TaskList"); + } + } + } +} diff --git a/src/VisualStudio/ProjectSystemCPS/LanguageService/IntellisenseErrorList.cs b/src/VisualStudio/ProjectSystemCPS/LanguageService/IntellisenseErrorList.cs new file mode 100644 index 0000000000..02196736cf --- /dev/null +++ b/src/VisualStudio/ProjectSystemCPS/LanguageService/IntellisenseErrorList.cs @@ -0,0 +1,125 @@ +// +// Copyright (c) XSharp B.V. All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +// + +using System; +using System.Collections.Generic; +using System.Threading; +using Microsoft.VisualStudio; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Threading; +using XSharpModel; +using DiagnosticSeverity = LanguageService.CodeAnalysis.DiagnosticSeverity; + +namespace XSharp.ProjectSystem.LanguageService +{ + /// + /// Shows the intellisense errors of a CPS project in the Error List. + /// + /// + /// Replaces the ErrorListManager of MPFproj (ProjectPackage), which needs the MPFproj hierarchy. + /// Updates arrive on background threads (parser); they are coalesced and applied on the UI thread. + /// + internal sealed class IntellisenseErrorList : IDisposable + { + private readonly JoinableTaskFactory joinableTaskFactory; + private readonly string projectName; + private ErrorListProvider provider; + private IReadOnlyList pending; + private int refreshScheduled; + private bool disposed; + + public IntellisenseErrorList(JoinableTaskFactory joinableTaskFactory, string projectName) + { + this.joinableTaskFactory = joinableTaskFactory; + this.projectName = projectName; + } + + /// + /// Replace the errors of the project with . + /// + public void Update(IReadOnlyList errors) + { + Volatile.Write(ref pending, errors); + if (Interlocked.Exchange(ref refreshScheduled, 1) == 0) + { + joinableTaskFactory.RunAsync(RefreshAsync).FileAndForget("XSharp/ProjectSystemCPS/ErrorList"); + } + } + + private async System.Threading.Tasks.Task RefreshAsync() + { + await joinableTaskFactory.SwitchToMainThreadAsync(); + Interlocked.Exchange(ref refreshScheduled, 0); + if (disposed) + return; + var errors = Volatile.Read(ref pending) ?? Array.Empty(); + if (provider == null) + { + provider = new ErrorListProvider(ServiceProvider.GlobalProvider) + { + ProviderName = "X# IntelliSense (" + projectName + ")", + }; + } + provider.SuspendRefresh(); + try + { + provider.Tasks.Clear(); + foreach (var error in errors) + { + var task = new ErrorTask + { + Document = error.Path, + // XError positions are 1-based, the error list is 0-based + Line = Math.Max(0, error.Span.Line - 1), + Column = Math.Max(0, error.Span.Column - 1), + Text = string.IsNullOrEmpty(error.ErrCode) ? error.ToString() : error.ErrCode + ": " + error.ToString(), + ErrorCategory = ToCategory(error.Severity), + Category = TaskCategory.CodeSense, + }; + task.Navigate += (sender, e) => + { + ThreadHelper.ThrowIfNotOnUIThread(); + provider?.Navigate((ErrorTask)sender, VSConstants.LOGVIEWID.TextView_guid); + }; + provider.Tasks.Add(task); + } + } + finally + { + provider.ResumeRefresh(); + } + } + + private static TaskErrorCategory ToCategory(DiagnosticSeverity severity) + { + switch (severity) + { + case DiagnosticSeverity.Error: + return TaskErrorCategory.Error; + case DiagnosticSeverity.Warning: + return TaskErrorCategory.Warning; + default: + return TaskErrorCategory.Message; + } + } + + public void Dispose() + { + disposed = true; + var old = provider; + provider = null; + if (old != null) + { + joinableTaskFactory.RunAsync(async () => + { + await joinableTaskFactory.SwitchToMainThreadAsync(); + old.Tasks.Clear(); + old.Dispose(); + }).FileAndForget("XSharp/ProjectSystemCPS/ErrorList"); + } + } + } +} diff --git a/src/VisualStudio/ProjectSystemCPS/LanguageService/IntellisenseErrorStore.cs b/src/VisualStudio/ProjectSystemCPS/LanguageService/IntellisenseErrorStore.cs index bec13bcca1..e8b3e384fc 100644 --- a/src/VisualStudio/ProjectSystemCPS/LanguageService/IntellisenseErrorStore.cs +++ b/src/VisualStudio/ProjectSystemCPS/LanguageService/IntellisenseErrorStore.cs @@ -16,13 +16,18 @@ namespace XSharp.ProjectSystem.LanguageService /// /// /// The MPFproj project system keeps these in its ErrorListManager (ProjectPackage), which is tied to the - /// MPFproj hierarchy. This store only keeps the errors for the code model (GetIntellisenseErrors), it does - /// not show them in the Error List yet. + /// MPFproj hierarchy. This store keeps the errors for the code model (GetIntellisenseErrors) and reports + /// every change through (used for the Error List, see IntellisenseErrorList). /// internal sealed class IntellisenseErrorStore { private readonly Dictionary> errors = new Dictionary>(StringComparer.OrdinalIgnoreCase); + /// + /// Called with all errors of the project after each change. + /// + public Action> Changed { get; set; } + public void Add(XError error) { if (error == null || string.IsNullOrEmpty(error.Path)) @@ -36,16 +41,33 @@ public void Add(XError error) } list.Add(error); } + OnChanged(); } public void Clear(string fileName) { if (string.IsNullOrEmpty(fileName)) return; + bool removed; + lock (errors) + { + removed = errors.Remove(fileName); + } + if (removed) + OnChanged(); + } + + private void OnChanged() + { + var changed = Changed; + if (changed == null) + return; + List all; lock (errors) { - errors.Remove(fileName); + all = errors.Values.SelectMany(l => l).ToList(); } + changed(all); } public List Get(string fileName) diff --git a/src/VisualStudio/ProjectSystemCPS/LanguageService/XSharpProjectAdapter.cs b/src/VisualStudio/ProjectSystemCPS/LanguageService/XSharpProjectAdapter.cs index 68861c2cc7..62a4beea8c 100644 --- a/src/VisualStudio/ProjectSystemCPS/LanguageService/XSharpProjectAdapter.cs +++ b/src/VisualStudio/ProjectSystemCPS/LanguageService/XSharpProjectAdapter.cs @@ -66,6 +66,8 @@ internal sealed class XSharpProjectAdapter : IProjectDynamicLoadComponent, IXSha private XProject model; private List links; + private IntellisenseErrorList errorList; + private CommentTaskList taskList; private XParseOptions parseOptions = XParseOptions.Default; private bool parseOptionsFromCommandLine; private string rootNamespace = ""; @@ -102,8 +104,13 @@ public async Task LoadAsync() if (model != null) return; model = new XProject(this); + model.ProjectWalkComplete += OnProjectWalkComplete; + model.FileWalkComplete += OnFileWalkComplete; } XSettings.Information("XSharpProjectAdapter: created code model for " + project.FullPath); + errorList = new IntellisenseErrorList(threading.JoinableTaskFactory, DisplayName); + errors.Changed = errorList.Update; + taskList = new CommentTaskList(threading.JoinableTaskFactory, DisplayName); var linkOptions = new DataflowLinkOptions { PropagateCompletion = true }; links = new List @@ -133,8 +140,18 @@ public Task UnloadAsync() link.Dispose(); links = null; } + if (model != null) + { + model.ProjectWalkComplete -= OnProjectWalkComplete; + model.FileWalkComplete -= OnFileWalkComplete; + } oldModel = model; model = null; + errors.Changed = null; + errorList?.Dispose(); + errorList = null; + taskList?.Dispose(); + taskList = null; files.Clear(); projectReferences.Clear(); } @@ -320,6 +337,26 @@ private static string GetProperty(IProjectRuleSnapshot snapshot, string name) return snapshot.Properties.TryGetValue(name, out var value) && value != null ? value : ""; } + // Like XSharpProjectNode.OnProjectWalkComplete/OnFileWalkComplete: refresh the comment tasks + private void OnProjectWalkComplete(XProject xProject) => RefreshCommentTasks(); + + private void OnFileWalkComplete(XFile xFile) => RefreshCommentTasks(); + + private void RefreshCommentTasks() + { + try + { + var current = model; + var tasks = taskList; + if (current != null && tasks != null) + tasks.Update(current.GetCommentTasks()); + } + catch (Exception e) + { + XSettings.Exception(e); + } + } + private void WalkProject() { var current = model; From 3a4d0a0358c69c8b8630a6bb7facec9c48b45434 Mon Sep 17 00:00:00 2001 From: Hansjoerg Petriffer Date: Sat, 26 Sep 2026 18:07:07 +0200 Subject: [PATCH 07/60] [VsIntegration] CPS project system WP-7: VO designers through the adapter The VO editor panes hold the IXSharpProject of the file's XProject, which is the XSharpProjectAdapter for CPS projects, so the VO designers are served through the adapter. The designers (XSharpVoEditors Functions.EnsureFileNodeExists/DeleteFile) write generated files to disk and then call HasFileNode/AddFileNode/DeleteFileNode. The adapter now registers and unregisters these files with the code model immediately instead of ignoring the calls; in SDK-style projects the XSharp.SDK.Props globs include them (with DependentUpon), so the project file does not change and CPS confirms the item with the next evaluation. HasFileNode compares full paths. Co-Authored-By: Claude Opus 5.5 --- .../LanguageService/XSharpProjectAdapter.cs | 39 +++++++++++++++++-- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/src/VisualStudio/ProjectSystemCPS/LanguageService/XSharpProjectAdapter.cs b/src/VisualStudio/ProjectSystemCPS/LanguageService/XSharpProjectAdapter.cs index 62a4beea8c..4f5fbb5b4f 100644 --- a/src/VisualStudio/ProjectSystemCPS/LanguageService/XSharpProjectAdapter.cs +++ b/src/VisualStudio/ProjectSystemCPS/LanguageService/XSharpProjectAdapter.cs @@ -378,21 +378,52 @@ private void WalkProject() public bool HasFileNode(string fileName) { + if (string.IsNullOrEmpty(fileName)) + return false; + var fullPath = XSharpCommandLine.MakeFullPath(fileName, ProjectFolder); lock (gate) { - return files.Contains(fileName); + return files.Contains(fullPath); } } + /// + /// Called by the VO designers (XSharpVoEditors, Functions.EnsureFileNodeExists) after they have written a + /// generated file (.prg, .rc, ...) to disk. + /// + /// + /// In SDK-style X# projects these files are included by the globs of XSharp.SDK.Props (including + /// DependentUpon), so the project file does not change: CPS picks the file up with the next evaluation. + /// The file is registered with the code model immediately, so HasFileNode is true right away; the later + /// source item update finds it already registered. + /// Projects without default items (EnableDefaultItems=false, legacy format) would need an explicit item + /// through CPS; that belongs to the legacy-format step. + /// public void AddFileNode(string fileName) { - // Called by the code model for files that it wants to see in the project (e.g. generated files). - // Under CPS the project file is the source of truth; adding items is done through CPS (WP-6). + if (string.IsNullOrEmpty(fileName)) + return; + var fullPath = XSharpCommandLine.MakeFullPath(fileName, ProjectFolder); + lock (gate) + { + if (model != null && files.Add(fullPath)) + model.AddFile(fullPath); + } } + /// + /// Called by the VO designers before they delete a generated file. See . + /// public void DeleteFileNode(string fileName) { - // See AddFileNode + if (string.IsNullOrEmpty(fileName)) + return; + var fullPath = XSharpCommandLine.MakeFullPath(fileName, ProjectFolder); + lock (gate) + { + if (model != null && files.Remove(fullPath)) + model.RemoveFile(fullPath); + } } public void ClearIntellisenseErrors(string fileName) => errors.Clear(fileName); From 467724a734d2392c48374d3abc93cc1a61fa0dda Mon Sep 17 00:00:00 2001 From: Hansjoerg Petriffer Date: Sat, 26 Sep 2026 18:19:45 +0200 Subject: [PATCH 08/60] [VsIntegration] CPS project system: make the project properties UI available X# CPS projects had no project properties UI: CPS only offers the project designer when at least one IVsProjectDesignerPageProvider applies (VsProjectDesignerPageService.IsProjectDesignerSupported), and the managed project system only has providers for C#, VB and F#. - New XSharpProjectDesignerPageProvider ([AppliesTo("XSharpCps & AppDesigner")]). It lists no classic COM pages: with the ProjectPropertiesEditor capability the AppDesigner editor factory opens the new property editor, which shows the XAML rules (managed pages + X# pages). - ProjectPropertiesEditor is now also an initial capability of the project type, so the new editor does not depend on the X# targets that declare it in XSharp.DesignTime.targets. Co-Authored-By: Claude Opus 5.5 --- .../XSharpProjectDesignerPageProvider.cs | 41 +++++++++++++++++++ .../ProjectSystemCPS/XSharpCapabilities.cs | 6 ++- 2 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 src/VisualStudio/ProjectSystemCPS/PropertyPages/XSharpProjectDesignerPageProvider.cs diff --git a/src/VisualStudio/ProjectSystemCPS/PropertyPages/XSharpProjectDesignerPageProvider.cs b/src/VisualStudio/ProjectSystemCPS/PropertyPages/XSharpProjectDesignerPageProvider.cs new file mode 100644 index 0000000000..9118112337 --- /dev/null +++ b/src/VisualStudio/ProjectSystemCPS/PropertyPages/XSharpProjectDesignerPageProvider.cs @@ -0,0 +1,41 @@ +// +// Copyright (c) XSharp B.V. All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +// + +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Threading.Tasks; +using Microsoft.VisualStudio.ProjectSystem; +using Microsoft.VisualStudio.ProjectSystem.VS.Properties; + +namespace XSharp.ProjectSystem.PropertyPages +{ + /// + /// Makes the project designer (Project > Properties, double click on the Properties folder) available for + /// X# CPS projects. + /// + /// + /// CPS only offers the project designer when at least one IVsProjectDesignerPageProvider applies to the + /// project (VsProjectDesignerPageService.IsProjectDesignerSupported). The managed project system only has + /// providers for C#, VB and F#. + /// Because X# CPS projects have the ProjectPropertiesEditor capability, the AppDesigner editor factory opens + /// the new property editor, which shows the XAML rules with Context "Project" (the managed pages plus the X# + /// pages from XSharpBuildTask\Rules). The classic COM property pages listed by this provider are not used + /// in that case, so there are none: the X# COM property pages (AppDesigner2022) depend on MPFproj. + /// + [Export(typeof(IVsProjectDesignerPageProvider))] + [AppliesTo(XSharpCapabilities.XSharpCps + " & AppDesigner")] + internal sealed class XSharpProjectDesignerPageProvider : IVsProjectDesignerPageProvider + { + private static readonly Task> noPages = + Task.FromResult>(Array.Empty()); + + public Task> GetPagesAsync() + { + return noPages; + } + } +} diff --git a/src/VisualStudio/ProjectSystemCPS/XSharpCapabilities.cs b/src/VisualStudio/ProjectSystemCPS/XSharpCapabilities.cs index c2b8dc3aaf..b9fcf5979d 100644 --- a/src/VisualStudio/ProjectSystemCPS/XSharpCapabilities.cs +++ b/src/VisualStudio/ProjectSystemCPS/XSharpCapabilities.cs @@ -31,9 +31,11 @@ internal static class XSharpCapabilities /// SharedImports (VB) and EditAndContinue (not supported by the X# debugger integration). /// AppDesigner: "Properties" folder; OpenProjectFile: "Edit Project File"; /// HandlesOwnReload: the project system reloads the project file itself after external changes; - /// ProjectConfigurationsDeclaredDimensions: configurations/platforms/target frameworks from the project file. + /// ProjectConfigurationsDeclaredDimensions: configurations/platforms/target frameworks from the project file; + /// ProjectPropertiesEditor: the AppDesigner opens the new property editor (XAML rules). Also declared in + /// XSharp.DesignTime.targets, but repeated here so that it does not depend on the installed X# targets. /// public const string ProjectTypeCapabilities = - XSharpCps + "; AppDesigner; HandlesOwnReload; OpenProjectFile; PreserveFormatting; ProjectConfigurationsDeclaredDimensions; .NET; UseProjectEvaluationCache"; + XSharpCps + "; AppDesigner; HandlesOwnReload; OpenProjectFile; PreserveFormatting; ProjectConfigurationsDeclaredDimensions; ProjectPropertiesEditor; .NET; UseProjectEvaluationCache"; } } From 1c22514c430aa0b7f9f866dc8886315a832bd642 Mon Sep 17 00:00:00 2001 From: Hansjoerg Petriffer Date: Sat, 26 Sep 2026 18:29:13 +0200 Subject: [PATCH 09/60] [VsIntegration] CPS project system: project selector for SDK-style and legacy projects Two X# project systems side by side, like the legacy and SDK project systems of C#/VB/F#: legacy .xsproj stay on MPFproj ({AA6C8D78-...}), SDK-style .xsproj are loaded by CPS ({AB494DCE-...}). - XSharpProjectSelector (IVsProjectSelector, the reserved guidProjectSelectorString) is registered for the MPFproj project type and sends projects with , or to the CPS project type. Existing solution files keep their project type GUID. Unreadable project files and errors fall back to MPFproj. - ProvideProjectSelectorAttribute writes Projects\{AA6C8D78}\ProjectSelector and ProjectSelectors\{selector}\Package; the package registers the selector with IVsRegisterProjectSelector during initialization and unregisters it on dispose. - Tools > Options > X# > Project System: "Load SDK-style projects with the new project system (CPS)" (default on) switches the routing off without uninstalling anything. - Still only registered with XSHARPCPS (Debug builds). - Test bed: Legacy/LegacyTestBed.xsproj and SelectorTestBed.sln (both projects with the MPFproj GUID); StartExp.cmd takes the solution as optional parameter. Co-Authored-By: Claude Opus 5.5 --- .../Legacy/LegacyTestBed.xsproj | 44 +++++++++ .../ProjectSystemCPS.TestBed/Legacy/Start.prg | 5 + .../SelectorTestBed.sln | 27 ++++++ .../ProjectSystemCPS.TestBed/StartExp.cmd | 7 +- .../Options/ProjectSystemOptionsPage.cs | 28 ++++++ .../ProvideProjectSelectorAttribute.cs | 64 +++++++++++++ .../Selection/XSharpProjectSelector.cs | 92 +++++++++++++++++++ .../XSharpProjectSystemPackage.cs | 63 ++++++++++++- 8 files changed, 325 insertions(+), 5 deletions(-) create mode 100644 src/VisualStudio/ProjectSystemCPS.TestBed/Legacy/LegacyTestBed.xsproj create mode 100644 src/VisualStudio/ProjectSystemCPS.TestBed/Legacy/Start.prg create mode 100644 src/VisualStudio/ProjectSystemCPS.TestBed/SelectorTestBed.sln create mode 100644 src/VisualStudio/ProjectSystemCPS/Options/ProjectSystemOptionsPage.cs create mode 100644 src/VisualStudio/ProjectSystemCPS/Selection/ProvideProjectSelectorAttribute.cs create mode 100644 src/VisualStudio/ProjectSystemCPS/Selection/XSharpProjectSelector.cs diff --git a/src/VisualStudio/ProjectSystemCPS.TestBed/Legacy/LegacyTestBed.xsproj b/src/VisualStudio/ProjectSystemCPS.TestBed/Legacy/LegacyTestBed.xsproj new file mode 100644 index 0000000000..3711d34d0f --- /dev/null +++ b/src/VisualStudio/ProjectSystemCPS.TestBed/Legacy/LegacyTestBed.xsproj @@ -0,0 +1,44 @@ + + + + + + Debug + AnyCPU + {9DAD9E60-2617-45A2-ADB3-98DD2AFD9EF3} + Exe + LegacyTestBed + LegacyTestBed + v4.8 + LegacyTestBed + Core + true + + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + + + + + + + + + + \ No newline at end of file diff --git a/src/VisualStudio/ProjectSystemCPS.TestBed/Legacy/Start.prg b/src/VisualStudio/ProjectSystemCPS.TestBed/Legacy/Start.prg new file mode 100644 index 0000000000..e48605e410 --- /dev/null +++ b/src/VisualStudio/ProjectSystemCPS.TestBed/Legacy/Start.prg @@ -0,0 +1,5 @@ +using System + +function Start() as void strict + Console.WriteLine("Hello from the classic X# project system") + return diff --git a/src/VisualStudio/ProjectSystemCPS.TestBed/SelectorTestBed.sln b/src/VisualStudio/ProjectSystemCPS.TestBed/SelectorTestBed.sln new file mode 100644 index 0000000000..e2785c9ca3 --- /dev/null +++ b/src/VisualStudio/ProjectSystemCPS.TestBed/SelectorTestBed.sln @@ -0,0 +1,27 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.14.37111.16 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{AA6C8D78-22FF-423A-9C7C-5F2393824E04}") = "CpsTestBed", "CpsTestBed.xsproj", "{E89C628F-2E33-47E2-AA05-0BC29B9C2DD2}" +EndProject +Project("{AA6C8D78-22FF-423A-9C7C-5F2393824E04}") = "LegacyTestBed", "Legacy\LegacyTestBed.xsproj", "{9DAD9E60-2617-45A2-ADB3-98DD2AFD9EF3}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {E89C628F-2E33-47E2-AA05-0BC29B9C2DD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E89C628F-2E33-47E2-AA05-0BC29B9C2DD2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E89C628F-2E33-47E2-AA05-0BC29B9C2DD2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E89C628F-2E33-47E2-AA05-0BC29B9C2DD2}.Release|Any CPU.Build.0 = Release|Any CPU + {9DAD9E60-2617-45A2-ADB3-98DD2AFD9EF3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9DAD9E60-2617-45A2-ADB3-98DD2AFD9EF3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9DAD9E60-2617-45A2-ADB3-98DD2AFD9EF3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9DAD9E60-2617-45A2-ADB3-98DD2AFD9EF3}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/src/VisualStudio/ProjectSystemCPS.TestBed/StartExp.cmd b/src/VisualStudio/ProjectSystemCPS.TestBed/StartExp.cmd index 944c034570..9ce0813cd3 100644 --- a/src/VisualStudio/ProjectSystemCPS.TestBed/StartExp.cmd +++ b/src/VisualStudio/ProjectSystemCPS.TestBed/StartExp.cmd @@ -6,6 +6,9 @@ rem XSharpCpsProjectSystem import in XSharp.CurrentVersion.targets). They are no rem copies the installed X# MsBuild folder to Artifacts\CpsMsBuild, overlays the changed files from the rem repository and starts devenv with XSharpMsBuildDir pointing to that folder. The installed X# is not changed. rem +rem Usage: StartExp.cmd [solution] (default CpsTestBed.sln; SelectorTestBed.sln tests the project selector: +rem both projects use the classic X# project type GUID, the SDK-style one must be loaded by CPS) +rem rem Prerequisite: a Debug build of VisualStudio\ProjectPackage\ProjectPackage2022.csproj, which deploys the rem VSIX (including XSharp.ProjectSystemCPS) to the experimental instance. setlocal @@ -32,4 +35,6 @@ if not defined DEVENV ( set XSharpMsBuildDir=%OVERLAY% echo XSharpMsBuildDir=%XSharpMsBuildDir% -start "" "%DEVENV%" /rootSuffix Exp "%~dp0CpsTestBed.sln" +set SOLUTION=%~1 +if "%SOLUTION%" == "" set SOLUTION=CpsTestBed.sln +start "" "%DEVENV%" /rootSuffix Exp "%~dp0%SOLUTION%" diff --git a/src/VisualStudio/ProjectSystemCPS/Options/ProjectSystemOptionsPage.cs b/src/VisualStudio/ProjectSystemCPS/Options/ProjectSystemOptionsPage.cs new file mode 100644 index 0000000000..ca0cc7b8f0 --- /dev/null +++ b/src/VisualStudio/ProjectSystemCPS/Options/ProjectSystemOptionsPage.cs @@ -0,0 +1,28 @@ +// +// Copyright (c) XSharp B.V. All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +// + +using System.ComponentModel; +using Microsoft.VisualStudio.Shell; + +namespace XSharp.ProjectSystem.Options +{ + /// + /// Tools > Options > X# > Project System. + /// + internal sealed class ProjectSystemOptionsPage : DialogPage + { + internal const string CategoryName = "X#"; + internal const string PageName = "Project System"; + + [Category("Project System")] + [DisplayName("Load SDK-style projects with the new project system (CPS)")] + [Description("When True, SDK-style X# projects () are loaded by the new CPS based project system; " + + "legacy X# projects are always loaded by the classic project system. " + + "The change applies to projects that are loaded (or reloaded) after the change.")] + [DefaultValue(true)] + public bool UseCpsForSdkProjects { get; set; } = true; + } +} diff --git a/src/VisualStudio/ProjectSystemCPS/Selection/ProvideProjectSelectorAttribute.cs b/src/VisualStudio/ProjectSystemCPS/Selection/ProvideProjectSelectorAttribute.cs new file mode 100644 index 0000000000..74dde8d968 --- /dev/null +++ b/src/VisualStudio/ProjectSystemCPS/Selection/ProvideProjectSelectorAttribute.cs @@ -0,0 +1,64 @@ +// +// Copyright (c) XSharp B.V. All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +// + +using System; +using Microsoft.VisualStudio.Shell; + +namespace XSharp.ProjectSystem.Selection +{ + /// + /// Registers a project selector for a project type: + /// + /// [$RootKey$\Projects\{projectType}] "ProjectSelector" = {selector} + /// [$RootKey$\ProjectSelectors\{selector}] "Package" = {package that registers the selector} + /// + /// VS loads the package when it needs the selector; the package registers the selector object with + /// IVsRegisterProjectSelector during its initialization. + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)] + internal sealed class ProvideProjectSelectorAttribute : RegistrationAttribute + { + private readonly Guid projectTypeGuid; + private readonly Guid selectorGuid; + + public ProvideProjectSelectorAttribute(string projectTypeGuid, string selectorGuid) + { + this.projectTypeGuid = new Guid(projectTypeGuid); + this.selectorGuid = new Guid(selectorGuid); + } + + private string ProjectTypeKey => "Projects\\" + projectTypeGuid.ToString("B"); + private string SelectorKey => "ProjectSelectors\\" + selectorGuid.ToString("B"); + + public override void Register(RegistrationContext context) + { + var projectKey = context.CreateKey(ProjectTypeKey); + try + { + projectKey.SetValue("ProjectSelector", selectorGuid.ToString("B")); + } + finally + { + projectKey.Close(); + } + var selectorKey = context.CreateKey(SelectorKey); + try + { + selectorKey.SetValue("Package", context.ComponentType.GUID.ToString("B")); + } + finally + { + selectorKey.Close(); + } + } + + public override void Unregister(RegistrationContext context) + { + context.RemoveValue(ProjectTypeKey, "ProjectSelector"); + context.RemoveKey(SelectorKey); + } + } +} diff --git a/src/VisualStudio/ProjectSystemCPS/Selection/XSharpProjectSelector.cs b/src/VisualStudio/ProjectSystemCPS/Selection/XSharpProjectSelector.cs new file mode 100644 index 0000000000..0461c8bd79 --- /dev/null +++ b/src/VisualStudio/ProjectSystemCPS/Selection/XSharpProjectSelector.cs @@ -0,0 +1,92 @@ +// +// Copyright (c) XSharp B.V. All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +// + +using System; +using System.Runtime.InteropServices; +using System.Xml; +using System.Xml.Linq; +using System.Xml.XPath; +using Microsoft.VisualStudio.Shell.Interop; +using XSharp.Settings; + +namespace XSharp.ProjectSystem.Selection +{ + /// + /// Chooses the project system for projects with the X# (MPFproj) project type GUID {AA6C8D78-...}: + /// SDK-style projects are loaded by the CPS project type, all other (legacy) projects by MPFproj. + /// + /// + /// Same mechanism as the managed project system uses for C#/VB/F# (FSharpProjectSelector): the selector is + /// registered for the legacy project type GUID (pkgdef "Projects\{guid}\ProjectSelector", see + /// ), so existing solution files keep their project type GUID. + /// Solutions that use the CPS project type GUID directly do not go through the selector. + /// Any problem (unreadable project file, routing switched off) falls back to MPFproj, the project system + /// that loaded these projects before. + /// + [Guid(XSharpConstants.guidProjectSelectorString)] + internal sealed class XSharpProjectSelector : IVsProjectSelector + { + private const string MSBuildXmlNamespace = "http://schemas.microsoft.com/developer/msbuild/2003"; + + private readonly Func useCpsForSdkProjects; + + public XSharpProjectSelector(Func useCpsForSdkProjects) + { + this.useCpsForSdkProjects = useCpsForSdkProjects; + } + + public void GetProjectFactoryGuid(Guid guidProjectType, string pszFilename, out Guid guidProjectFactory) + { + guidProjectFactory = XSharpConstants.guidXSharpProjectFactory; + try + { + if (useCpsForSdkProjects() && IsSdkProject(pszFilename)) + { + guidProjectFactory = XSharpConstants.guidCpsProjectType; + } + } + catch (Exception e) + { + XSettings.Exception(e); + guidProjectFactory = XSharpConstants.guidXSharpProjectFactory; + } + XSettings.Information("XSharpProjectSelector: " + pszFilename + " -> " + + (guidProjectFactory == XSharpConstants.guidCpsProjectType ? "CPS" : "MPFproj")); + } + + /// + /// True for SDK-style project files: <Project Sdk="...">, <Import Sdk="..." /> or <Sdk Name="..." />. + /// + internal static bool IsSdkProject(string fileName) + { + if (string.IsNullOrEmpty(fileName)) + return false; + XDocument document; + try + { + document = XDocument.Load(fileName); + } + catch (Exception) + { + // Not (yet) a valid project file, e.g. a new project that is still being created + return false; + } + return IsSdkProject(document); + } + + internal static bool IsSdkProject(XDocument document) + { + var namespaces = new XmlNamespaceManager(new NameTable()); + namespaces.AddNamespace("msb", MSBuildXmlNamespace); + return document.XPathSelectElement("/msb:Project[@Sdk]", namespaces) != null + || document.XPathSelectElement("/Project[@Sdk]") != null + || document.XPathSelectElement("/*/msb:Import[@Sdk]", namespaces) != null + || document.XPathSelectElement("/*/Import[@Sdk]") != null + || document.XPathSelectElement("/*/msb:Sdk[@Name]", namespaces) != null + || document.XPathSelectElement("/*/Sdk[@Name]") != null; + } + } +} diff --git a/src/VisualStudio/ProjectSystemCPS/XSharpProjectSystemPackage.cs b/src/VisualStudio/ProjectSystemCPS/XSharpProjectSystemPackage.cs index 13ca60cb73..11ea249f63 100644 --- a/src/VisualStudio/ProjectSystemCPS/XSharpProjectSystemPackage.cs +++ b/src/VisualStudio/ProjectSystemCPS/XSharpProjectSystemPackage.cs @@ -10,6 +10,9 @@ using Microsoft.VisualStudio.Shell; #if XSHARPCPS using Microsoft.VisualStudio.ProjectSystem.VS; +using Microsoft.VisualStudio.Shell.Interop; +using XSharp.ProjectSystem.Options; +using XSharp.ProjectSystem.Selection; #endif using Task = System.Threading.Tasks.Task; @@ -19,10 +22,16 @@ namespace XSharp.ProjectSystem /// Package for the CPS based X# project system. /// /// - /// The CPS project type is only registered when XSHARPCPS is defined (Debug builds). The MPFproj project - /// system registers the same .xsproj extension, so during the side-by-side phase the CPS project type uses - /// its own project type GUID and its own template language: a solution selects it explicitly through the - /// project type GUID, and the existing X# project templates keep creating MPFproj projects. + /// Two X# project systems run side by side, like the legacy and the SDK project systems of C#/VB/F#: + /// + /// legacy .xsproj: MPFproj (ProjectPackage), project type {AA6C8D78-...} + /// SDK-style .xsproj: CPS (this package), project type {AB494DCE-...} + /// + /// is registered for the MPFproj project type and sends SDK-style projects + /// to the CPS project type (switchable in Tools > Options > X# > Project System), so existing solution files + /// do not change. The CPS project type keeps its own template language, so the X# project templates keep + /// creating projects through the MPFproj project type (and the selector). + /// Everything is only registered when XSHARPCPS is defined (Debug builds) until parity with MPFproj is proven. /// [PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)] [Guid(XSharpConstants.guidXSharpCpsProjectPkgString)] @@ -36,6 +45,8 @@ namespace XSharp.ProjectSystem resourcePackageGuid: XSharpConstants.guidXSharpCpsProjectPkgString, Capabilities = XSharpCapabilities.ProjectTypeCapabilities, PossibleProjectExtensions = XSharpConstants.ProjectExtension)] + [ProvideProjectSelector(XSharpConstants.guidXSharpProjectFactoryString, XSharpConstants.guidProjectSelectorString)] + [ProvideOptionPage(typeof(ProjectSystemOptionsPage), ProjectSystemOptionsPage.CategoryName, ProjectSystemOptionsPage.PageName, 0, 0, true)] #endif public sealed class XSharpProjectSystemPackage : AsyncPackage { @@ -45,9 +56,53 @@ public sealed class XSharpProjectSystemPackage : AsyncPackage /// internal const string LanguageVsTemplate = "XSharpCps"; +#if XSHARPCPS + private IVsRegisterProjectSelector projectSelectorRegistration; + private uint projectSelectorCookie; + private ProjectSystemOptionsPage options; +#endif + protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress progress) { await base.InitializeAsync(cancellationToken, progress); +#if XSHARPCPS + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + options = (ProjectSystemOptionsPage)GetDialogPage(typeof(ProjectSystemOptionsPage)); + projectSelectorRegistration = await GetServiceAsync(typeof(SVsRegisterProjectTypes)) as IVsRegisterProjectSelector; + if (projectSelectorRegistration != null) + { + var selectorGuid = new Guid(XSharpConstants.guidProjectSelectorString); + var selector = new XSharpProjectSelector(() => options?.UseCpsForSdkProjects ?? true); + projectSelectorRegistration.RegisterProjectSelector(ref selectorGuid, selector, out projectSelectorCookie); + } +#endif } + + protected override void Dispose(bool disposing) + { +#if XSHARPCPS + if (disposing && projectSelectorCookie != 0) + { + JoinableTaskFactory.Run(async () => + { + await JoinableTaskFactory.SwitchToMainThreadAsync(); + UnregisterProjectSelector(); + }); + } +#endif + base.Dispose(disposing); + } +#if XSHARPCPS + + private void UnregisterProjectSelector() + { + ThreadHelper.ThrowIfNotOnUIThread(); + if (projectSelectorRegistration != null && projectSelectorCookie != 0) + { + projectSelectorRegistration.UnregisterProjectSelector(projectSelectorCookie); + projectSelectorCookie = 0; + } + } +#endif } } From 3c0dee91b875b66b604c1bb4ab7aa6a4b2abb3be Mon Sep 17 00:00:00 2001 From: Hansjoerg Petriffer Date: Sun, 27 Sep 2026 10:31:19 +0200 Subject: [PATCH 10/60] [VsIntegration] CPS project system: open the X# solution model for CPS-only solutions Found in the log of the first VS test: "XSharpProjectAdapter: X# solution model is not open, no code model". XSolution.Open is called by the X# project package in OnBeforeOpenSolution. For solutions with the MPFproj project type VS loads that package before the solution opens (it provides the project factory); for the CPS project type it is only autoloaded later, misses OnBeforeOpenSolution and does not see the solution yet while it is still opening, so the database was never opened and the CPS project got no code model. - XSharpProjectAdapter: after loading the X# project package, open the solution model for the solution file reported by IVsSolution.GetSolutionInfo, instead of waiting 10s and giving up. - XSharpShellLink.OnAfterOpenSolution: open the solution model before XSolution.AfterOpen() when it is still closed (safety net for any late package load). Co-Authored-By: Claude Opus 5.5 --- .../LanguageService/XSharpProjectAdapter.cs | 19 ++++++++++++++----- src/VisualStudio/Support/XSharpShellLink.cs | 6 ++++++ 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/VisualStudio/ProjectSystemCPS/LanguageService/XSharpProjectAdapter.cs b/src/VisualStudio/ProjectSystemCPS/LanguageService/XSharpProjectAdapter.cs index 4f5fbb5b4f..8805d56031 100644 --- a/src/VisualStudio/ProjectSystemCPS/LanguageService/XSharpProjectAdapter.cs +++ b/src/VisualStudio/ProjectSystemCPS/LanguageService/XSharpProjectAdapter.cs @@ -160,9 +160,16 @@ public Task UnloadAsync() } /// - /// The X# solution model (database) is opened by the X# packages when a solution opens. When a CPS - /// project loads before these packages have been initialized, load the X# project package and wait. + /// Makes sure that the X# solution model (database) is open before the code model is created. /// + /// + /// The X# project package opens it in OnBeforeOpenSolution. For solutions with the MPFproj project type + /// VS loads that package before the solution opens (it provides the project factory). For the CPS project + /// type it does not: the X# package is only loaded later (autoload), misses OnBeforeOpenSolution and does + /// not see the solution yet while it is still opening, so the database was never opened. + /// Load the X# project package (shell link, logging, settings) and open the database for the solution + /// file that is being loaded, like XSharpShellLink does in OnBeforeOpenSolution. + /// private async Task EnsureSolutionIsOpenAsync() { if (XSolution.IsOpen) @@ -173,10 +180,12 @@ private async Task EnsureSolutionIsOpenAsync() var packageGuid = new Guid(XSharpConstants.guidXSharpProjectPkgString); shell.LoadPackage(ref packageGuid, out _); } - await TaskScheduler.Default; - for (int i = 0; i < 100 && !XSolution.IsOpen; i++) + if (!XSolution.IsOpen && ServiceProvider.GlobalProvider.GetService(typeof(SVsSolution)) is IVsSolution solution && + solution.GetSolutionInfo(out _, out var solutionFile, out _) == 0 && + !string.IsNullOrEmpty(solutionFile) && File.Exists(solutionFile)) { - await Task.Delay(100); + XSettings.Information("XSharpProjectAdapter: opening the X# solution model for " + solutionFile); + XSolution.Open(solutionFile); } return XSolution.IsOpen; } diff --git a/src/VisualStudio/Support/XSharpShellLink.cs b/src/VisualStudio/Support/XSharpShellLink.cs index b1258443ad..4578353606 100644 --- a/src/VisualStudio/Support/XSharpShellLink.cs +++ b/src/VisualStudio/Support/XSharpShellLink.cs @@ -263,6 +263,12 @@ private void SolutionEvents_OnAfterOpenSolution(Solution solution) _missingProjects.Clear(); } + // The database is normally opened in OnBeforeOpenSolution. When this package was loaded after that + // event (e.g. a solution with only CPS based X# projects), it is still closed here. + if (!XSolution.IsOpen && !string.IsNullOrEmpty(solutionName) && File.Exists(solutionName)) + { + XSolution.Open(solutionName); + } XSolution.AfterOpen(); } private void SolutionEvents_OnAfterBackgroundSolutionLoadComplete() From d7428e6d91ac2d5d826a2eeab3a636f74667a275 Mon Sep 17 00:00:00 2001 From: Hansjoerg Petriffer Date: Sun, 27 Sep 2026 10:36:12 +0200 Subject: [PATCH 11/60] [VsIntegration] CPS project system: fix source item subscription, first VS test results Found in the experimental instance: the CPS code model got parse options and references from the design-time build, but never any source files. LinkTo without rule names subscribes to an empty rule set, so the SourceItemsRuleSource link delivered nothing. The source item rules are dynamic (one per item type): link with SourceItemRuleNamesSource as the rule names block, as dotnet/project-system does. Added a log line with the evaluated project properties. Verified in VS (SelectorTestBed.sln): the selector loads CpsTestBed through CPS and LegacyTestBed through MPFproj; the adapter opens the solution model, adds Program.prg, the walker parses it, the design-time build delivers 58 options and 10 references; editing the project file removes files live. Test bed: CpsTestBed.xsproj excludes the Legacy subfolder from its globs (it compiled the legacy project's Start.prg as well). Co-Authored-By: Claude Opus 5.5 --- src/VisualStudio/ProjectSystemCPS.TestBed/CpsTestBed.xsproj | 5 +++++ .../ProjectSystemCPS/LanguageService/XSharpProjectAdapter.cs | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/src/VisualStudio/ProjectSystemCPS.TestBed/CpsTestBed.xsproj b/src/VisualStudio/ProjectSystemCPS.TestBed/CpsTestBed.xsproj index 38a07adee6..68d9d07fa1 100644 --- a/src/VisualStudio/ProjectSystemCPS.TestBed/CpsTestBed.xsproj +++ b/src/VisualStudio/ProjectSystemCPS.TestBed/CpsTestBed.xsproj @@ -10,4 +10,9 @@ Core CpsTestBed + + + + + \ No newline at end of file diff --git a/src/VisualStudio/ProjectSystemCPS/LanguageService/XSharpProjectAdapter.cs b/src/VisualStudio/ProjectSystemCPS/LanguageService/XSharpProjectAdapter.cs index 8805d56031..e9f6cdfa65 100644 --- a/src/VisualStudio/ProjectSystemCPS/LanguageService/XSharpProjectAdapter.cs +++ b/src/VisualStudio/ProjectSystemCPS/LanguageService/XSharpProjectAdapter.cs @@ -115,8 +115,11 @@ public async Task LoadAsync() var linkOptions = new DataflowLinkOptions { PropagateCompletion = true }; links = new List { + // The source item rules are dynamic (one per item type): take the rule names from + // SourceItemRuleNamesSource. LinkTo without rule names subscribes to no rules at all. subscriptions.SourceItemsRuleSource.SourceBlock.LinkTo( DataflowBlockSlim.CreateActionBlock>(OnSourceItemsChanged), + subscriptions.SourceItemRuleNamesSource.SourceBlock, linkOptions), subscriptions.ProjectRuleSource.SourceBlock.LinkTo( DataflowBlockSlim.CreateActionBlock>(OnEvaluationChanged), @@ -254,6 +257,7 @@ private void OnEvaluationChanged(IProjectVersionedValue Date: Sun, 27 Sep 2026 10:42:29 +0200 Subject: [PATCH 12/60] [VsIntegration] CPS test bed: refresh the image library cache of the experimental instance The X# project icon was not shown in the experimental instance: its ImageLibrary.cache dated from 2024 and did not contain XSharp.ProjectSystemCPS.imagemanifest, because the Debug build deploys the VSIX without refreshing that cache. StartExp.cmd now deletes the cache of the experimental instance before starting; VS rebuilds it (verified: the manifest is in the rebuilt cache). Normal VSIX installations refresh the cache themselves. Co-Authored-By: Claude Opus 5.5 --- src/VisualStudio/ProjectSystemCPS.TestBed/StartExp.cmd | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/VisualStudio/ProjectSystemCPS.TestBed/StartExp.cmd b/src/VisualStudio/ProjectSystemCPS.TestBed/StartExp.cmd index 9ce0813cd3..43782a028b 100644 --- a/src/VisualStudio/ProjectSystemCPS.TestBed/StartExp.cmd +++ b/src/VisualStudio/ProjectSystemCPS.TestBed/StartExp.cmd @@ -33,6 +33,11 @@ if not defined DEVENV ( exit /b 1 ) +rem A Debug build deploys the VSIX to the experimental instance without refreshing its image library cache, +rem so new image monikers (XSharp.ProjectSystemCPS.imagemanifest: project and file icons) would not be found. +rem VS rebuilds the cache when it is missing. +for /d %%h in ("%LOCALAPPDATA%\Microsoft\VisualStudio\17.0_*Exp") do if exist "%%h\ImageLibrary\ImageLibrary.cache" del /q "%%h\ImageLibrary\ImageLibrary.cache" + set XSharpMsBuildDir=%OVERLAY% echo XSharpMsBuildDir=%XSharpMsBuildDir% set SOLUTION=%~1 From b426dde06b45e8db9163555a91c45fffacc8280c Mon Sep 17 00:00:00 2001 From: Hansjoerg Petriffer Date: Sun, 27 Sep 2026 11:10:45 +0200 Subject: [PATCH 13/60] [VsIntegration] CPS project system: register the CPS project type in Release builds The CPS project type, the project selector and the Tools > Options page were only registered with XSHARPCPS (Debug builds). The define and the conditional code are removed, so the Release VSIX registers them as well. Safety net for the shipped product: the selector only sends SDK-style projects to CPS when the installed X# MSBuild support files contain the CPS design-time import (XSharpCpsProjectSystem in XSharp.CurrentVersion.targets) and the XAML rules (new XSharpMsBuildSupport; folder from XSharpMsBuildDir, the registry or Program Files (x86)\XSharp\MsBuild, like XSharp.BeforeCommon.Props). With an older X# installation SDK-style projects stay on MPFproj instead of loading in CPS without rules. Verified: installed X# -> false, repository overlay (StartExp.cmd) -> true. Co-Authored-By: Claude Opus 5.5 --- .../source.extension.2022.vsixmanifest | 2 +- .../ProjectSystemCPS/ProjectSystemCPS.csproj | 3 +- .../Selection/XSharpMsBuildSupport.cs | 76 +++++++++++++++++++ .../Selection/XSharpProjectSelector.cs | 5 +- .../XSharpProjectSystemPackage.cs | 17 +---- 5 files changed, 83 insertions(+), 20 deletions(-) create mode 100644 src/VisualStudio/ProjectSystemCPS/Selection/XSharpMsBuildSupport.cs diff --git a/src/VisualStudio/ProjectPackage/source.extension.2022.vsixmanifest b/src/VisualStudio/ProjectPackage/source.extension.2022.vsixmanifest index 04d1e07bca..e6214fec69 100644 --- a/src/VisualStudio/ProjectPackage/source.extension.2022.vsixmanifest +++ b/src/VisualStudio/ProjectPackage/source.extension.2022.vsixmanifest @@ -41,7 +41,7 @@ Includes a Project System, Language Service , Debugger Support, Custom Editors e - + diff --git a/src/VisualStudio/ProjectSystemCPS/ProjectSystemCPS.csproj b/src/VisualStudio/ProjectSystemCPS/ProjectSystemCPS.csproj index c58a3c4bef..efd1d0e922 100644 --- a/src/VisualStudio/ProjectSystemCPS/ProjectSystemCPS.csproj +++ b/src/VisualStudio/ProjectSystemCPS/ProjectSystemCPS.csproj @@ -29,8 +29,7 @@ false - - TRACE;DEBUG;XSHARP;DEV17;XSHARPCPS + TRACE;DEBUG;XSHARP;DEV17 full diff --git a/src/VisualStudio/ProjectSystemCPS/Selection/XSharpMsBuildSupport.cs b/src/VisualStudio/ProjectSystemCPS/Selection/XSharpMsBuildSupport.cs new file mode 100644 index 0000000000..9673983bee --- /dev/null +++ b/src/VisualStudio/ProjectSystemCPS/Selection/XSharpMsBuildSupport.cs @@ -0,0 +1,76 @@ +// +// Copyright (c) XSharp B.V. All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +// + +using System; +using System.IO; +using Microsoft.Win32; +using XSharp.Settings; + +namespace XSharp.ProjectSystem.Selection +{ + /// + /// Checks whether the installed X# MSBuild support files can be used by the CPS project system. + /// + /// + /// CPS projects need the design-time import in XSharp.CurrentVersion.targets (property XSharpCpsProjectSystem) + /// and the XAML rules in the Rules folder. These are part of the X# installation, not of the VSIX. When an older + /// X# installation is combined with this VSIX, SDK-style projects stay on MPFproj instead of loading in CPS + /// without rules. + /// The folder is determined like XSharp.BeforeCommon.Props does: environment variable XSharpMsBuildDir, + /// the XSharpPath in the registry, or Program Files (x86)\XSharp\MsBuild. + /// + internal static class XSharpMsBuildSupport + { + private static readonly Lazy supportsCps = new Lazy(CheckSupportsCps); + + public static bool SupportsCps => supportsCps.Value; + + private static bool CheckSupportsCps() + { + try + { + var folder = FindMsBuildFolder(); + if (string.IsNullOrEmpty(folder)) + { + XSettings.Information("XSharpMsBuildSupport: X# MsBuild folder not found, CPS disabled"); + return false; + } + var targets = Path.Combine(folder, "XSharp.CurrentVersion.targets"); + var rule = Path.Combine(folder, "Rules", "XSharpCompilerCommandLineArgs.xaml"); + var supported = File.Exists(targets) && File.Exists(rule) && + File.ReadAllText(targets).IndexOf("XSharpCpsProjectSystem", StringComparison.OrdinalIgnoreCase) >= 0; + XSettings.Information("XSharpMsBuildSupport: " + folder + (supported ? " supports CPS" : " does not support CPS (X# installation too old), SDK-style projects stay on the classic project system")); + return supported; + } + catch (Exception e) + { + XSettings.Exception(e); + return false; + } + } + + private static string FindMsBuildFolder() + { + var folder = Environment.GetEnvironmentVariable("XSharpMsBuildDir"); + if (!string.IsNullOrEmpty(folder) && Directory.Exists(folder)) + return folder; + foreach (var key in new[] { @"SOFTWARE\WOW6432Node\XSharpBV\XSharp", @"SOFTWARE\XSharpBV\XSharp" }) + { + using (var regKey = Registry.LocalMachine.OpenSubKey(key)) + { + if (regKey?.GetValue("XSharpPath") is string path && !string.IsNullOrEmpty(path)) + { + folder = Path.Combine(path, "MsBuild"); + if (Directory.Exists(folder)) + return folder; + } + } + } + folder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "XSharp", "MsBuild"); + return Directory.Exists(folder) ? folder : null; + } + } +} diff --git a/src/VisualStudio/ProjectSystemCPS/Selection/XSharpProjectSelector.cs b/src/VisualStudio/ProjectSystemCPS/Selection/XSharpProjectSelector.cs index 0461c8bd79..bc2883fadb 100644 --- a/src/VisualStudio/ProjectSystemCPS/Selection/XSharpProjectSelector.cs +++ b/src/VisualStudio/ProjectSystemCPS/Selection/XSharpProjectSelector.cs @@ -23,7 +23,8 @@ namespace XSharp.ProjectSystem.Selection /// registered for the legacy project type GUID (pkgdef "Projects\{guid}\ProjectSelector", see /// ), so existing solution files keep their project type GUID. /// Solutions that use the CPS project type GUID directly do not go through the selector. - /// Any problem (unreadable project file, routing switched off) falls back to MPFproj, the project system + /// Any problem (unreadable project file, routing switched off, X# MSBuild support files without the CPS + /// design-time import, see ) falls back to MPFproj, the project system /// that loaded these projects before. /// [Guid(XSharpConstants.guidProjectSelectorString)] @@ -43,7 +44,7 @@ public void GetProjectFactoryGuid(Guid guidProjectType, string pszFilename, out guidProjectFactory = XSharpConstants.guidXSharpProjectFactory; try { - if (useCpsForSdkProjects() && IsSdkProject(pszFilename)) + if (useCpsForSdkProjects() && IsSdkProject(pszFilename) && XSharpMsBuildSupport.SupportsCps) { guidProjectFactory = XSharpConstants.guidCpsProjectType; } diff --git a/src/VisualStudio/ProjectSystemCPS/XSharpProjectSystemPackage.cs b/src/VisualStudio/ProjectSystemCPS/XSharpProjectSystemPackage.cs index 11ea249f63..518e488d81 100644 --- a/src/VisualStudio/ProjectSystemCPS/XSharpProjectSystemPackage.cs +++ b/src/VisualStudio/ProjectSystemCPS/XSharpProjectSystemPackage.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) XSharp B.V. All Rights Reserved. // Licensed under the Apache License, Version 2.0. // See License.txt in the project root for license information. @@ -7,13 +7,11 @@ using System; using System.Runtime.InteropServices; using System.Threading; -using Microsoft.VisualStudio.Shell; -#if XSHARPCPS using Microsoft.VisualStudio.ProjectSystem.VS; +using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; using XSharp.ProjectSystem.Options; using XSharp.ProjectSystem.Selection; -#endif using Task = System.Threading.Tasks.Task; namespace XSharp.ProjectSystem @@ -31,11 +29,9 @@ namespace XSharp.ProjectSystem /// to the CPS project type (switchable in Tools > Options > X# > Project System), so existing solution files /// do not change. The CPS project type keeps its own template language, so the X# project templates keep /// creating projects through the MPFproj project type (and the selector). - /// Everything is only registered when XSHARPCPS is defined (Debug builds) until parity with MPFproj is proven. /// [PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)] [Guid(XSharpConstants.guidXSharpCpsProjectPkgString)] -#if XSHARPCPS [ProjectTypeRegistration( projectTypeGuid: XSharpConstants.guidCpsProjectTypeString, displayName: "XSharp (CPS)", @@ -47,7 +43,6 @@ namespace XSharp.ProjectSystem PossibleProjectExtensions = XSharpConstants.ProjectExtension)] [ProvideProjectSelector(XSharpConstants.guidXSharpProjectFactoryString, XSharpConstants.guidProjectSelectorString)] [ProvideOptionPage(typeof(ProjectSystemOptionsPage), ProjectSystemOptionsPage.CategoryName, ProjectSystemOptionsPage.PageName, 0, 0, true)] -#endif public sealed class XSharpProjectSystemPackage : AsyncPackage { /// @@ -56,16 +51,13 @@ public sealed class XSharpProjectSystemPackage : AsyncPackage /// internal const string LanguageVsTemplate = "XSharpCps"; -#if XSHARPCPS private IVsRegisterProjectSelector projectSelectorRegistration; private uint projectSelectorCookie; private ProjectSystemOptionsPage options; -#endif protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress progress) { await base.InitializeAsync(cancellationToken, progress); -#if XSHARPCPS await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); options = (ProjectSystemOptionsPage)GetDialogPage(typeof(ProjectSystemOptionsPage)); projectSelectorRegistration = await GetServiceAsync(typeof(SVsRegisterProjectTypes)) as IVsRegisterProjectSelector; @@ -75,12 +67,10 @@ protected override async Task InitializeAsync(CancellationToken cancellationToke var selector = new XSharpProjectSelector(() => options?.UseCpsForSdkProjects ?? true); projectSelectorRegistration.RegisterProjectSelector(ref selectorGuid, selector, out projectSelectorCookie); } -#endif } protected override void Dispose(bool disposing) { -#if XSHARPCPS if (disposing && projectSelectorCookie != 0) { JoinableTaskFactory.Run(async () => @@ -89,10 +79,8 @@ protected override void Dispose(bool disposing) UnregisterProjectSelector(); }); } -#endif base.Dispose(disposing); } -#if XSHARPCPS private void UnregisterProjectSelector() { @@ -103,6 +91,5 @@ private void UnregisterProjectSelector() projectSelectorCookie = 0; } } -#endif } } From 8ee1974ca9790d3f91894b013e58a1b148566033 Mon Sep 17 00:00:00 2001 From: Hansjoerg Petriffer Date: Sun, 27 Sep 2026 11:32:56 +0200 Subject: [PATCH 14/60] [VsIntegration] Port the shadow WinForms designer to the CPS project system Prerequisite for removing the MPFproj SDK path (WP-8): the shadow designer (WinForms designer for SDK-style X# projects via a hidden companion C# project) was built on the MPFproj SDK project node, so SDK projects loaded by CPS had no WinForms designer. - ShadowDesigner moved from ProjectPackage to ProjectSystemCPS (namespace XSharp.ProjectSystem.ShadowDesigner) and made project system independent: ShadowDesignerBridge.TryOpen/TryResolveCompanionPaths take the .prg path and the XProject instead of an XSharpFileNode; services from the global service provider instead of the MPFproj node/package; ShadowDesignerCleanup implements IVsSolutionEvents instead of deriving from the MPFproj SolutionListener; no Community Toolkit dependency. Public helpers IsCpsProject and HasDesignerFile. - XSharpCodeDomHelper (BuildDesignerFileName, MergeCodeCompileUnit, ...) moved from ProjectPackage to the shared CodeDomProvider project, so both project systems can use it. - CPS entry points: XSharpShadowDesignerDefaultActionCommand (double click / Enter) and XSharpShadowDesignerViewFormCommand (View Designer) open the shadow designer for a .prg with a matching .Designer.prg; otherwise CPS falls back to its default action. - MPFproj keeps working: XSharpFileNode calls the new API; the Sync Designer Changes / Sync Event Handlers commands now work for SDK projects in MPFproj and in CPS. Co-Authored-By: Claude Opus 5.5 --- .../CodeDomProvider/CodeDomProvider.csproj | 1 + .../CodeDomProvider/XSharpCodeDomHelper.cs | 8 +- .../Commands/CommandSyncDesignerChanges.cs | 25 ++-- .../Commands/CommandSyncEventHandlers.cs | 25 ++-- .../ProjectPackage/Nodes/XSharpFileNode.cs | 4 +- .../ProjectPackage/ProjectPackage.csproj | 1 - .../ProjectPackage/ProjectPackage2022.csproj | 9 -- .../ProjectSystemCPS/ProjectSystemCPS.csproj | 6 +- .../ShadowDesigner/CompanionProjectWriter.cs | 14 +- .../ShadowDesigner/CompanionSaveWatcher.cs | 26 ++-- .../ShadowDesigner/DesignerChangesSync.cs | 7 +- .../ShadowDesigner/EventHandlerSync.cs | 7 +- .../ProjectSystemCPS/ShadowDesigner/Logger.cs | 20 +++ .../ShadowDesigner/RoslynToCodeDom.cs | 5 +- .../ShadowDesigner/ShadowDesignerBridge.cs | 84 +++++++----- .../ShadowDesigner/ShadowDesignerCleanup.cs | 37 +++-- .../ShadowDesigner/SolutionWiring.cs | 8 +- .../XSharpShadowDesignerCommands.cs | 128 ++++++++++++++++++ 18 files changed, 303 insertions(+), 112 deletions(-) rename src/VisualStudio/{ProjectPackage => }/CodeDomProvider/XSharpCodeDomHelper.cs (96%) rename src/VisualStudio/{ProjectPackage => ProjectSystemCPS}/ShadowDesigner/CompanionProjectWriter.cs (95%) rename src/VisualStudio/{ProjectPackage => ProjectSystemCPS}/ShadowDesigner/CompanionSaveWatcher.cs (93%) rename src/VisualStudio/{ProjectPackage => ProjectSystemCPS}/ShadowDesigner/DesignerChangesSync.cs (95%) rename src/VisualStudio/{ProjectPackage => ProjectSystemCPS}/ShadowDesigner/EventHandlerSync.cs (97%) create mode 100644 src/VisualStudio/ProjectSystemCPS/ShadowDesigner/Logger.cs rename src/VisualStudio/{ProjectPackage => ProjectSystemCPS}/ShadowDesigner/RoslynToCodeDom.cs (98%) rename src/VisualStudio/{ProjectPackage => ProjectSystemCPS}/ShadowDesigner/ShadowDesignerBridge.cs (83%) rename src/VisualStudio/{ProjectPackage => ProjectSystemCPS}/ShadowDesigner/ShadowDesignerCleanup.cs (84%) rename src/VisualStudio/{ProjectPackage => ProjectSystemCPS}/ShadowDesigner/SolutionWiring.cs (97%) create mode 100644 src/VisualStudio/ProjectSystemCPS/ShadowDesigner/XSharpShadowDesignerCommands.cs diff --git a/src/VisualStudio/CodeDomProvider/CodeDomProvider.csproj b/src/VisualStudio/CodeDomProvider/CodeDomProvider.csproj index 16fd540120..75497b660c 100644 --- a/src/VisualStudio/CodeDomProvider/CodeDomProvider.csproj +++ b/src/VisualStudio/CodeDomProvider/CodeDomProvider.csproj @@ -65,6 +65,7 @@ Component + diff --git a/src/VisualStudio/ProjectPackage/CodeDomProvider/XSharpCodeDomHelper.cs b/src/VisualStudio/CodeDomProvider/XSharpCodeDomHelper.cs similarity index 96% rename from src/VisualStudio/ProjectPackage/CodeDomProvider/XSharpCodeDomHelper.cs rename to src/VisualStudio/CodeDomProvider/XSharpCodeDomHelper.cs index f82a805830..8896d26f88 100644 --- a/src/VisualStudio/ProjectPackage/CodeDomProvider/XSharpCodeDomHelper.cs +++ b/src/VisualStudio/CodeDomProvider/XSharpCodeDomHelper.cs @@ -18,7 +18,7 @@ public class XSharpCodeDomHelper /// /// /// - internal static XMergedCodeCompileUnit MergeCodeCompileUnit( XCodeCompileUnit formCompileUnit, XCodeCompileUnit designerCompileUnit) + public static XMergedCodeCompileUnit MergeCodeCompileUnit( XCodeCompileUnit formCompileUnit, XCodeCompileUnit designerCompileUnit) { // Create the merged CodeCompileUnit var mergedCodeCompileUnit = new XMergedCodeCompileUnit(formCompileUnit); @@ -73,7 +73,7 @@ internal static XMergedCodeCompileUnit MergeCodeCompileUnit( XCodeCompileUnit fo } - internal static CodeTypeDeclaration FindDesignerClass(CodeCompileUnit ccu, CodeTypeDeclaration masterClass) + public static CodeTypeDeclaration FindDesignerClass(CodeCompileUnit ccu, CodeTypeDeclaration masterClass) { return FindDesignerClass(ccu, out _, masterClass.Name); } @@ -83,12 +83,12 @@ internal static CodeTypeDeclaration FindDesignerClass(CodeCompileUnit ccu, CodeT /// /// /// - internal static CodeTypeDeclaration FindDesignerClass(CodeCompileUnit ccu) + public static CodeTypeDeclaration FindDesignerClass(CodeCompileUnit ccu) { return FindDesignerClass(ccu, out _); } - internal static CodeTypeDeclaration FindDesignerClass(CodeCompileUnit ccu, out CodeNamespace namespaceName, string name = "") + public static CodeTypeDeclaration FindDesignerClass(CodeCompileUnit ccu, out CodeNamespace namespaceName, string name = "") { namespaceName = null; // We search the first Class that has a Candidate for InitializeComponent diff --git a/src/VisualStudio/ProjectPackage/Commands/CommandSyncDesignerChanges.cs b/src/VisualStudio/ProjectPackage/Commands/CommandSyncDesignerChanges.cs index e03c6133ba..57d0166c90 100644 --- a/src/VisualStudio/ProjectPackage/Commands/CommandSyncDesignerChanges.cs +++ b/src/VisualStudio/ProjectPackage/Commands/CommandSyncDesignerChanges.cs @@ -11,7 +11,7 @@ using System.Linq; using System.Threading.Tasks; -using XSharp.Project.ShadowDesigner; +using XSharp.ProjectSystem.ShadowDesigner; using XSharpModel; namespace XSharp.Project @@ -26,12 +26,14 @@ namespace XSharp.Project [Command(PackageIds.idSyncDesignerChanges)] internal sealed class CommandSyncDesignerChanges : BaseCommand { - private XSharpFileNode _currentFile; + private string _currentPath; + private XProject _currentProject; protected override void BeforeQueryStatus(EventArgs e) { base.BeforeQueryStatus(e); - _currentFile = null; + _currentPath = null; + _currentProject = null; ThreadHelper.JoinableTaskFactory.Run(CheckAvailabilityAsync); } @@ -43,14 +45,13 @@ private async Task CheckAvailabilityAsync() { if (item is PhysicalFile file) { - var project = await VS.Solutions.GetActiveProjectAsync(); - var xproject = project != null ? XSolution.FindProject(project.FullPath, "") : null; - if (xproject?.ProjectNode is XSharpProjectNode prjNode && - prjNode.FindChild(file.FullPath) is XSharpFileNode fileNode && - fileNode.HasDesigner && - prjNode is XSharpSdkProjectNode) + // SDK-style projects: loaded by CPS (default) or by the MPFproj SDK project node + var xproject = XSolution.FindFile(file.FullPath)?.Project; + bool sdkProject = ShadowDesignerBridge.IsCpsProject(xproject) || xproject?.ProjectNode is XSharpSdkProjectNode; + if (sdkProject && ShadowDesignerBridge.HasDesignerFile(file.FullPath)) { - _currentFile = fileNode; + _currentPath = file.FullPath; + _currentProject = xproject; visible = true; } } @@ -62,11 +63,11 @@ private async Task CheckAvailabilityAsync() protected override async Task ExecuteAsync(OleMenuCmdEventArgs e) { await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); - if (_currentFile == null) return; + if (_currentPath == null) return; await VS.Commands.ExecuteAsync(KnownCommands.File_SaveAll); - if (!ShadowDesignerBridge.TryResolveCompanionPaths(_currentFile, out var location, out string error)) + if (!ShadowDesignerBridge.TryResolveCompanionPaths(_currentPath, _currentProject, out var location, out string error)) { await VS.MessageBox.ShowErrorAsync("X# WinForms Designer", error); return; diff --git a/src/VisualStudio/ProjectPackage/Commands/CommandSyncEventHandlers.cs b/src/VisualStudio/ProjectPackage/Commands/CommandSyncEventHandlers.cs index da28fd8c2d..ef6cf2913a 100644 --- a/src/VisualStudio/ProjectPackage/Commands/CommandSyncEventHandlers.cs +++ b/src/VisualStudio/ProjectPackage/Commands/CommandSyncEventHandlers.cs @@ -10,7 +10,7 @@ using System; using System.Threading.Tasks; -using XSharp.Project.ShadowDesigner; +using XSharp.ProjectSystem.ShadowDesigner; using XSharpModel; namespace XSharp.Project @@ -25,12 +25,14 @@ namespace XSharp.Project [Command(PackageIds.idSyncEventHandlers)] internal sealed class CommandSyncEventHandlers : BaseCommand { - private XSharpFileNode _currentFile; + private string _currentPath; + private XProject _currentProject; protected override void BeforeQueryStatus(EventArgs e) { base.BeforeQueryStatus(e); - _currentFile = null; + _currentPath = null; + _currentProject = null; ThreadHelper.JoinableTaskFactory.Run(CheckAvailabilityAsync); } @@ -42,14 +44,13 @@ private async Task CheckAvailabilityAsync() { if (item is PhysicalFile file) { - var project = await VS.Solutions.GetActiveProjectAsync(); - var xproject = project != null ? XSolution.FindProject(project.FullPath, "") : null; - if (xproject?.ProjectNode is XSharpProjectNode prjNode && - prjNode.FindChild(file.FullPath) is XSharpFileNode fileNode && - fileNode.HasDesigner && - prjNode is XSharpSdkProjectNode) + // SDK-style projects: loaded by CPS (default) or by the MPFproj SDK project node + var xproject = XSolution.FindFile(file.FullPath)?.Project; + bool sdkProject = ShadowDesignerBridge.IsCpsProject(xproject) || xproject?.ProjectNode is XSharpSdkProjectNode; + if (sdkProject && ShadowDesignerBridge.HasDesignerFile(file.FullPath)) { - _currentFile = fileNode; + _currentPath = file.FullPath; + _currentProject = xproject; visible = true; } } @@ -61,13 +62,13 @@ private async Task CheckAvailabilityAsync() protected override async Task ExecuteAsync(OleMenuCmdEventArgs e) { await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); - if (_currentFile == null) return; + if (_currentPath == null) return; // The Designer-added stub/wiring can sit unsaved in an open document buffer -- // both sync steps read the companion files from disk, not the live buffer. await VS.Commands.ExecuteAsync(KnownCommands.File_SaveAll); - if (!ShadowDesignerBridge.TryResolveCompanionPaths(_currentFile, out var location, out string error)) + if (!ShadowDesignerBridge.TryResolveCompanionPaths(_currentPath, _currentProject, out var location, out string error)) { await VS.MessageBox.ShowErrorAsync("X# WinForms Designer", error); return; diff --git a/src/VisualStudio/ProjectPackage/Nodes/XSharpFileNode.cs b/src/VisualStudio/ProjectPackage/Nodes/XSharpFileNode.cs index 924f2aa4b8..1580a87588 100644 --- a/src/VisualStudio/ProjectPackage/Nodes/XSharpFileNode.cs +++ b/src/VisualStudio/ProjectPackage/Nodes/XSharpFileNode.cs @@ -983,7 +983,9 @@ private bool TryRedirectToShadowDesigner() { return false; } - if (XSharp.Project.ShadowDesigner.ShadowDesignerBridge.TryOpen(this, out string shadowError)) + var projectNode = (XSharpProjectNode)this.ProjectMgr; + if (XSharp.ProjectSystem.ShadowDesigner.ShadowDesignerBridge.TryOpen(this.Url, projectNode.ProjectModel, + () => projectNode.ForceRefreshReferences(), out string shadowError)) { return true; } diff --git a/src/VisualStudio/ProjectPackage/ProjectPackage.csproj b/src/VisualStudio/ProjectPackage/ProjectPackage.csproj index 8676a93b83..c6cd3164db 100644 --- a/src/VisualStudio/ProjectPackage/ProjectPackage.csproj +++ b/src/VisualStudio/ProjectPackage/ProjectPackage.csproj @@ -118,7 +118,6 @@ - diff --git a/src/VisualStudio/ProjectPackage/ProjectPackage2022.csproj b/src/VisualStudio/ProjectPackage/ProjectPackage2022.csproj index d0b556d41e..3e68c63298 100644 --- a/src/VisualStudio/ProjectPackage/ProjectPackage2022.csproj +++ b/src/VisualStudio/ProjectPackage/ProjectPackage2022.csproj @@ -118,7 +118,6 @@ - @@ -477,14 +476,6 @@ - - - - - - - - diff --git a/src/VisualStudio/ProjectSystemCPS/ProjectSystemCPS.csproj b/src/VisualStudio/ProjectSystemCPS/ProjectSystemCPS.csproj index efd1d0e922..2ee96111ee 100644 --- a/src/VisualStudio/ProjectSystemCPS/ProjectSystemCPS.csproj +++ b/src/VisualStudio/ProjectSystemCPS/ProjectSystemCPS.csproj @@ -1,4 +1,4 @@ - + + runtime; build; native; contentfiles; analyzers; buildtransitive all @@ -64,6 +66,8 @@ + + diff --git a/src/VisualStudio/ProjectPackage/ShadowDesigner/CompanionProjectWriter.cs b/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/CompanionProjectWriter.cs similarity index 95% rename from src/VisualStudio/ProjectPackage/ShadowDesigner/CompanionProjectWriter.cs rename to src/VisualStudio/ProjectSystemCPS/ShadowDesigner/CompanionProjectWriter.cs index 29c25cc153..9b85d0d06a 100644 --- a/src/VisualStudio/ProjectPackage/ShadowDesigner/CompanionProjectWriter.cs +++ b/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/CompanionProjectWriter.cs @@ -3,7 +3,9 @@ // Licensed under the Apache License, Version 2.0. // See License.txt in the project root for license information. // -using Microsoft.VisualStudio.Project; +// Moved from ProjectPackage, which suppresses VSTHRD010 for the whole project: the UI thread +// requirements of this code are handled explicitly (ThreadHelper) and were not rewritten. +#pragma warning disable VSTHRD010 using System; using System.Collections.Generic; @@ -12,7 +14,7 @@ using System.Text; using System.Xml.Linq; -namespace XSharp.Project.ShadowDesigner +namespace XSharp.ProjectSystem.ShadowDesigner { /// /// Generates/refreshes a plain C# companion project (sibling to the real .xsproj's @@ -204,12 +206,12 @@ internal static List ReadPackageReferences(string xsprojPath) { var doc = XDocument.Load(xsprojPath); var result = new List(); - foreach (var el in doc.Descendants(ProjectFileConstants.PackageReference)) + foreach (var el in doc.Descendants("PackageReference")) { - string include = el.Attribute(ProjectFileConstants.Include)?.Value; + string include = el.Attribute("Include")?.Value; if (string.IsNullOrEmpty(include)) continue; - string version = el.Attribute(ProjectFileConstants.Version)?.Value - ?? el.Element(ProjectFileConstants.Version)?.Value; + string version = el.Attribute("Version")?.Value + ?? el.Element("Version")?.Value; result.Add(new PackageRef(include, version)); } return result; diff --git a/src/VisualStudio/ProjectPackage/ShadowDesigner/CompanionSaveWatcher.cs b/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/CompanionSaveWatcher.cs similarity index 93% rename from src/VisualStudio/ProjectPackage/ShadowDesigner/CompanionSaveWatcher.cs rename to src/VisualStudio/ProjectSystemCPS/ShadowDesigner/CompanionSaveWatcher.cs index e2ecb91a21..3e03215f11 100644 --- a/src/VisualStudio/ProjectPackage/ShadowDesigner/CompanionSaveWatcher.cs +++ b/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/CompanionSaveWatcher.cs @@ -3,6 +3,9 @@ // Licensed under the Apache License, Version 2.0. // See License.txt in the project root for license information. // +// Moved from ProjectPackage, which suppresses VSTHRD010 for the whole project: the UI thread +// requirements of this code are handled explicitly (ThreadHelper) and were not rewritten. +#pragma warning disable VSTHRD010 using System; using System.Collections.Generic; using System.IO; @@ -14,14 +17,12 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.VisualStudio; -using Microsoft.VisualStudio.Project; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; using Microsoft.VisualStudio.TextManager.Interop; using Microsoft.VisualStudio.Threading; -using Logger = XSharp.Project.Logger; -namespace XSharp.Project.ShadowDesigner +namespace XSharp.ProjectSystem.ShadowDesigner { /// /// Automatically runs the two manual sync commands (EventHandlerSync then @@ -68,7 +69,7 @@ private CompanionSaveWatcher(IVsRunningDocumentTable rdt, DTE2 dte) /// ShadowDesignerBridge.TryOpen succeeds -- idempotent (overwrites any existing /// entry for the same paths). /// - public static void Watch(ProjectNode projectMgr, ShadowDesignerBridge.CompanionLocation location) + public static void Watch(IServiceProvider services, ShadowDesignerBridge.CompanionLocation location) { ThreadHelper.ThrowIfNotOnUIThread(); if (_instance == null) @@ -77,8 +78,8 @@ public static void Watch(ProjectNode projectMgr, ShadowDesignerBridge.CompanionL { if (_instance == null) { - var rdt = projectMgr.GetService(typeof(SVsRunningDocumentTable)) as IVsRunningDocumentTable; - var dte = projectMgr.GetService(typeof(SDTE)) as DTE2; + var rdt = services.GetService(typeof(SVsRunningDocumentTable)) as IVsRunningDocumentTable; + var dte = services.GetService(typeof(SDTE)) as DTE2; if (rdt == null || dte == null) { return; @@ -218,12 +219,14 @@ public int OnBeforeDocumentWindowShow(uint docCookie, int fFirstShow, IVsWindowF // above is unaffected. // // Deliberately fire-and-forget (this call must return now so the frame can - // finish showing) -- uses the package's own JoinableTaskFactory rather than the - // static ThreadHelper one so VS can still track/join the pending operation. - XSharpProjectPackage.XInstance.JoinableTaskFactory.RunAsync(async () => + // finish showing) -- ThreadHelper.JoinableTaskFactory lets VS track/join the pending operation. + // VSSDK007 wants a package JoinableTaskFactory; this static, package independent code uses + // ThreadHelper and hands the task to FileAndForget (fault reporting) instead. +#pragma warning disable VSSDK007 + ThreadHelper.JoinableTaskFactory.RunAsync(async () => { await Task.Yield(); - await XSharpProjectPackage.XInstance.JoinableTaskFactory.SwitchToMainThreadAsync(); + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); try { string handlerName = TryGetCaretMethodName(pFrame, path); @@ -237,7 +240,8 @@ public int OnBeforeDocumentWindowShow(uint docCookie, int fFirstShow, IVsWindowF { _syncing = false; } - }).Task.FileAndForget("XSharp/ShadowDesigner/ExistingHandlerRedirect"); + }).FileAndForget("XSharp/ShadowDesigner/ExistingHandlerRedirect"); +#pragma warning restore VSSDK007 return VSConstants.S_OK; } diff --git a/src/VisualStudio/ProjectPackage/ShadowDesigner/DesignerChangesSync.cs b/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/DesignerChangesSync.cs similarity index 95% rename from src/VisualStudio/ProjectPackage/ShadowDesigner/DesignerChangesSync.cs rename to src/VisualStudio/ProjectSystemCPS/ShadowDesigner/DesignerChangesSync.cs index 64713de4b8..e7f9dd444f 100644 --- a/src/VisualStudio/ProjectPackage/ShadowDesigner/DesignerChangesSync.cs +++ b/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/DesignerChangesSync.cs @@ -3,6 +3,9 @@ // Licensed under the Apache License, Version 2.0. // See License.txt in the project root for license information. // +// Moved from ProjectPackage, which suppresses VSTHRD010 for the whole project: the UI thread +// requirements of this code are handled explicitly (ThreadHelper) and were not rewritten. +#pragma warning disable VSTHRD010 using System; using System.CodeDom; using System.CodeDom.Compiler; @@ -13,7 +16,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using XSharp.CodeDom; -namespace XSharp.Project.ShadowDesigner +namespace XSharp.ProjectSystem.ShadowDesigner { /// /// "Designer -> Code, beyond handler creation" (general property edits, control @@ -31,7 +34,7 @@ namespace XSharp.Project.ShadowDesigner /// wiring (+=) lines into Form1.Designer.prg, via targeted regex insertion rather than /// a full regenerate. Run event sync before this one when both apply in the same pass. /// - internal static class DesignerChangesSync + public static class DesignerChangesSync { public sealed class SyncResult { diff --git a/src/VisualStudio/ProjectPackage/ShadowDesigner/EventHandlerSync.cs b/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/EventHandlerSync.cs similarity index 97% rename from src/VisualStudio/ProjectPackage/ShadowDesigner/EventHandlerSync.cs rename to src/VisualStudio/ProjectSystemCPS/ShadowDesigner/EventHandlerSync.cs index 1d1b73d098..60c257125b 100644 --- a/src/VisualStudio/ProjectPackage/ShadowDesigner/EventHandlerSync.cs +++ b/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/EventHandlerSync.cs @@ -3,6 +3,9 @@ // Licensed under the Apache License, Version 2.0. // See License.txt in the project root for license information. // +// Moved from ProjectPackage, which suppresses VSTHRD010 for the whole project: the UI thread +// requirements of this code are handled explicitly (ThreadHelper) and were not rewritten. +#pragma warning disable VSTHRD010 using System; using System.Collections.Generic; using System.IO; @@ -12,7 +15,7 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; -namespace XSharp.Project.ShadowDesigner +namespace XSharp.ProjectSystem.ShadowDesigner { /// /// Detects event-handler stubs and wiring statements the out-of-process Designer added @@ -22,7 +25,7 @@ namespace XSharp.Project.ShadowDesigner /// since Microsoft.CSharp.CSharpCodeProvider.Parse() doesn't implement C# parsing at all /// (CodeDom only ever supported generation, not parsing, for this provider). /// - internal static class EventHandlerSync + public static class EventHandlerSync { public sealed class SyncResult { diff --git a/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/Logger.cs b/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/Logger.cs new file mode 100644 index 0000000000..5a9c3f9384 --- /dev/null +++ b/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/Logger.cs @@ -0,0 +1,20 @@ +// +// Copyright (c) XSharp B.V. All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +// +using System; +using XSharp.Settings; + +namespace XSharp.ProjectSystem.ShadowDesigner +{ + /// + /// Writes to the X# log (XSettings.Logger), like XSharp.Project.Logger in the X# project package. + /// + internal static class Logger + { + internal static void Exception(Exception e, string msg) => XSettings.Logger.Exception(e, msg); + internal static void Information(string msg) => XSettings.Logger.Information(msg); + internal static void Error(string msg) => XSettings.Logger.Error(msg); + } +} diff --git a/src/VisualStudio/ProjectPackage/ShadowDesigner/RoslynToCodeDom.cs b/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/RoslynToCodeDom.cs similarity index 98% rename from src/VisualStudio/ProjectPackage/ShadowDesigner/RoslynToCodeDom.cs rename to src/VisualStudio/ProjectSystemCPS/ShadowDesigner/RoslynToCodeDom.cs index ad96f78aeb..b10e95aad6 100644 --- a/src/VisualStudio/ProjectPackage/ShadowDesigner/RoslynToCodeDom.cs +++ b/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/RoslynToCodeDom.cs @@ -1,3 +1,6 @@ +// Moved from ProjectPackage, which suppresses VSTHRD010 for the whole project: the UI thread +// requirements of this code are handled explicitly (ThreadHelper) and were not rewritten. +#pragma warning disable VSTHRD010 using System; using System.CodeDom; using System.Collections.Generic; @@ -6,7 +9,7 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; -namespace XSharp.Project.ShadowDesigner +namespace XSharp.ProjectSystem.ShadowDesigner { /// /// Translates the statement shapes the out-of-process WinForms Designer emits into the diff --git a/src/VisualStudio/ProjectPackage/ShadowDesigner/ShadowDesignerBridge.cs b/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/ShadowDesignerBridge.cs similarity index 83% rename from src/VisualStudio/ProjectPackage/ShadowDesigner/ShadowDesignerBridge.cs rename to src/VisualStudio/ProjectSystemCPS/ShadowDesigner/ShadowDesignerBridge.cs index 91164882df..994be26854 100644 --- a/src/VisualStudio/ProjectPackage/ShadowDesigner/ShadowDesignerBridge.cs +++ b/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/ShadowDesignerBridge.cs @@ -3,13 +3,15 @@ // Licensed under the Apache License, Version 2.0. // See License.txt in the project root for license information. // +// Moved from ProjectPackage, which suppresses VSTHRD010 for the whole project: the UI thread +// requirements of this code are handled explicitly (ThreadHelper) and were not rewritten. +#pragma warning disable VSTHRD010 using System; using System.CodeDom; using System.CodeDom.Compiler; using System.Collections.Generic; using System.IO; using System.Linq; -using Community.VisualStudio.Toolkit; using EnvDTE80; using Microsoft.CSharp; using Microsoft.VisualStudio.Shell; @@ -17,9 +19,8 @@ using XSharp.CodeDom; using XSharp.Settings; using XSharpModel; -using Logger = XSharp.Project.Logger; -namespace XSharp.Project.ShadowDesigner +namespace XSharp.ProjectSystem.ShadowDesigner { /// /// Entry point for the "View Designer on a SDK-style .prg" bridge. VS's out-of-process @@ -31,17 +32,42 @@ namespace XSharp.Project.ShadowDesigner /// Designer view (whose owning IVsHierarchy is a real C# project, so the out-of-process /// Designer's project-type gate passes). /// - /// This code lives inside X#'s own ProjectSystem, so XSharpCodeParser/ - /// XSharpCodeDomHelper/XProject are called directly -- no reflection is needed to reach - /// them. + /// XSharpCodeParser/XSharpCodeDomHelper/XProject are called directly -- no reflection is + /// needed to reach them. The bridge only needs the path of the .prg and the X# code model + /// (XProject) of its project, so it serves both project systems: the CPS project system + /// (XSharpShadowDesignerDefaultActionCommand, XSharpShadowDesignerViewFormCommand) and the MPFproj SDK project node (XSharpFileNode). /// /// Also exposes , used by /// and to locate an /// already-open companion project's files deterministically, recomputed fresh each time /// from the real .prg's own class name. /// - internal static class ShadowDesignerBridge + public static class ShadowDesignerBridge { + /// + /// True when is loaded by the CPS project system (its IXSharpProject is the + /// XSharpProjectAdapter). Lets the X# project package decide without knowing the CPS types. + /// + public static bool IsCpsProject(XProject project) => project?.ProjectNode is LanguageService.XSharpProjectAdapter; + + /// + /// True when is a .prg with a matching .Designer.prg next to it -- the signal + /// for a form/user control in SDK-style projects, which rarely have an explicit SubType. + /// + public static bool HasDesignerFile(string prgPath) + { + if (string.IsNullOrEmpty(prgPath) || + !string.Equals(Path.GetExtension(prgPath), ".prg", StringComparison.OrdinalIgnoreCase) || + prgPath.EndsWith(".designer.prg", StringComparison.OrdinalIgnoreCase)) + { + return false; + } + string designerPrg = XSharpCodeDomHelper.BuildDesignerFileName(prgPath); + return !string.IsNullOrEmpty(designerPrg) && + !string.Equals(designerPrg, prgPath, StringComparison.OrdinalIgnoreCase) && + File.Exists(designerPrg); + } + private static readonly string[] SharedFrameworkMarkers = { @"\dotnet\shared\", @@ -52,31 +78,27 @@ internal static class ShadowDesignerBridge }; /// - /// Attempts to open the shadow Designer for (expected to - /// be a SDK-style project's .prg file with HasDesigner true). Returns false with an + /// Attempts to open the shadow Designer for (expected to + /// be a SDK-style project's .prg file with a matching .Designer.prg), using the code model + /// of its project. is called + /// after a build that was needed to resolve the references (MPFproj reads them from the + /// response file); CPS projects pass null, their references come from the design-time build. Returns false with an /// error message on failure -- callers should fall back to whatever they'd otherwise /// have done (e.g. today's "does not support project" Designer error) rather than /// throwing, since a partially-set-up solution (mid-restore, no build yet) is a /// normal, recoverable condition, not a bug. /// - public static bool TryOpen(XSharpFileNode fileNode, out string error) + public static bool TryOpen(string mainPrgPath, XProject xProject, Action refreshReferences, out string error) { + ThreadHelper.ThrowIfNotOnUIThread(); try { - var projectNode = fileNode.ProjectMgr as XSharpProjectNode; - if (projectNode == null) - { - error = "Could not resolve the owning X# project."; - return false; - } - XProject xProject = projectNode.ProjectModel; if (xProject == null) { error = "The project model is not available yet."; return false; } - string mainPrgPath = fileNode.Url; string designerPrgPath = XSharpCodeDomHelper.BuildDesignerFileName(mainPrgPath); if (string.IsNullOrEmpty(designerPrgPath) || !File.Exists(designerPrgPath)) { @@ -84,7 +106,7 @@ public static bool TryOpen(XSharpFileNode fileNode, out string error) return false; } - var dte = fileNode.ProjectMgr.GetService(typeof(SDTE)) as DTE2; + var dte = ServiceProvider.GlobalProvider.GetService(typeof(SDTE)) as DTE2; if (dte == null) { error = "Could not obtain the DTE service."; @@ -110,7 +132,7 @@ public static bool TryOpen(XSharpFileNode fileNode, out string error) // from XSharpIDEBuildLogger's MSBuild logger callback) has already // completed by the time the synchronous BuildProject(...) call above // returned -- force a synchronous re-read right now instead. - projectNode.ForceRefreshReferences(); + refreshReferences?.Invoke(); } XCodeCompileUnit mainUnit = ToXCodeCompileUnit(ParseFile(xProject, mainPrgPath, null)); @@ -141,7 +163,7 @@ public static bool TryOpen(XSharpFileNode fileNode, out string error) var companion = CompanionProjectWriter.EnsureCompanionProject( xProject.FileName, referencePaths, shadowCSharp, namespaceName, className); - CompanionSaveWatcher.Watch(fileNode.ProjectMgr, new CompanionLocation + CompanionSaveWatcher.Watch(ServiceProvider.GlobalProvider, new CompanionLocation { MainPrgPath = mainPrgPath, DesignerPrgPath = designerPrgPath, @@ -182,27 +204,24 @@ public sealed class CompanionLocation } /// - /// Locates an already-open companion project's files for + /// Locates an already-open companion project's files for /// deterministically -- only a lightweight parse of the main .prg (to get the class /// name), no merge/generate/write. Does NOT create the companion project if it /// doesn't exist yet (callers should tell the user to run "Open Shadow Designer" /// first in that case, distinguishable via the companion .csproj not existing on /// disk). /// - public static bool TryResolveCompanionPaths(XSharpFileNode fileNode, out CompanionLocation location, out string error) + public static bool TryResolveCompanionPaths(string mainPrgPath, XProject xProject, out CompanionLocation location, out string error) { location = null; try { - var projectNode = fileNode.ProjectMgr as XSharpProjectNode; - XProject xProject = projectNode?.ProjectModel; if (xProject == null) { error = "Could not resolve the owning X# project."; return false; } - string mainPrgPath = fileNode.Url; string designerPrgPath = XSharpCodeDomHelper.BuildDesignerFileName(mainPrgPath); if (string.IsNullOrEmpty(designerPrgPath) || !File.Exists(designerPrgPath)) { @@ -267,13 +286,12 @@ private static bool EnsureBuilt(DTE2 dte, XProject xProject, out string error) bool proceed = XCustomEditorSettings.AutoBuildForShadowDesigner; if (!proceed) { - ThreadHelper.JoinableTaskFactory.Run(async () => - { - proceed = await VS.MessageBox.ShowConfirmAsync( - "X# WinForms Designer", - "This project needs to be built at least once so X# can resolve its " + - "assembly references for the Designer.\n\nBuild now?"); - }); + proceed = VsShellUtilities.ShowMessageBox(ServiceProvider.GlobalProvider, + "This project needs to be built at least once so X# can resolve its " + + "assembly references for the Designer.\n\nBuild now?", + "X# WinForms Designer", + OLEMSGICON.OLEMSGICON_QUERY, OLEMSGBUTTON.OLEMSGBUTTON_OKCANCEL, + OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST) == 1; // IDOK } if (!proceed) { diff --git a/src/VisualStudio/ProjectPackage/ShadowDesigner/ShadowDesignerCleanup.cs b/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/ShadowDesignerCleanup.cs similarity index 84% rename from src/VisualStudio/ProjectPackage/ShadowDesigner/ShadowDesignerCleanup.cs rename to src/VisualStudio/ProjectSystemCPS/ShadowDesigner/ShadowDesignerCleanup.cs index 761f47ef39..598ad84c09 100644 --- a/src/VisualStudio/ProjectPackage/ShadowDesigner/ShadowDesignerCleanup.cs +++ b/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/ShadowDesignerCleanup.cs @@ -3,16 +3,17 @@ // Licensed under the Apache License, Version 2.0. // See License.txt in the project root for license information. // +// Moved from ProjectPackage, which suppresses VSTHRD010 for the whole project: the UI thread +// requirements of this code are handled explicitly (ThreadHelper) and were not rewritten. +#pragma warning disable VSTHRD010 using System; using System.Collections.Generic; using System.IO; using Microsoft.VisualStudio; -using Microsoft.VisualStudio.Project; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; -using Logger = XSharp.Project.Logger; -namespace XSharp.Project.ShadowDesigner +namespace XSharp.ProjectSystem.ShadowDesigner { /// /// Removes each shadow companion project from the solution and deletes its folder when @@ -38,7 +39,7 @@ namespace XSharp.Project.ShadowDesigner /// fully unloaded, so there's no risk of deleting files still locked by an active /// project node. /// - internal sealed class ShadowDesignerCleanup : SolutionListener + internal sealed class ShadowDesignerCleanup : IVsSolutionEvents { private static readonly object _lock = new object(); private static ShadowDesignerCleanup _instance; @@ -49,16 +50,18 @@ internal sealed class ShadowDesignerCleanup : SolutionListener new HashSet(StringComparer.OrdinalIgnoreCase); private readonly IVsSolution _vsSolution; + private uint _eventsCookie; - private ShadowDesignerCleanup(IServiceProvider serviceProvider) : base(serviceProvider) + private ShadowDesignerCleanup(IServiceProvider serviceProvider) { + ThreadHelper.ThrowIfNotOnUIThread(); var solutionService = serviceProvider.GetService(typeof(SVsSolution)); if (solutionService == null) { throw new InvalidOperationException("Could not obtain the IVsSolution service."); } _vsSolution = (IVsSolution)solutionService; - Init(); + ErrorHandler.ThrowOnFailure(_vsSolution.AdviseSolutionEvents(this, out _eventsCookie)); } /// @@ -75,19 +78,14 @@ public static void Track(string companionCsprojPath) { if (_instance == null) { - var package = XSharpProjectPackage.XInstance; - if (package == null) - { - return; - } - _instance = new ShadowDesignerCleanup(package); + _instance = new ShadowDesignerCleanup(ServiceProvider.GlobalProvider); } } } _companionCsprojPaths.Add(companionCsprojPath); } - public override int OnQueryCloseSolution(object reserved, ref int cancel) + public int OnQueryCloseSolution(object reserved, ref int cancel) { if (_vsSolution != null) { @@ -143,7 +141,7 @@ private void CloseProjectElement(string csprojPath) } } - public override int OnAfterCloseSolution(object reserved) + public int OnAfterCloseSolution(object reserved) { // Confirmed via diagnostic logging (twice) that the folder is still locked well // beyond a ~1 second bounded retry at the moment this event fires ("The process @@ -199,5 +197,16 @@ private static void DeleteWithRetry(string dir) } } } + + #region IVsSolutionEvents members that are not used + public int OnAfterOpenProject(IVsHierarchy pHierarchy, int fAdded) => VSConstants.S_OK; + public int OnQueryCloseProject(IVsHierarchy pHierarchy, int fRemoving, ref int pfCancel) => VSConstants.S_OK; + public int OnBeforeCloseProject(IVsHierarchy pHierarchy, int fRemoved) => VSConstants.S_OK; + public int OnAfterLoadProject(IVsHierarchy pStubHierarchy, IVsHierarchy pRealHierarchy) => VSConstants.S_OK; + public int OnQueryUnloadProject(IVsHierarchy pRealHierarchy, ref int pfCancel) => VSConstants.S_OK; + public int OnBeforeUnloadProject(IVsHierarchy pRealHierarchy, IVsHierarchy pStubHierarchy) => VSConstants.S_OK; + public int OnAfterOpenSolution(object pUnkReserved, int fNewSolution) => VSConstants.S_OK; + public int OnBeforeCloseSolution(object pUnkReserved) => VSConstants.S_OK; + #endregion } } diff --git a/src/VisualStudio/ProjectPackage/ShadowDesigner/SolutionWiring.cs b/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/SolutionWiring.cs similarity index 97% rename from src/VisualStudio/ProjectPackage/ShadowDesigner/SolutionWiring.cs rename to src/VisualStudio/ProjectSystemCPS/ShadowDesigner/SolutionWiring.cs index 9a84660b2e..8ca4ddd20d 100644 --- a/src/VisualStudio/ProjectPackage/ShadowDesigner/SolutionWiring.cs +++ b/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/SolutionWiring.cs @@ -3,6 +3,9 @@ // Licensed under the Apache License, Version 2.0. // See License.txt in the project root for license information. // +// Moved from ProjectPackage, which suppresses VSTHRD010 for the whole project: the UI thread +// requirements of this code are handled explicitly (ThreadHelper) and were not rewritten. +#pragma warning disable VSTHRD010 using System; using System.Collections.Generic; using EnvDTE; @@ -10,9 +13,8 @@ using Microsoft.VisualStudio; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; -using Logger = XSharp.Project.Logger; -namespace XSharp.Project.ShadowDesigner +namespace XSharp.ProjectSystem.ShadowDesigner { /// /// The only class in this feature that touches EnvDTE/DTE2. Adds the companion project @@ -98,7 +100,7 @@ private static void TryRemoveStaleProjectItem(DTE2 dte, string expectedName) /// private static void RemoveStaleHierarchy(string csprojPath) { - if (!(((IServiceProvider)XSharpProjectPackage.XInstance)?.GetService(typeof(SVsSolution)) is IVsSolution vsSolution)) + if (!(ServiceProvider.GlobalProvider.GetService(typeof(SVsSolution)) is IVsSolution vsSolution)) { return; } diff --git a/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/XSharpShadowDesignerCommands.cs b/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/XSharpShadowDesignerCommands.cs new file mode 100644 index 0000000000..7c7e2256ae --- /dev/null +++ b/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/XSharpShadowDesignerCommands.cs @@ -0,0 +1,128 @@ +// +// Copyright (c) XSharp B.V. All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +// +using System; +using System.Collections.Immutable; +using System.ComponentModel.Composition; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.VisualStudio; +using Microsoft.VisualStudio.ProjectSystem; +using XSharpModel; + +namespace XSharp.ProjectSystem.ShadowDesigner +{ + /// + /// Opens the shadow WinForms designer for X# forms in CPS projects (the CPS counterpart of + /// XSharpFileNode.TryRedirectToShadowDesigner in MPFproj). + /// + /// + /// A .prg is treated as a form when a matching .Designer.prg exists next to it, the same signal the MPFproj + /// SDK project node uses (SDK-style projects rarely have an explicit SubType). When the shadow designer + /// cannot be opened the command is not handled, so CPS falls back to its default action. + /// + internal static class XSharpShadowDesignerCommands + { + public static bool TryGetFormFile(IImmutableSet nodes, out string path) + { + path = null; + if (nodes == null || nodes.Count != 1) + return false; + var node = nodes.First(); + if (node.IsFolder || string.IsNullOrEmpty(node.FilePath)) + return false; + if (!ShadowDesignerBridge.HasDesignerFile(node.FilePath)) + return false; + path = node.FilePath; + return true; + } + + public static async Task TryOpenAsync(IProjectThreadingService threading, string path) + { + await threading.SwitchToUIThread(); + var xProject = XSolution.FindFile(path)?.Project; + if (xProject == null) + { + Logger.Information("XSharp ShadowDesigner: no X# code model for " + path); + return false; + } + if (ShadowDesignerBridge.TryOpen(path, xProject, null, out var error)) + return true; + Logger.Information("XSharp ShadowDesigner: " + error); + return false; + } + + public static Task EnabledStatusAsync(string commandText) => + new CommandStatusResult(true, commandText, CommandStatus.Enabled | CommandStatus.Supported).AsTask(); + } + + /// + /// Double click / Enter on an X# form in Solution Explorer (UIHierarchyWindow commands). + /// + [ExportCommandGroup(UIHierarchyWindowCommandSet)] + [AppliesTo(XSharpCapabilities.XSharpCps)] + [Order(1000)] + internal sealed class XSharpShadowDesignerDefaultActionCommand : IAsyncCommandGroupHandler + { + private const string UIHierarchyWindowCommandSet = "{60481700-078B-11D1-AAF8-00A0C9055A90}"; + private const long DoubleClick = 2; + private const long EnterKey = 3; + + private readonly IProjectThreadingService threading; + + [ImportingConstructor] + public XSharpShadowDesignerDefaultActionCommand(IProjectThreadingService threading) + { + this.threading = threading; + } + + public Task GetCommandStatusAsync(IImmutableSet nodes, long commandId, bool focused, string commandText, CommandStatus progressiveStatus) + { + if ((commandId == DoubleClick || commandId == EnterKey) && XSharpShadowDesignerCommands.TryGetFormFile(nodes, out _)) + return XSharpShadowDesignerCommands.EnabledStatusAsync(commandText); + return CommandStatusResult.Unhandled.AsTask(); + } + + public Task TryHandleCommandAsync(IImmutableSet nodes, long commandId, bool focused, long commandExecuteOptions, IntPtr variantArgIn, IntPtr variantArgOut) + { + if ((commandId == DoubleClick || commandId == EnterKey) && XSharpShadowDesignerCommands.TryGetFormFile(nodes, out var path)) + return XSharpShadowDesignerCommands.TryOpenAsync(threading, path); + return Task.FromResult(false); + } + } + + /// + /// "View Designer" (Shift+F7) on an X# form. + /// + [ExportCommandGroup(VSConstants.CMDSETID.StandardCommandSet97_string)] + [AppliesTo(XSharpCapabilities.XSharpCps)] + [Order(1000)] + internal sealed class XSharpShadowDesignerViewFormCommand : IAsyncCommandGroupHandler + { + private const long ViewForm = (long)VSConstants.VSStd97CmdID.ViewForm; + + private readonly IProjectThreadingService threading; + + [ImportingConstructor] + public XSharpShadowDesignerViewFormCommand(IProjectThreadingService threading) + { + this.threading = threading; + } + + public Task GetCommandStatusAsync(IImmutableSet nodes, long commandId, bool focused, string commandText, CommandStatus progressiveStatus) + { + if (commandId == ViewForm && XSharpShadowDesignerCommands.TryGetFormFile(nodes, out _)) + return XSharpShadowDesignerCommands.EnabledStatusAsync(commandText); + return CommandStatusResult.Unhandled.AsTask(); + } + + public Task TryHandleCommandAsync(IImmutableSet nodes, long commandId, bool focused, long commandExecuteOptions, IntPtr variantArgIn, IntPtr variantArgOut) + { + if (commandId == ViewForm && XSharpShadowDesignerCommands.TryGetFormFile(nodes, out var path)) + return XSharpShadowDesignerCommands.TryOpenAsync(threading, path); + return Task.FromResult(false); + } + } +} From 8d8cd6587a81f0bc2e5acdeda090b38e3dd136ca Mon Sep 17 00:00:00 2001 From: Hansjoerg Petriffer Date: Sun, 27 Sep 2026 11:37:06 +0200 Subject: [PATCH 15/60] [VsIntegration] CPS test bed: SDK-style WinForms project for the shadow designer WinForms/WinFormsTestBed.xsproj (net8.0-windows, UseWindowsForms, Core dialect, from the CoreWinFormApp template) with Form1.prg/Form1.designer.prg, added to SelectorTestBed.sln with the classic X# project type GUID, so it is loaded by CPS through the project selector. Double click or View Designer on Form1.prg must open the shadow WinForms designer. CpsTestBed.xsproj excludes the WinForms subfolder from its globs. Co-Authored-By: Claude Opus 5.5 --- .../CpsTestBed.xsproj | 4 ++- .../SelectorTestBed.sln | 6 ++++ .../WinForms/Form1.designer.prg | 36 +++++++++++++++++++ .../WinForms/Form1.prg | 13 +++++++ .../WinForms/Program.prg | 10 ++++++ .../WinForms/WinFormsTestBed.xsproj | 16 +++++++++ 6 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 src/VisualStudio/ProjectSystemCPS.TestBed/WinForms/Form1.designer.prg create mode 100644 src/VisualStudio/ProjectSystemCPS.TestBed/WinForms/Form1.prg create mode 100644 src/VisualStudio/ProjectSystemCPS.TestBed/WinForms/Program.prg create mode 100644 src/VisualStudio/ProjectSystemCPS.TestBed/WinForms/WinFormsTestBed.xsproj diff --git a/src/VisualStudio/ProjectSystemCPS.TestBed/CpsTestBed.xsproj b/src/VisualStudio/ProjectSystemCPS.TestBed/CpsTestBed.xsproj index 68d9d07fa1..8314ab1fbb 100644 --- a/src/VisualStudio/ProjectSystemCPS.TestBed/CpsTestBed.xsproj +++ b/src/VisualStudio/ProjectSystemCPS.TestBed/CpsTestBed.xsproj @@ -11,8 +11,10 @@ CpsTestBed - + + + \ No newline at end of file diff --git a/src/VisualStudio/ProjectSystemCPS.TestBed/SelectorTestBed.sln b/src/VisualStudio/ProjectSystemCPS.TestBed/SelectorTestBed.sln index e2785c9ca3..0f89ae0ee9 100644 --- a/src/VisualStudio/ProjectSystemCPS.TestBed/SelectorTestBed.sln +++ b/src/VisualStudio/ProjectSystemCPS.TestBed/SelectorTestBed.sln @@ -6,6 +6,8 @@ Project("{AA6C8D78-22FF-423A-9C7C-5F2393824E04}") = "CpsTestBed", "CpsTestBed.xs EndProject Project("{AA6C8D78-22FF-423A-9C7C-5F2393824E04}") = "LegacyTestBed", "Legacy\LegacyTestBed.xsproj", "{9DAD9E60-2617-45A2-ADB3-98DD2AFD9EF3}" EndProject +Project("{AA6C8D78-22FF-423A-9C7C-5F2393824E04}") = "WinFormsTestBed", "WinForms\WinFormsTestBed.xsproj", "{6804AF7F-4D24-45EE-B0B3-6CD0758A7641}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -20,6 +22,10 @@ Global {9DAD9E60-2617-45A2-ADB3-98DD2AFD9EF3}.Debug|Any CPU.Build.0 = Debug|Any CPU {9DAD9E60-2617-45A2-ADB3-98DD2AFD9EF3}.Release|Any CPU.ActiveCfg = Release|Any CPU {9DAD9E60-2617-45A2-ADB3-98DD2AFD9EF3}.Release|Any CPU.Build.0 = Release|Any CPU + {6804AF7F-4D24-45EE-B0B3-6CD0758A7641}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6804AF7F-4D24-45EE-B0B3-6CD0758A7641}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6804AF7F-4D24-45EE-B0B3-6CD0758A7641}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6804AF7F-4D24-45EE-B0B3-6CD0758A7641}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/VisualStudio/ProjectSystemCPS.TestBed/WinForms/Form1.designer.prg b/src/VisualStudio/ProjectSystemCPS.TestBed/WinForms/Form1.designer.prg new file mode 100644 index 0000000000..456dc884f3 --- /dev/null +++ b/src/VisualStudio/ProjectSystemCPS.TestBed/WinForms/Form1.designer.prg @@ -0,0 +1,36 @@ +BEGIN NAMESPACE WinFormsTestBed + + PARTIAL CLASS Form1 + + PRIVATE components := NULL AS System.ComponentModel.IContainer + PRIVATE button1 AS System.Windows.Forms.Button + + PROTECTED OVERRIDE METHOD Dispose(disposing AS LOGIC) AS VOID STRICT + IF (disposing .AND. (components != NULL)) + components:Dispose() + ENDIF + SUPER:Dispose(disposing) + RETURN + END METHOD + + #region Windows Form Designer generated code + PRIVATE METHOD InitializeComponent() AS VOID STRICT + SELF:button1 := System.Windows.Forms.Button{} + SELF:SuspendLayout() + SELF:button1:Location := System.Drawing.Point{12, 12} + SELF:button1:Name := "button1" + SELF:button1:Size := System.Drawing.Size{120, 30} + SELF:button1:TabIndex := 0 + SELF:button1:Text := "Hello" + SELF:AutoScaleMode := System.Windows.Forms.AutoScaleMode.Font + SELF:ClientSize := System.Drawing.Size{300, 160} + SELF:Controls:Add(SELF:button1) + SELF:Name := "Form1" + SELF:Text := "X# CPS WinForms" + SELF:ResumeLayout(FALSE) + RETURN + END METHOD + #endregion + END CLASS + +END NAMESPACE diff --git a/src/VisualStudio/ProjectSystemCPS.TestBed/WinForms/Form1.prg b/src/VisualStudio/ProjectSystemCPS.TestBed/WinForms/Form1.prg new file mode 100644 index 0000000000..b3f69dd891 --- /dev/null +++ b/src/VisualStudio/ProjectSystemCPS.TestBed/WinForms/Form1.prg @@ -0,0 +1,13 @@ +USING System +USING System.Windows.Forms + +BEGIN NAMESPACE WinFormsTestBed + + PUBLIC PARTIAL CLASS Form1 INHERIT System.Windows.Forms.Form + + PUBLIC CONSTRUCTOR() STRICT + SELF:InitializeComponent() + RETURN + END CONSTRUCTOR + END CLASS +END NAMESPACE diff --git a/src/VisualStudio/ProjectSystemCPS.TestBed/WinForms/Program.prg b/src/VisualStudio/ProjectSystemCPS.TestBed/WinForms/Program.prg new file mode 100644 index 0000000000..d2c8701b44 --- /dev/null +++ b/src/VisualStudio/ProjectSystemCPS.TestBed/WinForms/Program.prg @@ -0,0 +1,10 @@ +USING System +USING System.Windows.Forms +USING WinFormsTestBed + +[STAThread] ; +FUNCTION Start() AS VOID + Application.EnableVisualStyles() + Application.SetCompatibleTextRenderingDefault(FALSE) + Application.Run(Form1{}) + RETURN diff --git a/src/VisualStudio/ProjectSystemCPS.TestBed/WinForms/WinFormsTestBed.xsproj b/src/VisualStudio/ProjectSystemCPS.TestBed/WinForms/WinFormsTestBed.xsproj new file mode 100644 index 0000000000..9c6a5d06b9 --- /dev/null +++ b/src/VisualStudio/ProjectSystemCPS.TestBed/WinForms/WinFormsTestBed.xsproj @@ -0,0 +1,16 @@ + + + + WinExe + net8.0-windows + true + Core + WinFormsTestBed + True + True + + \ No newline at end of file From f195b9afae1c393fb0f32ec265ba206caf680b52 Mon Sep 17 00:00:00 2001 From: Hansjoerg Petriffer Date: Sun, 27 Sep 2026 15:22:21 +0200 Subject: [PATCH 16/60] [VsIntegration] Keep XSharpCodeDomHelper out of the GAC'd XSharp.CodeDomProvider VS test: "Could not load type 'XSharp.CodeDom.XSharpCodeDomHelper' from assembly 'XSharp.CodeDomProvider, Version=3.0.2.0'". The X# installation puts XSharp.CodeDomProvider (and XSharp.VSParser, XSharp.Evaluator) in the GAC with the same assembly version, and the GAC copy wins over the one in the VSIX, so the class moved there in the shadow designer port was not found. - XSharpCodeDomHelper back in ProjectPackage/CodeDomProvider; CodeDomProvider.csproj and the VS 2019 ProjectPackage.csproj are unchanged compared to before the port again. - ProjectSystemCPS links the same source file as an internal copy (define XSHARP_PROJECTSYSTEMCPS), so the shadow designer does not depend on a GAC'd assembly version. Co-Authored-By: Claude Opus 5.5 --- .../CodeDomProvider/CodeDomProvider.csproj | 1 - .../CodeDomProvider/XSharpCodeDomHelper.cs | 15 +++++++++++---- .../ProjectPackage/ProjectPackage.csproj | 1 + .../ProjectPackage/ProjectPackage2022.csproj | 1 + .../ProjectSystemCPS/ProjectSystemCPS.csproj | 6 ++++-- 5 files changed, 17 insertions(+), 7 deletions(-) rename src/VisualStudio/{ => ProjectPackage}/CodeDomProvider/XSharpCodeDomHelper.cs (94%) diff --git a/src/VisualStudio/CodeDomProvider/CodeDomProvider.csproj b/src/VisualStudio/CodeDomProvider/CodeDomProvider.csproj index 75497b660c..16fd540120 100644 --- a/src/VisualStudio/CodeDomProvider/CodeDomProvider.csproj +++ b/src/VisualStudio/CodeDomProvider/CodeDomProvider.csproj @@ -65,7 +65,6 @@ Component - diff --git a/src/VisualStudio/CodeDomProvider/XSharpCodeDomHelper.cs b/src/VisualStudio/ProjectPackage/CodeDomProvider/XSharpCodeDomHelper.cs similarity index 94% rename from src/VisualStudio/CodeDomProvider/XSharpCodeDomHelper.cs rename to src/VisualStudio/ProjectPackage/CodeDomProvider/XSharpCodeDomHelper.cs index 8896d26f88..7de4d49e4b 100644 --- a/src/VisualStudio/CodeDomProvider/XSharpCodeDomHelper.cs +++ b/src/VisualStudio/ProjectPackage/CodeDomProvider/XSharpCodeDomHelper.cs @@ -10,7 +10,14 @@ namespace XSharp.CodeDom { + // Also compiled into XSharp.ProjectSystemCPS (shadow WinForms designer), as an internal copy. It does not + // live in XSharp.CodeDomProvider because the X# installation puts that assembly in the GAC: a newer + // VSIX would then run against the older installed version. +#if XSHARP_PROJECTSYSTEMCPS + internal class XSharpCodeDomHelper +#else public class XSharpCodeDomHelper +#endif { /// /// Merge both CodeCompileUnit. The main type (class) will come from formCompileUnit @@ -18,7 +25,7 @@ public class XSharpCodeDomHelper /// /// /// - public static XMergedCodeCompileUnit MergeCodeCompileUnit( XCodeCompileUnit formCompileUnit, XCodeCompileUnit designerCompileUnit) + internal static XMergedCodeCompileUnit MergeCodeCompileUnit( XCodeCompileUnit formCompileUnit, XCodeCompileUnit designerCompileUnit) { // Create the merged CodeCompileUnit var mergedCodeCompileUnit = new XMergedCodeCompileUnit(formCompileUnit); @@ -73,7 +80,7 @@ public static XMergedCodeCompileUnit MergeCodeCompileUnit( XCodeCompileUnit form } - public static CodeTypeDeclaration FindDesignerClass(CodeCompileUnit ccu, CodeTypeDeclaration masterClass) + internal static CodeTypeDeclaration FindDesignerClass(CodeCompileUnit ccu, CodeTypeDeclaration masterClass) { return FindDesignerClass(ccu, out _, masterClass.Name); } @@ -83,12 +90,12 @@ public static CodeTypeDeclaration FindDesignerClass(CodeCompileUnit ccu, CodeTyp /// /// /// - public static CodeTypeDeclaration FindDesignerClass(CodeCompileUnit ccu) + internal static CodeTypeDeclaration FindDesignerClass(CodeCompileUnit ccu) { return FindDesignerClass(ccu, out _); } - public static CodeTypeDeclaration FindDesignerClass(CodeCompileUnit ccu, out CodeNamespace namespaceName, string name = "") + internal static CodeTypeDeclaration FindDesignerClass(CodeCompileUnit ccu, out CodeNamespace namespaceName, string name = "") { namespaceName = null; // We search the first Class that has a Candidate for InitializeComponent diff --git a/src/VisualStudio/ProjectPackage/ProjectPackage.csproj b/src/VisualStudio/ProjectPackage/ProjectPackage.csproj index c6cd3164db..8676a93b83 100644 --- a/src/VisualStudio/ProjectPackage/ProjectPackage.csproj +++ b/src/VisualStudio/ProjectPackage/ProjectPackage.csproj @@ -118,6 +118,7 @@ + diff --git a/src/VisualStudio/ProjectPackage/ProjectPackage2022.csproj b/src/VisualStudio/ProjectPackage/ProjectPackage2022.csproj index 3e68c63298..c15b1d4549 100644 --- a/src/VisualStudio/ProjectPackage/ProjectPackage2022.csproj +++ b/src/VisualStudio/ProjectPackage/ProjectPackage2022.csproj @@ -118,6 +118,7 @@ + diff --git a/src/VisualStudio/ProjectSystemCPS/ProjectSystemCPS.csproj b/src/VisualStudio/ProjectSystemCPS/ProjectSystemCPS.csproj index 2ee96111ee..218a32e309 100644 --- a/src/VisualStudio/ProjectSystemCPS/ProjectSystemCPS.csproj +++ b/src/VisualStudio/ProjectSystemCPS/ProjectSystemCPS.csproj @@ -29,17 +29,19 @@ false - TRACE;DEBUG;XSHARP;DEV17 + TRACE;DEBUG;XSHARP;DEV17;XSHARP_PROJECTSYSTEMCPS full - TRACE;XSHARP;DEV17 + TRACE;XSHARP;DEV17;XSHARP_PROJECTSYSTEMCPS pdbonly + + From 54ce3fb0643ad4e7fe0f366ad5fad1d155859024 Mon Sep 17 00:00:00 2001 From: Hansjoerg Petriffer Date: Mon, 28 Sep 2026 07:23:55 +0200 Subject: [PATCH 17/60] [VsIntegration] Keep shadow designer companion projects out of X# SDK globs VS test: the shadow designer opened the first time, the second time the WinForms designer reported that Form1.Designer.cs "is not contained within a project that supports code". The companion C# project ({Project}.ShadowDesigner) is generated next to the X# project folder, so the X# project's own globs never see it -- but an SDK-style X# project in a parent folder does (here CpsTestBed, which contains the WinForms test project). After the first open the generated .cs files were None items of that X# project, VS used it as the owning hierarchy of Form1.Designer.cs and the designer refused it. XSharp.SDK.Props excludes **/*.ShadowDesigner/** from the default items of every X# SDK project (before XSharpDefaultItemExcludes captures DefaultItemExcludes, so the X# specific globs skip it too). Verified with a dummy companion folder: installed props -> None item, fixed props -> excluded. StartExp.cmd copies XSharp.SDK.Props into the overlay. Co-Authored-By: Claude Opus 5.5 --- src/Compiler/src/Compiler/XSharpBuildTask/XSharp.SDK.Props | 4 ++++ src/VisualStudio/ProjectSystemCPS.TestBed/StartExp.cmd | 1 + 2 files changed, 5 insertions(+) diff --git a/src/Compiler/src/Compiler/XSharpBuildTask/XSharp.SDK.Props b/src/Compiler/src/Compiler/XSharpBuildTask/XSharp.SDK.Props index de18bdbd9b..ccca2ea88b 100644 --- a/src/Compiler/src/Compiler/XSharpBuildTask/XSharp.SDK.Props +++ b/src/Compiler/src/Compiler/XSharpBuildTask/XSharp.SDK.Props @@ -23,6 +23,10 @@ Copyright (C) XSharp BV. All rights reserved. is imported after this file) and is listed so the value is picked up should that order ever change. The output directories are added explicitly for the same reason - they are only appended to DefaultItemExcludes later, which is too late for the globs below. --> + + $(DefaultItemExcludes);**/*.ShadowDesigner/** $(DefaultItemExcludes);$(DefaultExcludesInProjectFolder) $(XSharpDefaultItemExcludes);$(BaseOutputPath)/** $(XSharpDefaultItemExcludes);$(BaseIntermediateOutputPath)/** diff --git a/src/VisualStudio/ProjectSystemCPS.TestBed/StartExp.cmd b/src/VisualStudio/ProjectSystemCPS.TestBed/StartExp.cmd index 43782a028b..b803a443b7 100644 --- a/src/VisualStudio/ProjectSystemCPS.TestBed/StartExp.cmd +++ b/src/VisualStudio/ProjectSystemCPS.TestBed/StartExp.cmd @@ -25,6 +25,7 @@ xcopy /y /e /q /i "%XSharpMsBuildDir%" "%OVERLAY%" >nul copy /y "%BUILDTASK%\XSharp.CurrentVersion.targets" "%OVERLAY%" >nul copy /y "%BUILDTASK%\XSharp.CrossTargeting.targets" "%OVERLAY%" >nul copy /y "%BUILDTASK%\XSharp.DesignTime.targets" "%OVERLAY%" >nul +copy /y "%BUILDTASK%\XSharp.SDK.Props" "%OVERLAY%" >nul xcopy /y /e /q /i "%BUILDTASK%\Rules" "%OVERLAY%\Rules" >nul for /f "usebackq delims=" %%i in (`"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" -version [17.0^,18.0^) -latest -property productPath`) do set DEVENV=%%i From c1ac3d62f782bc8604c2ce14d8b83d52119b55d7 Mon Sep 17 00:00:00 2001 From: Hansjoerg Petriffer Date: Mon, 28 Sep 2026 07:30:18 +0200 Subject: [PATCH 18/60] [VsIntegration] CPS project system: form, user control and VO binary icons VS test: X# forms showed the X# document icon in CPS projects. XSharpProjectTreePropertiesProvider now mirrors XSharpFileNode (MPFproj): a .prg is a form/user control when its SubType says so, or when it has a matching .Designer.prg and its class inherits from Form/UserControl (same inference as the MPFproj SDK node, cached per file timestamp) -> KnownImageIds.WindowsForm / UserControl. VO binaries get the MPFproj icons (FormInstance, Database, MainMenuControl, ValidationRule, Report). The item path comes from the FullPath metadata (ItemName is only the file name). Co-Authored-By: Claude Opus 5.5 --- .../XSharpProjectTreePropertiesProvider.cs | 123 ++++++++++++++++-- 1 file changed, 113 insertions(+), 10 deletions(-) diff --git a/src/VisualStudio/ProjectSystemCPS/Imaging/XSharpProjectTreePropertiesProvider.cs b/src/VisualStudio/ProjectSystemCPS/Imaging/XSharpProjectTreePropertiesProvider.cs index ca88606664..351d3f75c2 100644 --- a/src/VisualStudio/ProjectSystemCPS/Imaging/XSharpProjectTreePropertiesProvider.cs +++ b/src/VisualStudio/ProjectSystemCPS/Imaging/XSharpProjectTreePropertiesProvider.cs @@ -5,44 +5,147 @@ // using System; +using System.Collections.Concurrent; +using System.Collections.Generic; using System.ComponentModel.Composition; using System.IO; +using System.Text.RegularExpressions; +using Microsoft.VisualStudio.Imaging; using Microsoft.VisualStudio.ProjectSystem; +using XSharp.ProjectSystem.ShadowDesigner; namespace XSharp.ProjectSystem.Imaging { /// - /// Sets the X# icons in Solution Explorer: the project node and the X# source files. + /// Sets the X# icons in Solution Explorer, like XSharpFileNode does in MPFproj: the project node, forms and + /// user controls, the VO binaries and the other X# source files. /// /// /// The managed project system provides the project icon through IProjectImageProvider, which is internal /// to it and only implemented for C#, VB and F#. IProjectTreePropertiesProvider is the public CPS way. + /// A .prg is a form/user control when its SubType says so, or -- SDK-style projects rarely have a SubType -- + /// when it has a matching .Designer.prg and its class inherits from Form/UserControl (same inference as the + /// MPFproj SDK project node). /// [Export(typeof(IProjectTreePropertiesProvider))] [AppliesTo(XSharpCapabilities.XSharpCps)] [Order(1000)] internal sealed class XSharpProjectTreePropertiesProvider : IProjectTreePropertiesProvider { + private static readonly ProjectImageMoniker WindowsForm = Known(KnownImageIds.WindowsForm); + private static readonly ProjectImageMoniker UserControl = Known(KnownImageIds.UserControl); + + private static readonly Dictionary ExtensionIcons = + new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { ".prg", XSharpImages.Document }, + { ".prgx", XSharpImages.Document }, + { ".xs", XSharpImages.Document }, + { ".xsfrm", Known(KnownImageIds.FormInstance) }, + { ".vnfrm", Known(KnownImageIds.FormInstance) }, + { ".xsdbs", Known(KnownImageIds.Database) }, + { ".vndbs", Known(KnownImageIds.Database) }, + { ".xssql", Known(KnownImageIds.Database) }, + { ".vnsqs", Known(KnownImageIds.Database) }, + { ".xsmnu", Known(KnownImageIds.MainMenuControl) }, + { ".vnmnu", Known(KnownImageIds.MainMenuControl) }, + { ".xsfs", Known(KnownImageIds.ValidationRule) }, + { ".vnfs", Known(KnownImageIds.ValidationRule) }, + { ".xsrep", Known(KnownImageIds.Report) }, + { ".vnrep", Known(KnownImageIds.Report) }, + }; + + // Result of the base class inference per file, invalidated by the file's timestamp + private static readonly ConcurrentDictionary inferredSubTypes = + new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + + private static readonly Regex BaseClassRegex = + new Regex(@"\bCLASS\s+\S+\s+INHERIT\s+([A-Za-z_][\w.]*)", RegexOptions.IgnoreCase | RegexOptions.Compiled); + + private readonly UnconfiguredProject project; + + [ImportingConstructor] + public XSharpProjectTreePropertiesProvider(UnconfiguredProject project) + { + this.project = project; + } + + private static ProjectImageMoniker Known(int id) => new ProjectImageMoniker(KnownImageIds.ImageCatalogGuid, id); + public void CalculatePropertyValues(IProjectTreeCustomizablePropertyContext propertyContext, IProjectTreeCustomizablePropertyValues propertyValues) { if (propertyValues.Flags.Contains(ProjectTreeFlags.ProjectRoot)) { propertyValues.Icon = XSharpImages.Project; + return; } - else if (!propertyValues.Flags.Contains(ProjectTreeFlags.Folder) && IsXSharpSourceFile(propertyContext.ItemName)) + if (propertyValues.Flags.Contains(ProjectTreeFlags.Folder) || string.IsNullOrEmpty(propertyContext.ItemName)) + return; + var extension = Path.GetExtension(propertyContext.ItemName); + if (!ExtensionIcons.TryGetValue(extension, out var icon)) + return; + if (string.Equals(extension, ".prg", StringComparison.OrdinalIgnoreCase)) { - propertyValues.Icon = XSharpImages.Document; + switch (GetDesignerSubType(propertyContext)) + { + case "Form": + icon = WindowsForm; + break; + case "UserControl": + icon = UserControl; + break; + } } + propertyValues.Icon = icon; + propertyValues.ExpandedIcon = icon; } - private static bool IsXSharpSourceFile(string itemName) + private string GetDesignerSubType(IProjectTreeCustomizablePropertyContext propertyContext) { - if (string.IsNullOrEmpty(itemName)) - return false; - var extension = Path.GetExtension(itemName); - return string.Equals(extension, ".prg", StringComparison.OrdinalIgnoreCase) - || string.Equals(extension, ".prgx", StringComparison.OrdinalIgnoreCase) - || string.Equals(extension, ".xs", StringComparison.OrdinalIgnoreCase); + if (propertyContext.Metadata != null && propertyContext.Metadata.TryGetValue("SubType", out var subType) && + !string.IsNullOrEmpty(subType)) + { + return subType; + } + // ItemName is only the file name; the full path is in the FullPath metadata (as used by the managed + // project system). Fall back to the project folder for items without metadata. + string path = null; + if (propertyContext.Metadata == null || !propertyContext.Metadata.TryGetValue("FullPath", out path) || string.IsNullOrEmpty(path)) + path = Path.Combine(Path.GetDirectoryName(project.FullPath), propertyContext.ItemName); + if (!ShadowDesignerBridge.HasDesignerFile(path)) + return null; + return InferSubTypeFromBaseClass(path); + } + + /// + /// "CLASS Foo INHERIT System.Windows.Forms.Form" -> Form, "... INHERIT UserControl" -> UserControl. + /// Same rule as XSharpFileNode.InferSubTypeFromBaseClass (MPFproj). + /// + private static string InferSubTypeFromBaseClass(string path) + { + try + { + var stamp = File.GetLastWriteTimeUtc(path); + if (inferredSubTypes.TryGetValue(path, out var cached) && cached.Stamp == stamp) + return cached.SubType; + string subType = null; + var match = BaseClassRegex.Match(File.ReadAllText(path)); + if (match.Success) + { + var baseType = match.Groups[1].Value; + if (baseType.Equals("Form", StringComparison.OrdinalIgnoreCase) || baseType.EndsWith(".Form", StringComparison.OrdinalIgnoreCase)) + subType = "Form"; + else if (baseType.Equals("UserControl", StringComparison.OrdinalIgnoreCase) || baseType.EndsWith(".UserControl", StringComparison.OrdinalIgnoreCase)) + subType = "UserControl"; + } + inferredSubTypes[path] = (stamp, subType); + return subType; + } + catch (Exception) + { + // Best effort, a failure must never break the tree + return null; + } } } } From 757c27a0c53123e67c36551c22dd8e0a69e6c0ec Mon Sep 17 00:00:00 2001 From: Hansjoerg Petriffer Date: Mon, 28 Sep 2026 07:46:20 +0200 Subject: [PATCH 19/60] [VsIntegration] Shadow designer: remove the empty companion solution folder on close VS test: after closing, the saved .sln still contained the "Shadow Designer (generated)" Solution Folder. ShadowDesignerCleanup removed the companion projects in OnQueryCloseSolution, but not the folder they were nested in (behaviour inherited from the MPFproj implementation). It now also removes that folder when it is empty (IVsSolution.CloseSolutionElement, like the companion projects), so the .sln stays unchanged. Co-Authored-By: Claude Opus 5.5 --- .../ShadowDesigner/ShadowDesignerCleanup.cs | 52 +++++++++++++++++++ .../ShadowDesigner/SolutionWiring.cs | 2 +- 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/ShadowDesignerCleanup.cs b/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/ShadowDesignerCleanup.cs index 598ad84c09..f1b4d9dd7d 100644 --- a/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/ShadowDesignerCleanup.cs +++ b/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/ShadowDesignerCleanup.cs @@ -108,10 +108,62 @@ public int OnQueryCloseSolution(object reserved, ref int cancel) // anyway; a stale .sln reference is a lesser, recoverable annoyance. } } + try + { + // Without this the empty Solution Folder stayed in the saved .sln. + CloseEmptyShadowDesignerFolder(); + } + catch + { + // Best-effort, see above + } } return VSConstants.S_OK; } + /// + /// Removes the "Shadow Designer (generated)" Solution Folder (SolutionWiring) once the companion + /// projects in it have been removed, so the saved .sln does not keep an empty folder. Only removed + /// when empty -- a user who moved own projects into it keeps them. + /// + private void CloseEmptyShadowDesignerFolder() + { + Guid solutionFolderType = new Guid("2150E333-8FDC-42A3-9474-1A3956D46DE8"); + int hr = _vsSolution.GetProjectEnum((uint)(__VSENUMPROJFLAGS.EPF_ALLINSOLUTION | __VSENUMPROJFLAGS.EPF_MATCHTYPE), + ref solutionFolderType, out IEnumHierarchies hierarchies); + if (ErrorHandler.Failed(hr) || hierarchies == null) + { + return; + } + + var buffer = new IVsHierarchy[1]; + while (hierarchies.Next(1, buffer, out uint fetched) == VSConstants.S_OK && fetched == 1) + { + var hierarchy = buffer[0]; + if (ErrorHandler.Succeeded(hierarchy.GetProperty(VSConstants.VSITEMID_ROOT, (int)__VSHPROPID.VSHPROPID_Name, out object name)) && + string.Equals(name as string, SolutionWiring.ShadowDesignerFolderName, StringComparison.OrdinalIgnoreCase) && + ErrorHandler.Succeeded(hierarchy.GetProperty(VSConstants.VSITEMID_ROOT, (int)__VSHPROPID.VSHPROPID_FirstChild, out object firstChild)) && + IsNil(firstChild)) + { + _vsSolution.CloseSolutionElement(0, hierarchy, 0); + return; + } + } + } + + private static bool IsNil(object itemId) + { + switch (itemId) + { + case int i: + return unchecked((uint)i) == VSConstants.VSITEMID_NIL; + case uint u: + return u == VSConstants.VSITEMID_NIL; + default: + return itemId == null; + } + } + /// /// Finds the IVsHierarchy for the project at csprojPath (searching every project in /// the solution, including ones nested in Solution Folders) and asks IVsSolution to diff --git a/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/SolutionWiring.cs b/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/SolutionWiring.cs index 8ca4ddd20d..71bcca65e7 100644 --- a/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/SolutionWiring.cs +++ b/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/SolutionWiring.cs @@ -24,7 +24,7 @@ internal static class SolutionWiring { /// Name of the Solution Folder companion projects are nested under, to keep /// them visually separated from the user's real projects in Solution Explorer. - private const string ShadowDesignerFolderName = "Shadow Designer (generated)"; + internal const string ShadowDesignerFolderName = "Shadow Designer (generated)"; /// /// Adds csprojPath to the running solution, nested inside a dedicated Solution Folder From e47d8cee8a5791dfda60467c5972cc8c16bb214f Mon Sep 17 00:00:00 2001 From: Hansjoerg Petriffer Date: Mon, 28 Sep 2026 07:51:18 +0200 Subject: [PATCH 20/60] [VsIntegration] CPS project system: register the project selector for the CPS project type GUID too When VS saves a solution it writes the GUID of the factory that loaded the project, so an SDK project's .sln entry flips from {AA6C8D78} (MPFproj) to {AB494DCE} (CPS). C# SDK projects flip the same way ({FAE04EC0} -> {9A19103F}), and VS offers no way to keep the original GUID. Routing the CPS GUID through the same selector means those entries still load with MPFproj when the routing option is off, the project is not SDK-style, or the installed X# MSBuild files are too old for CPS. Co-Authored-By: Claude Opus 5.5 --- .../Selection/XSharpProjectSelector.cs | 13 +++++++++---- .../ProjectSystemCPS/XSharpProjectSystemPackage.cs | 5 +++++ 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/VisualStudio/ProjectSystemCPS/Selection/XSharpProjectSelector.cs b/src/VisualStudio/ProjectSystemCPS/Selection/XSharpProjectSelector.cs index bc2883fadb..0723f00f53 100644 --- a/src/VisualStudio/ProjectSystemCPS/Selection/XSharpProjectSelector.cs +++ b/src/VisualStudio/ProjectSystemCPS/Selection/XSharpProjectSelector.cs @@ -15,14 +15,19 @@ namespace XSharp.ProjectSystem.Selection { /// - /// Chooses the project system for projects with the X# (MPFproj) project type GUID {AA6C8D78-...}: - /// SDK-style projects are loaded by the CPS project type, all other (legacy) projects by MPFproj. + /// Chooses the project system for projects with the X# (MPFproj) project type GUID {AA6C8D78-...} or the CPS + /// project type GUID {AB494DCE-...}: SDK-style projects are loaded by the CPS project type, all other (legacy) + /// projects by MPFproj. VS rewrites {AA6C8D78} to {AB494DCE} in the .sln when it saves the solution, so both + /// GUIDs are routed the same way. /// /// /// Same mechanism as the managed project system uses for C#/VB/F# (FSharpProjectSelector): the selector is /// registered for the legacy project type GUID (pkgdef "Projects\{guid}\ProjectSelector", see - /// ), so existing solution files keep their project type GUID. - /// Solutions that use the CPS project type GUID directly do not go through the selector. + /// ), so existing solution files keep working. Unlike the managed + /// project system it is registered for the CPS project type GUID as well: VS persists the GUID of the factory + /// the selector returned, so an SDK project's .sln entry flips to {AB494DCE} once (C# has the same flip, + /// {FAE04EC0} -> {9A19103F}); with the second registration such an entry still loads with MPFproj when the + /// routing is switched off or the X# MSBuild support files are too old. /// Any problem (unreadable project file, routing switched off, X# MSBuild support files without the CPS /// design-time import, see ) falls back to MPFproj, the project system /// that loaded these projects before. diff --git a/src/VisualStudio/ProjectSystemCPS/XSharpProjectSystemPackage.cs b/src/VisualStudio/ProjectSystemCPS/XSharpProjectSystemPackage.cs index 518e488d81..3c8642fa51 100644 --- a/src/VisualStudio/ProjectSystemCPS/XSharpProjectSystemPackage.cs +++ b/src/VisualStudio/ProjectSystemCPS/XSharpProjectSystemPackage.cs @@ -42,6 +42,11 @@ namespace XSharp.ProjectSystem Capabilities = XSharpCapabilities.ProjectTypeCapabilities, PossibleProjectExtensions = XSharpConstants.ProjectExtension)] [ProvideProjectSelector(XSharpConstants.guidXSharpProjectFactoryString, XSharpConstants.guidProjectSelectorString)] + // Visual Studio writes the GUID of the project system that actually loaded a project into the .sln when it + // saves the solution ({AA6C8D78} -> {AB494DCE}, the same happens for C#: {FAE04EC0} -> {9A19103F}). The + // selector on the CPS project type makes such entries behave like the original ones: legacy projects, the + // Tools > Options switch and the X# MSBuild support check still send projects to MPFproj. + [ProvideProjectSelector(XSharpConstants.guidCpsProjectTypeString, XSharpConstants.guidProjectSelectorString)] [ProvideOptionPage(typeof(ProjectSystemOptionsPage), ProjectSystemOptionsPage.CategoryName, ProjectSystemOptionsPage.PageName, 0, 0, true)] public sealed class XSharpProjectSystemPackage : AsyncPackage { From de74c7708c565984699f03c5125236c2f61f9404 Mon Sep 17 00:00:00 2001 From: Hansjoerg Petriffer Date: Mon, 28 Sep 2026 09:02:50 +0200 Subject: [PATCH 21/60] [VsIntegration] Shadow designer: make the empty solution folder cleanup work The first version never found the "Shadow Designer (generated)" folder: solution folders are virtual projects, so GetProjectEnum needs EPF_ALLPROJECTS rather than EPF_ALLINSOLUTION. Right after the companion project is removed the folder can still list it as a child without a name and without a nested project, so such children, and *.ShadowDesigner projects, no longer count as content. Each step is logged. Verified in the Exp instance: the folder is removed and is no longer in the saved .sln. Co-Authored-By: Claude Opus 5.5 --- .../ShadowDesigner/CompanionProjectWriter.cs | 9 ++- .../ShadowDesigner/ShadowDesignerCleanup.cs | 68 ++++++++++++++++--- 2 files changed, 66 insertions(+), 11 deletions(-) diff --git a/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/CompanionProjectWriter.cs b/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/CompanionProjectWriter.cs index 9b85d0d06a..5b01440d08 100644 --- a/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/CompanionProjectWriter.cs +++ b/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/CompanionProjectWriter.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) XSharp B.V. All Rights Reserved. // Licensed under the Apache License, Version 2.0. // See License.txt in the project root for license information. @@ -23,6 +23,9 @@ namespace XSharp.ProjectSystem.ShadowDesigner /// internal static class CompanionProjectWriter { + /// Companion project name = real project name + this suffix (also its folder name). + internal const string CompanionSuffix = ".ShadowDesigner"; + public readonly struct CompanionPaths { public CompanionPaths(string csprojPath, string designerCsPath) @@ -61,7 +64,7 @@ public PackageRef(string include, string version) public static CompanionPaths ComputePaths(string realXsprojPath, string className) { string companionDir = GetCompanionDir(realXsprojPath); - string csprojName = Path.GetFileNameWithoutExtension(realXsprojPath) + ".ShadowDesigner"; + string csprojName = Path.GetFileNameWithoutExtension(realXsprojPath) + CompanionSuffix; return new CompanionPaths( Path.Combine(companionDir, csprojName + ".csproj"), Path.Combine(companionDir, className + ".Designer.cs")); @@ -80,7 +83,7 @@ private static string GetCompanionDir(string realXsprojPath) string realProjectName = Path.GetFileNameWithoutExtension(realXsprojPath); string solutionParentDir = Path.GetDirectoryName(realProjectDir) ?? throw new InvalidOperationException($"Could not determine parent directory of {realProjectDir}."); - return Path.Combine(solutionParentDir, realProjectName + ".ShadowDesigner"); + return Path.Combine(solutionParentDir, realProjectName + CompanionSuffix); } public static CompanionPaths EnsureCompanionProject( diff --git a/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/ShadowDesignerCleanup.cs b/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/ShadowDesignerCleanup.cs index f1b4d9dd7d..d76abfe3ee 100644 --- a/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/ShadowDesignerCleanup.cs +++ b/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/ShadowDesignerCleanup.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) XSharp B.V. All Rights Reserved. // Licensed under the Apache License, Version 2.0. // See License.txt in the project root for license information. @@ -113,9 +113,10 @@ public int OnQueryCloseSolution(object reserved, ref int cancel) // Without this the empty Solution Folder stayed in the saved .sln. CloseEmptyShadowDesignerFolder(); } - catch + catch (Exception ex) { // Best-effort, see above + Logger.Exception(ex, "ShadowDesignerCleanup: could not remove the Solution Folder"); } } return VSConstants.S_OK; @@ -128,11 +129,14 @@ public int OnQueryCloseSolution(object reserved, ref int cancel) /// private void CloseEmptyShadowDesignerFolder() { + // Solution Folders are virtual projects: EPF_ALLINSOLUTION alone (loaded + unloaded) does not return + // them, EPF_ALLPROJECTS includes EPF_ALLVIRTUAL. Guid solutionFolderType = new Guid("2150E333-8FDC-42A3-9474-1A3956D46DE8"); - int hr = _vsSolution.GetProjectEnum((uint)(__VSENUMPROJFLAGS.EPF_ALLINSOLUTION | __VSENUMPROJFLAGS.EPF_MATCHTYPE), + int hr = _vsSolution.GetProjectEnum((uint)(__VSENUMPROJFLAGS.EPF_ALLPROJECTS | __VSENUMPROJFLAGS.EPF_MATCHTYPE), ref solutionFolderType, out IEnumHierarchies hierarchies); if (ErrorHandler.Failed(hr) || hierarchies == null) { + Logger.Information($"ShadowDesignerCleanup: GetProjectEnum failed, hr=0x{hr:X8}"); return; } @@ -140,15 +144,63 @@ private void CloseEmptyShadowDesignerFolder() while (hierarchies.Next(1, buffer, out uint fetched) == VSConstants.S_OK && fetched == 1) { var hierarchy = buffer[0]; - if (ErrorHandler.Succeeded(hierarchy.GetProperty(VSConstants.VSITEMID_ROOT, (int)__VSHPROPID.VSHPROPID_Name, out object name)) && - string.Equals(name as string, SolutionWiring.ShadowDesignerFolderName, StringComparison.OrdinalIgnoreCase) && - ErrorHandler.Succeeded(hierarchy.GetProperty(VSConstants.VSITEMID_ROOT, (int)__VSHPROPID.VSHPROPID_FirstChild, out object firstChild)) && - IsNil(firstChild)) + if (ErrorHandler.Failed(hierarchy.GetProperty(VSConstants.VSITEMID_ROOT, (int)__VSHPROPID.VSHPROPID_Name, out object name)) || + !string.Equals(name as string, SolutionWiring.ShadowDesignerFolderName, StringComparison.OrdinalIgnoreCase)) { - _vsSolution.CloseSolutionElement(0, hierarchy, 0); + continue; + } + // The companion projects removed just before can still be listed as children at this point, so the + // folder counts as empty when it only contains companion projects. + if (!ContainsOnlyCompanionProjects(hierarchy, out string otherChild)) + { + Logger.Information($"ShadowDesignerCleanup: Solution Folder '{name}' contains '{otherChild}', kept"); return; } + hr = _vsSolution.CloseSolutionElement(0, hierarchy, 0); + Logger.Information($"ShadowDesignerCleanup: removed the empty Solution Folder '{name}', hr=0x{hr:X8}"); + return; + } + Logger.Information($"ShadowDesignerCleanup: Solution Folder '{SolutionWiring.ShadowDesignerFolderName}' not found"); + } + + private bool ContainsOnlyCompanionProjects(IVsHierarchy folder, out string otherChild) + { + otherChild = null; + folder.GetProperty(VSConstants.VSITEMID_ROOT, (int)__VSHPROPID.VSHPROPID_FirstChild, out object child); + while (!IsNil(child)) + { + uint itemId = child is int i ? unchecked((uint)i) : (uint)child; + folder.GetProperty(itemId, (int)__VSHPROPID.VSHPROPID_Name, out object childName); + string projectPath = null; + Guid hierarchyGuid = typeof(IVsHierarchy).GUID; + if (ErrorHandler.Succeeded(folder.GetNestedHierarchy(itemId, ref hierarchyGuid, out IntPtr nested, out uint _)) && nested != IntPtr.Zero) + { + try + { + if (System.Runtime.InteropServices.Marshal.GetObjectForIUnknown(nested) is IVsProject project) + { + project.GetMkDocument(VSConstants.VSITEMID_ROOT, out projectPath); + } + } + finally + { + System.Runtime.InteropServices.Marshal.Release(nested); + } + } + // A removed companion can remain as a child without name and without nested project until the + // solution refreshes its hierarchy; that is not user content either. + bool isCompanion = (projectPath != null && _companionCsprojPaths.Contains(projectPath)) || + (childName as string)?.EndsWith(CompanionProjectWriter.CompanionSuffix, StringComparison.OrdinalIgnoreCase) == true || + (projectPath == null && string.IsNullOrEmpty(childName as string)); + Logger.Information($"ShadowDesignerCleanup: Solution Folder child '{childName}' ({projectPath ?? "no project"}), companion: {isCompanion}"); + if (!isCompanion) + { + otherChild = childName as string ?? projectPath ?? itemId.ToString(); + return false; + } + folder.GetProperty(itemId, (int)__VSHPROPID.VSHPROPID_NextSibling, out child); } + return true; } private static bool IsNil(object itemId) From 7df874f5c3955f828ed3f9f12d238233518b2fc3 Mon Sep 17 00:00:00 2001 From: Hansjoerg Petriffer Date: Mon, 28 Sep 2026 09:02:53 +0200 Subject: [PATCH 22/60] [VsIntegration] Shadow designer: resolve assembly references before parsing the form Opening a form shortly after the solution loaded failed with an ArgumentNullException ("e") in CSharpCodeGenerator. The project's assembly references were still queued (XProject.ResolveReferences runs at most every 15 seconds), so System.Windows.Forms.Form could not be resolved and the parser dropped the right-hand side of assignments. TryOpen already forced the queued references to load, but only after parsing; it now does so before. Co-Authored-By: Claude Opus 5.5 --- .../ShadowDesigner/ShadowDesignerBridge.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/ShadowDesignerBridge.cs b/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/ShadowDesignerBridge.cs index 994be26854..4f6ed1ed09 100644 --- a/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/ShadowDesignerBridge.cs +++ b/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/ShadowDesignerBridge.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) XSharp B.V. All Rights Reserved. // Licensed under the Apache License, Version 2.0. // See License.txt in the project root for license information. @@ -135,6 +135,11 @@ public static bool TryOpen(string mainPrgPath, XProject xProject, Action refresh refreshReferences?.Invoke(); } + // The parser resolves System.Windows.Forms.Form & co. through the project's assembly references. + // Shortly after the solution loaded they can still be queued (XProject.ResolveReferences runs at + // most every 15 seconds); unresolved types make the parser drop the right-hand side of assignments + // and CSharpCodeProvider then throws ArgumentNullException ("e") on the first open. + ForceResolveUnprocessedReferences(xProject); XCodeCompileUnit mainUnit = ToXCodeCompileUnit(ParseFile(xProject, mainPrgPath, null)); CodeTypeDeclaration firstClass = mainUnit.GetFirstClass(); XCodeCompileUnit designerUnit = ToXCodeCompileUnit(ParseFile(xProject, designerPrgPath, firstClass)); From 0424f2517df182e60c81232f88f2396da2bae344 Mon Sep 17 00:00:00 2001 From: Hansjoerg Petriffer Date: Mon, 28 Sep 2026 09:13:55 +0200 Subject: [PATCH 23/60] [VsIntegration] WP-8: remove the SDK-style project path from MPFproj SDK-style X# projects are loaded by the CPS project system (XSharp.ProjectSystemCPS) since the project selector routes them there and the shadow WinForms designer was ported. MPFproj keeps the legacy projects. Removed from ProjectPackage (VS 2022 only; VS 2019 never had it): - NodesSDK (XSharpSdkProjectNode, SDK reference/dependency/folder nodes) - the debug target "Framework" menu (commandDebuggerFramework.cs), the Publish/Pack commands with PublishDialog, and the priority command target that intercepted VS's own Publish/Pack. That interception also hid or broke Publish/Pack for X# projects loaded by CPS. - the SDK-only parts of the file node (shadow designer redirect), project node (SDK project reference completion), configuration provider and shell events, and the matching Menus.vsct/Menus.cs symbols. The MPFproj factory now rejects an SDK-style project with a clear message instead of creating the removed node. That only happens when the selector cannot use CPS (X# MSBuild support files too old). ProjectSystemCPS: the Tools > Options switch is gone, because MPFproj can no longer load SDK-style projects. The SDK project file check moved to the public SdkProjectFile class, which the MPFproj factory uses as well. Co-Authored-By: Claude Opus 5.5 --- .../ProjectPackage/Commands/CommandBuild.cs | 227 ---- .../Commands/CommandEditProjectFile.cs | 3 - .../ProjectPackage/Commands/CommandPack.cs | 71 -- .../ProjectPackage/Commands/CommandPublish.cs | 244 ---- .../Commands/CommandSyncDesignerChanges.cs | 4 +- .../Commands/CommandSyncEventHandlers.cs | 4 +- .../Commands/PublishDialog.xaml | 100 -- .../Commands/PublishDialog.xaml.cs | 104 -- .../Commands/XSharpBaseCommand.cs | 6 - .../Commands/commandDebuggerFramework.cs | 191 --- .../Editors/XSharpProjectFactory.cs | 38 +- src/VisualStudio/ProjectPackage/Menus.cs | 13 - src/VisualStudio/ProjectPackage/Menus.vsct | 94 -- .../ProjectPackage/Nodes/XSharpFileNode.cs | 57 - .../ProjectPackage/Nodes/XSharpProjectNode.cs | 99 -- .../Nodes/XSharpReferenceContainerNode.cs | 43 +- .../NodesSDK/SdkDependenciesContainer.cs | 172 --- .../ProjectPackage/NodesSDK/SdkFolderNodes.cs | 118 -- .../ProjectPackage/NodesSDK/SdkProjectNode.cs | 1049 ----------------- .../NodesSDK/SdkReferenceNodes.cs | 214 ---- .../ProjectPackage/ProjectPackage.csproj | 1 - .../ProjectPackage/ProjectPackage2022.csproj | 15 - .../ProjectPackage/XSharpConfigprovider.cs | 27 - .../ProjectPackage/XSharpProjectPackage.cs | 8 - .../ProjectPackage/XSharpShellEvents.cs | 37 - .../Options/ProjectSystemOptionsPage.cs | 28 - .../Selection/SdkProjectFile.cs | 54 + .../Selection/XSharpProjectSelector.cs | 60 +- .../XSharpShadowDesignerCommands.cs | 7 +- .../XSharpProjectSystemPackage.cs | 13 +- 30 files changed, 101 insertions(+), 3000 deletions(-) delete mode 100644 src/VisualStudio/ProjectPackage/Commands/CommandBuild.cs delete mode 100644 src/VisualStudio/ProjectPackage/Commands/CommandPack.cs delete mode 100644 src/VisualStudio/ProjectPackage/Commands/CommandPublish.cs delete mode 100644 src/VisualStudio/ProjectPackage/Commands/PublishDialog.xaml delete mode 100644 src/VisualStudio/ProjectPackage/Commands/PublishDialog.xaml.cs delete mode 100644 src/VisualStudio/ProjectPackage/Commands/commandDebuggerFramework.cs delete mode 100644 src/VisualStudio/ProjectPackage/NodesSDK/SdkDependenciesContainer.cs delete mode 100644 src/VisualStudio/ProjectPackage/NodesSDK/SdkFolderNodes.cs delete mode 100644 src/VisualStudio/ProjectPackage/NodesSDK/SdkProjectNode.cs delete mode 100644 src/VisualStudio/ProjectPackage/NodesSDK/SdkReferenceNodes.cs delete mode 100644 src/VisualStudio/ProjectSystemCPS/Options/ProjectSystemOptionsPage.cs create mode 100644 src/VisualStudio/ProjectSystemCPS/Selection/SdkProjectFile.cs diff --git a/src/VisualStudio/ProjectPackage/Commands/CommandBuild.cs b/src/VisualStudio/ProjectPackage/Commands/CommandBuild.cs deleted file mode 100644 index ebc78b3064..0000000000 --- a/src/VisualStudio/ProjectPackage/Commands/CommandBuild.cs +++ /dev/null @@ -1,227 +0,0 @@ -using Community.VisualStudio.Toolkit; - -using Microsoft.VisualStudio; -using OLE=Microsoft.VisualStudio.OLE.Interop; -using Microsoft.VisualStudio.Shell; - -using System; -using System.Diagnostics; -using System.IO; -using System.Threading.Tasks; -using System.Reflection.Metadata.Ecma335; - -namespace XSharp.Project -{ - internal abstract class CommandBuild : BaseCommand where T : class, new() - { - protected abstract string CommandName { get; } - protected abstract string CommandDescription { get; } - protected CommandProgression DoCmd() - { - bool ourProject = false; - ThreadHelper.JoinableTaskFactory.Run(async () => - { - ourProject = await Commands.ProjectIsXSharpProjectAsync(); - if (ourProject) - await DoCmdAsync(); - - - }); - if (ourProject) - return CommandProgression.Stop; - return CommandProgression.Continue; - } - - - protected override async Task InitializeCompletedAsync() - { - await base.InitializeCompletedAsync(); - var cmd = await VS.Commands.FindCommandAsync(CommandName); - if (cmd != null) - { - await VS.Commands.InterceptAsync(CommandName, () => DoCmd()); - } - } - protected string projectPath; - protected async Task VerifySdkProjectAsync() - { - await VS.Commands.ExecuteAsync(KnownCommands.File_SaveAll); - var project = await VS.Solutions.GetActiveProjectAsync(); - if (project == null) - { - await VS.MessageBox.ShowErrorAsync(CommandDescription, "No active project selected."); - return false; - } - projectPath = project.FullPath; - var ourProject = await Commands.ProjectIsXSharpProjectAsync(); - if (!ourProject) - { - return false; - } - var prj = XSharpProjectNode.FindProject(projectPath); - if (prj == null || !prj.IsSdkProject) - { - await VS.MessageBox.ShowErrorAsync(CommandDescription, $"The {CommandDescription} command is only available for SDK-style projects."); - return false; - } - return true; - } - - protected async Task CreateProcessAsync(string arguments, string output) - { - var psi = new ProcessStartInfo - { - FileName = "dotnet", - Arguments = arguments, - WorkingDirectory = Path.GetDirectoryName(projectPath), - RedirectStandardOutput = true, - RedirectStandardError = true, - UseShellExecute = false, - CreateNoWindow = true - }; - var process = new Process { StartInfo = psi }; - process.OutputDataReceived += Process_OutputDataReceived; - process.ErrorDataReceived += Process_ErrorDataReceived; ; - - await EnsureOutputPaneAsync(); - await outputPane.ActivateAsync(); - await outputPane.WriteLineAsync(output); - await outputPane.WriteLineAsync($"Command: dotnet {arguments}"); - await outputPane.WriteLineAsync(""); - process.Start(); - process.BeginOutputReadLine(); - process.BeginErrorReadLine(); - - await Task.Run(() => process.WaitForExit()); - - if (process.ExitCode == 0) - { - await outputPane.WriteLineAsync(""); - var msg = CommandDescription + " succeeded."; - await outputPane.WriteLineAsync(msg); - await VS.StatusBar.ShowMessageAsync(msg); - } - else - { - await outputPane.WriteLineAsync(""); - await outputPane.WriteLineAsync($"{CommandDescription} failed with exit code {process.ExitCode}."); - await VS.StatusBar.ShowMessageAsync(CommandDescription+" failed."); - await VS.MessageBox.ShowErrorAsync(CommandDescription, CommandDescription + " failed. See Output window for details."); - } - - return process.ExitCode; - } - - private void Process_ErrorDataReceived(object sender, DataReceivedEventArgs ea) - { - ThreadHelper.JoinableTaskFactory.Run(async () => - { - if (!string.IsNullOrEmpty(ea.Data)) - { - await outputPane.WriteLineAsync($"ERROR: {ea.Data}"); - } - }); - } - - private void Process_OutputDataReceived(object sender, DataReceivedEventArgs ea) - { - ThreadHelper.JoinableTaskFactory.Run(async () => - { - try - { - if (!string.IsNullOrEmpty(ea.Data)) - { - await outputPane.WriteLineAsync(ea.Data); - } - } - catch { } - }); - - } - - protected override async Task ExecuteAsync(OleMenuCmdEventArgs e) - { - await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); - await DoCmdAsync(); - } - protected abstract Task DoCmdAsync(); - protected static OutputWindowPane outputPane = null; - protected async Task EnsureOutputPaneAsync() - { - - if (outputPane == null) - { - var guid = VSConstants.GUID_BuildOutputWindowPane; - outputPane = await VS.Windows.GetOutputWindowPaneAsync(guid); - } - } - protected override void BeforeQueryStatus(EventArgs e) - { - base.BeforeQueryStatus(e); - ThreadHelper.JoinableTaskFactory.Run(CheckAvailabilityAsync); - } - protected async Task CheckAvailabilityAsync() - { - Command.Visible = await Commands.ProjectIsXSharpProjectAsync(); - if (Command.Visible) - { - var project = await VS.Solutions.GetActiveProjectAsync(); - if (project == null) - { - Command.Visible = false; - Command.Enabled = false; - return; - } - var path = project.FullPath; - var prj = XSharpProjectNode.FindProject(path); - // Only show for SDK-style projects as they support dotnet pack/publish - Command.Visible = prj != null && prj.IsSdkProject; - Command.Enabled = Command.Visible; - } - } - - - } - class BuildCommandFilter : OLE.IOleCommandTarget - { - static readonly Guid PackGuid = new Guid("{568ABDF7-D522-474D-9EED-34B5E5095BA5}"); - - public int QueryStatus(ref Guid pguidCmdGroup, uint cCmds, - OLE.OLECMD[] prgCmds, IntPtr pCmdText) - { - bool isXSharp = false; - ThreadHelper.JoinableTaskFactory.Run(async delegate - { - - isXSharp = await Commands.ProjectIsXSharpProjectAsync(); - }); - if (!isXSharp) - { - return VSConstants.S_OK; - } - var mustCheck = false; - var cmd = prgCmds[0].cmdID; - if (pguidCmdGroup == CommandPack.CommandGroup && cmd == CommandPack.CommandID) - mustCheck = true; - else if (pguidCmdGroup == CommandPublish.CommandGroup && cmd == CommandPublish.CommandID) - mustCheck = true; - if (mustCheck) - { - bool show = false; - ThreadHelper.JoinableTaskFactory.Run(async delegate - { - show = await Commands.ProjectIsXSharpSdkProjectAsync(); - }); - prgCmds[0].cmdf = show - ? (uint)(OLE.OLECMDF.OLECMDF_SUPPORTED | OLE.OLECMDF.OLECMDF_ENABLED) - : (uint)(OLE.OLECMDF.OLECMDF_SUPPORTED | OLE.OLECMDF.OLECMDF_INVISIBLE); - return VSConstants.S_OK; - } - return (int) OLE.Constants.OLECMDERR_E_NOTSUPPORTED; - } - - public int Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, - IntPtr pvaIn, IntPtr pvaOut) - => (int)OLE.Constants.OLECMDERR_E_NOTSUPPORTED; - } -} diff --git a/src/VisualStudio/ProjectPackage/Commands/CommandEditProjectFile.cs b/src/VisualStudio/ProjectPackage/Commands/CommandEditProjectFile.cs index b1536c5349..51c9273b52 100644 --- a/src/VisualStudio/ProjectPackage/Commands/CommandEditProjectFile.cs +++ b/src/VisualStudio/ProjectPackage/Commands/CommandEditProjectFile.cs @@ -55,9 +55,6 @@ protected override async Task ExecuteAsync(OleMenuCmdEventArgs e) var projectNode = XSharpProjectNode.FindProject(project.FullPath); if (projectNode != null && projectNode.QueryEditProjectFile(true)) { -#if DEV17 - projectNode.ClearSdkProjectReferences(); -#endif projectNode.SetProjectFileDirty(false); Directory.CreateDirectory(tempProjectDir); var prjFile = project.FullPath; diff --git a/src/VisualStudio/ProjectPackage/Commands/CommandPack.cs b/src/VisualStudio/ProjectPackage/Commands/CommandPack.cs deleted file mode 100644 index 5b918ffbb3..0000000000 --- a/src/VisualStudio/ProjectPackage/Commands/CommandPack.cs +++ /dev/null @@ -1,71 +0,0 @@ -using Community.VisualStudio.Toolkit; - -using System; -using System.IO; - -using Task = System.Threading.Tasks.Task; - -namespace XSharp.Project -{ - [Command(PackageIds.idPackProject)] - internal sealed class CommandPack : CommandBuild - { - protected override string CommandName => "Build.PackSelection"; - protected override string CommandDescription => "Pack"; - public static readonly Guid CommandGroup = new Guid("{568ABDF7-D522-474D-9EED-34B5E5095BA5}"); - public const int CommandID = 8193; - - protected override async Task DoCmdAsync() - { - if (!await VerifySdkProjectAsync()) - { - return; - } - // Show confirmation dialog - var result = await VS.MessageBox.ShowAsync( - "Create NuGet Package", - "This will create a NuGet package from the project using 'dotnet pack'.\n\n" + - "The package will be created in the project's bin folder.\n\n" + - "Continue?", - Microsoft.VisualStudio.Shell.Interop.OLEMSGICON.OLEMSGICON_QUERY, - Microsoft.VisualStudio.Shell.Interop.OLEMSGBUTTON.OLEMSGBUTTON_OKCANCEL); - - if (result == Microsoft.VisualStudio.VSConstants.MessageBoxResult.IDOK) - { - await PackProjectAsync(projectPath); - } - } - - private async Task PackProjectAsync(string projectPath) - { - try - { - await VS.StatusBar.ShowMessageAsync("Creating NuGet package..."); - - // Build dotnet pack command - var arguments = $"pack \"{projectPath}\" -c Release"; - - var result = await CreateProcessAsync(arguments, - $"Creating NuGet package for: {Path.GetFileName(projectPath)}"); - - if (result == 0) - { - string packagePath = null; - var message = "NuGet package created successfully."; - if (!string.IsNullOrEmpty(packagePath)) - { - message += $"\n\nPackage location:\n{packagePath}"; - } - await VS.MessageBox.ShowAsync(CommandDescription, - message, - Microsoft.VisualStudio.Shell.Interop.OLEMSGICON.OLEMSGICON_INFO, - Microsoft.VisualStudio.Shell.Interop.OLEMSGBUTTON.OLEMSGBUTTON_OK); - } - } - catch (Exception ex) - { - await VS.MessageBox.ShowErrorAsync(CommandDescription+" Error", $"Failed to create NuGet package:\n{ex.Message}"); - } - } - } -} diff --git a/src/VisualStudio/ProjectPackage/Commands/CommandPublish.cs b/src/VisualStudio/ProjectPackage/Commands/CommandPublish.cs deleted file mode 100644 index e1bbb1a89b..0000000000 --- a/src/VisualStudio/ProjectPackage/Commands/CommandPublish.cs +++ /dev/null @@ -1,244 +0,0 @@ -using Community.VisualStudio.Toolkit; - -using Microsoft.VisualStudio; -using Microsoft.VisualStudio.Project; - -using Newtonsoft.Json; - -using System; -using System.ComponentModel; -using System.IO; -using System.Linq; -using System.Runtime.CompilerServices; - -using Task = System.Threading.Tasks.Task; - -namespace XSharp.Project -{ - - - - [Command(PackageIds.idPublishProject)] - internal sealed class CommandPublish : CommandBuild - { - protected override string CommandName => "Build.PublishSelection"; - protected override string CommandDescription => "Publish"; - - public const int CommandID = (int)2005; // ECMD_PUBLISHSELECTION - public static readonly Guid CommandGroup = VsMenus.guidStandardCommandSet2K; - - protected override async Task DoCmdAsync() - { - if (!await VerifySdkProjectAsync()) - { - return; - } - - // Show publish dialog to get options - var dialog = new PublishDialog(projectPath); - var result = dialog.ShowDialog(); - - if (result == true) - { - await PublishProjectAsync(projectPath, dialog.PublishOptions); - } - } - - private async Task PublishProjectAsync(string projectPath, PublishOptions options) - { - try - { - await VS.StatusBar.ShowMessageAsync("Publishing project..."); - - var arguments = BuildPublishArguments(projectPath, options); - options.Save(); - var result = await CreateProcessAsync(arguments, - $"Publishing project: {Path.GetFileName(projectPath)}"); - - if (result == 0) - { - await VS.MessageBox.ShowAsync(CommandDescription, - $"{CommandDescription} published successfully to:\n{options.OutputPath}", - Microsoft.VisualStudio.Shell.Interop.OLEMSGICON.OLEMSGICON_INFO, - Microsoft.VisualStudio.Shell.Interop.OLEMSGBUTTON.OLEMSGBUTTON_OK); - } - } - catch (Exception ex) - { - await VS.MessageBox.ShowErrorAsync(CommandDescription+" Error", $"Failed to publish project:\n{ex.Message}"); - } - } - - private string BuildPublishArguments(string projectPath, PublishOptions options) - { - var args = $"publish \"{projectPath}\""; - - if (!string.IsNullOrEmpty(options.Configuration)) - { - args += $" -c {options.Configuration}"; - } - - if (!string.IsNullOrEmpty(options.TargetFramework)) - { - args += $" -f {options.TargetFramework}"; - } - - if (!string.IsNullOrEmpty(options.Runtime)) - { - args += $" -r {options.Runtime}"; - } - - if (!string.IsNullOrEmpty(options.OutputPath)) - { - args += $" -o \"{options.OutputPath}\""; - } - - if (options.SelfContained.HasValue) - { - args += options.SelfContained.Value ? " --self-contained" : " --no-self-contained"; - } - - if (options.SingleFile) - { - args += " -p:PublishSingleFile=true"; - } - - if (options.ReadyToRun) - { - args += " -p:PublishReadyToRun=true"; - } - - if (options.Trimmed) - { - args += " -p:PublishTrimmed=true"; - } - - return args; - } - } - - public class PublishOptions : INotifyPropertyChanged - { - private string _projectDir; - private string _projectName; - private string _jsonFile; - private string _configuration = "Release"; - private string _targetFramework; - private string _runtime; - private string _outputPath; - private bool? _selfContained; - private bool _singleFile; - private bool _readyToRun; - private bool _trimmed; - - public event PropertyChangedEventHandler PropertyChanged; - - public PublishOptions() - { - - } - public PublishOptions(string projectPath) - { - // Set default output path based on project location - _projectDir = Path.GetDirectoryName(projectPath); - _projectName = Path.GetFileNameWithoutExtension(projectPath); - OutputPath = Path.Combine(_projectDir, "bin", "publish", _projectName); - _jsonFile = Path.Combine(_projectDir, $"{_projectName}.publishsettings.json"); - var project = XSharpProjectNode.FindProject( projectPath ) as XSharpSdkProjectNode; - if (project != null) - { - TargetFramework = project.TargetFrameworks.FirstOrDefault(); - Runtime = "win-x86"; // Default runtime, could be improved to detect from project - } - } - - public string Configuration - { - get => _configuration; - set => SetProperty(ref _configuration, value); - } - - public string TargetFramework - { - get => _targetFramework; - set => SetProperty(ref _targetFramework, value); - } - - public string Runtime - { - get => _runtime; - set => SetProperty(ref _runtime, value); - } - - public string OutputPath - { - get => _outputPath; - set => SetProperty(ref _outputPath, value); - } - - public bool? SelfContained - { - get => _selfContained; - set => SetProperty(ref _selfContained, value); - } - - public bool SingleFile - { - get => _singleFile; - set => SetProperty(ref _singleFile, value); - } - - public bool ReadyToRun - { - get => _readyToRun; - set => SetProperty(ref _readyToRun, value); - } - - public bool Trimmed - { - get => _trimmed; - set => SetProperty(ref _trimmed, value); - } - - protected void OnPropertyChanged([CallerMemberName] string propertyName = null) - { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - } - - protected bool SetProperty(ref T field, T value, [CallerMemberName] string propertyName = null) - { - if (Equals(field, value)) return false; - field = value; - OnPropertyChanged(propertyName); - return true; - } - - public void Save() - { - var str = JsonConvert.SerializeObject(this, Formatting.Indented); - System.IO.File.WriteAllText(_jsonFile, str); - - - } - public void Restore() - { - if (System.IO.File.Exists(_jsonFile)) - { - var str = System.IO.File.ReadAllText(_jsonFile); - var options = JsonConvert.DeserializeObject(str); - if (options != null) - { - Configuration = options.Configuration; - TargetFramework = options.TargetFramework; - Runtime = options.Runtime; - OutputPath = options.OutputPath; - SelfContained = options.SelfContained; - SingleFile = options.SingleFile; - ReadyToRun = options.ReadyToRun; - Trimmed = options.Trimmed; - } - } - - } - } -} diff --git a/src/VisualStudio/ProjectPackage/Commands/CommandSyncDesignerChanges.cs b/src/VisualStudio/ProjectPackage/Commands/CommandSyncDesignerChanges.cs index 57d0166c90..959579cdd7 100644 --- a/src/VisualStudio/ProjectPackage/Commands/CommandSyncDesignerChanges.cs +++ b/src/VisualStudio/ProjectPackage/Commands/CommandSyncDesignerChanges.cs @@ -45,9 +45,9 @@ private async Task CheckAvailabilityAsync() { if (item is PhysicalFile file) { - // SDK-style projects: loaded by CPS (default) or by the MPFproj SDK project node + // SDK-style projects, loaded by the CPS project system var xproject = XSolution.FindFile(file.FullPath)?.Project; - bool sdkProject = ShadowDesignerBridge.IsCpsProject(xproject) || xproject?.ProjectNode is XSharpSdkProjectNode; + bool sdkProject = ShadowDesignerBridge.IsCpsProject(xproject); if (sdkProject && ShadowDesignerBridge.HasDesignerFile(file.FullPath)) { _currentPath = file.FullPath; diff --git a/src/VisualStudio/ProjectPackage/Commands/CommandSyncEventHandlers.cs b/src/VisualStudio/ProjectPackage/Commands/CommandSyncEventHandlers.cs index ef6cf2913a..9cf9fe1d3e 100644 --- a/src/VisualStudio/ProjectPackage/Commands/CommandSyncEventHandlers.cs +++ b/src/VisualStudio/ProjectPackage/Commands/CommandSyncEventHandlers.cs @@ -44,9 +44,9 @@ private async Task CheckAvailabilityAsync() { if (item is PhysicalFile file) { - // SDK-style projects: loaded by CPS (default) or by the MPFproj SDK project node + // SDK-style projects, loaded by the CPS project system var xproject = XSolution.FindFile(file.FullPath)?.Project; - bool sdkProject = ShadowDesignerBridge.IsCpsProject(xproject) || xproject?.ProjectNode is XSharpSdkProjectNode; + bool sdkProject = ShadowDesignerBridge.IsCpsProject(xproject); if (sdkProject && ShadowDesignerBridge.HasDesignerFile(file.FullPath)) { _currentPath = file.FullPath; diff --git a/src/VisualStudio/ProjectPackage/Commands/PublishDialog.xaml b/src/VisualStudio/ProjectPackage/Commands/PublishDialog.xaml deleted file mode 100644 index 5f995c110c..0000000000 --- a/src/VisualStudio/ProjectPackage/Commands/PublishDialog.xaml +++ /dev/null @@ -1,100 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - public static string ComputeCompanionDir(string realXsprojPath) => GetCompanionDir(realXsprojPath); + /// The X# project. + /// Filtered assembly references of the X# project. + /// The merged form class (.prg + .Designer.prg) as C#. + /// The Form1.cs-equivalent stub: the partial class with the same base types and + /// imports as the generated designer code (see ShadowDesignerBridge.BuildStubCSharp). + /// The form class. public static CompanionPaths EnsureCompanionProject( string realXsprojPath, IReadOnlyList referencePaths, string generatedDesignerCsharp, - string namespaceName, + string stubCsharp, string className) { string companionDir = GetCompanionDir(realXsprojPath); @@ -124,12 +131,13 @@ public static CompanionPaths EnsureCompanionProject( WriteIfChanged(paths.CsprojPath, BuildCsprojContent(targetFramework, filteredReferencePaths, packageReferences)); WriteIfChanged(paths.DesignerCsPath, generatedDesignerCsharp); - // Only written once -- an "ordinary user file" slot, not regenerated each run, so - // a future manual tweak here survives re-runs. + // The stub is where the Designer puts new members (e.g. event handler stubs, picked up by + // EventHandlerSync), so it is only regenerated while it is still an empty generated stub -- e.g. one + // that was written with an outdated base class. string stubCsPath = ComputeFormCsPath(realXsprojPath, className); - if (!File.Exists(stubCsPath)) + if (!File.Exists(stubCsPath) || IsEmptyStub(File.ReadAllText(stubCsPath))) { - File.WriteAllText(stubCsPath, BuildStubContent(namespaceName, className)); + WriteIfChanged(stubCsPath, stubCsharp); } return paths; @@ -225,16 +233,21 @@ internal static List ReadPackageReferences(string xsprojPath) return result; } - private static string BuildStubContent(string namespaceName, string className) + // An empty generated stub: comments, usings, optional namespace and a partial class without members + private static readonly Regex EmptyStubRegex = new Regex( + @"^\s*(namespace\s+[\w.]+\s*\{)?\s*public\s+partial\s+class\s+\w+(\s*:[^{]*)?\s*\{\s*\}\s*\}?\s*$", + RegexOptions.Singleline); + + /// + /// True when is an empty generated stub (from this writer), i.e. a file that can be + /// regenerated without losing anything. + /// + internal static bool IsEmptyStub(string content) { - return -$@"namespace {namespaceName} -{{ - public partial class {className} : System.Windows.Forms.Form - {{ - }} -}} -"; + var lines = content.Replace("\r", "").Split('\n') + .Select(l => l.Trim()) + .Where(l => !l.StartsWith("//", StringComparison.Ordinal) && !(l.StartsWith("using ", StringComparison.Ordinal) && l.EndsWith(";", StringComparison.Ordinal))); + return EmptyStubRegex.IsMatch(string.Join("\n", lines)); } private static string Escape(string value) => System.Security.SecurityElement.Escape(value); diff --git a/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/ShadowDesignerBridge.cs b/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/ShadowDesignerBridge.cs index af134445e2..df5832c7d6 100644 --- a/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/ShadowDesignerBridge.cs +++ b/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/ShadowDesignerBridge.cs @@ -12,6 +12,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Reflection; using EnvDTE80; using Microsoft.CSharp; using Microsoft.VisualStudio.Shell; @@ -163,12 +164,14 @@ public static bool TryOpen(string mainPrgPath, XProject xProject, Action refresh string namespaceName = mergedNamespace?.Name ?? "GeneratedShadow"; string className = mergedType?.Name ?? Path.GetFileNameWithoutExtension(mainPrgPath); + string stubCSharp = BuildStubCSharp(mergedNamespace, mergedType, namespaceName, className); + var referencePaths = GetFilteredReferencePaths(xProject); // A background deletion from the previous solution close may still be retrying on this folder ShadowDesignerCleanup.CancelPendingDelete(CompanionProjectWriter.ComputeCompanionDir(xProject.FileName)); var companion = CompanionProjectWriter.EnsureCompanionProject( - xProject.FileName, referencePaths, shadowCSharp, namespaceName, className); + xProject.FileName, referencePaths, shadowCSharp, stubCSharp, className); CompanionSaveWatcher.Watch(ServiceProvider.GlobalProvider, new CompanionLocation { @@ -325,6 +328,44 @@ private static bool EnsureBuilt(DTE2 dte, XProject xProject, out string error) } } + /// + /// The companion's Form1.cs-equivalent stub: the partial form class with the same base types and namespace + /// imports as the generated designer code, generated by the same CSharpCodeProvider. Both partial + /// declarations therefore name the same base class (a hard-coded Form gave CS0263 for user controls). + /// + private static string BuildStubCSharp(CodeNamespace mergedNamespace, CodeTypeDeclaration mergedType, string namespaceName, string className) + { + var ns = new CodeNamespace(namespaceName); + if (mergedNamespace != null) + { + foreach (CodeNamespaceImport import in mergedNamespace.Imports) + { + ns.Imports.Add(new CodeNamespaceImport(import.Namespace)); + } + } + var type = new CodeTypeDeclaration(className) + { + IsClass = true, + IsPartial = true, + TypeAttributes = TypeAttributes.Public, + }; + if (mergedType != null) + { + foreach (CodeTypeReference baseType in mergedType.BaseTypes) + { + type.BaseTypes.Add(baseType); + } + } + ns.Types.Add(type); + var unit = new CodeCompileUnit(); + unit.Namespaces.Add(ns); + using (var writer = new StringWriter()) + { + new CSharpCodeProvider().GenerateCodeFromCompileUnit(unit, writer, new CodeGeneratorOptions { BracingStyle = "C" }); + return writer.ToString(); + } + } + private static XCodeCompileUnit ToXCodeCompileUnit(CodeCompileUnit unit) => unit is XCodeCompileUnit xccu ? xccu : new XCodeCompileUnit(unit); From 8443647ce2e1f07e0336b224936f0eba3161fe80 Mon Sep 17 00:00:00 2001 From: Hansjoerg Petriffer Date: Mon, 28 Sep 2026 15:02:31 +0200 Subject: [PATCH 35/60] [VsIntegration] Shadow designer: companion target framework from the loaded project The companion project took its target framework only from in the .xsproj XML, so multi-targeting projects () and a TargetFramework from Directory.Build.props made the designer fail. The bridge now passes the active target framework of the loaded project (VSHPROPID_TargetFrameworkMoniker of its hierarchy, converted to the short form: net8.0, net48, netcoreapp3.1, ...). Without it the writer falls back to the project file, now including the first entry of . The "-windows" suffix is still appended as before. Verified: moniker conversion for .NETCoreApp 8.0/3.1 and .NETFramework 4.8/4.7.2; the test bed companion gets net8.0-windows. Co-Authored-By: Claude Opus 5.5 --- .../ShadowDesigner/CompanionProjectWriter.cs | 32 +++++++-- .../ShadowDesigner/ShadowDesignerBridge.cs | 70 ++++++++++++++++++- 2 files changed, 94 insertions(+), 8 deletions(-) diff --git a/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/CompanionProjectWriter.cs b/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/CompanionProjectWriter.cs index 7b154b4335..4c3c5dacbc 100644 --- a/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/CompanionProjectWriter.cs +++ b/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/CompanionProjectWriter.cs @@ -93,6 +93,8 @@ private static string GetCompanionDir(string realXsprojPath) public static string ComputeCompanionDir(string realXsprojPath) => GetCompanionDir(realXsprojPath); /// The X# project. + /// The active target framework of the loaded project (short form, e.g. + /// net8.0), or null to read it from the project file. /// Filtered assembly references of the X# project. /// The merged form class (.prg + .Designer.prg) as C#. /// The Form1.cs-equivalent stub: the partial class with the same base types and @@ -100,6 +102,7 @@ private static string GetCompanionDir(string realXsprojPath) /// The form class. public static CompanionPaths EnsureCompanionProject( string realXsprojPath, + string evaluatedTargetFramework, IReadOnlyList referencePaths, string generatedDesignerCsharp, string stubCsharp, @@ -108,7 +111,7 @@ public static CompanionPaths EnsureCompanionProject( string companionDir = GetCompanionDir(realXsprojPath); Directory.CreateDirectory(companionDir); - string targetFramework = ReadTargetFramework(realXsprojPath); + string targetFramework = GetTargetFramework(realXsprojPath, evaluatedTargetFramework); // Read items directly out of the real .xsproj's XML (Include + // Version only, no MSBuild evaluation) and emit equivalent items into the @@ -143,15 +146,30 @@ public static CompanionPaths EnsureCompanionProject( return paths; } - private static string ReadTargetFramework(string xsprojPath) + /// + /// The target framework of the companion project: the evaluated (active) target framework of the loaded + /// project, which also covers multi-targeting and a TargetFramework set in Directory.Build.props; otherwise + /// the first of <TargetFramework> / <TargetFrameworks> in the project file. + /// + private static string GetTargetFramework(string xsprojPath, string evaluatedTargetFramework) { - var doc = XDocument.Load(xsprojPath); - var element = doc.Descendants(XSharpProjectFileConstants.TargetFramework).FirstOrDefault(); - if (element == null) + string tfm = evaluatedTargetFramework; + if (string.IsNullOrEmpty(tfm)) { - throw new InvalidOperationException($"No element found in {xsprojPath}."); + var doc = XDocument.Load(xsprojPath); + tfm = doc.Descendants(XSharpProjectFileConstants.TargetFramework).FirstOrDefault()?.Value?.Trim(); + if (string.IsNullOrEmpty(tfm)) + { + tfm = doc.Descendants("TargetFrameworks").FirstOrDefault()?.Value + ?.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries) + .Select(t => t.Trim()) + .FirstOrDefault(t => t.Length > 0 && t.IndexOf('$') < 0); + } + if (string.IsNullOrEmpty(tfm)) + { + throw new InvalidOperationException($"Could not determine the target framework of {xsprojPath}."); + } } - string tfm = element.Value; // The out-of-process Designer's host-process launcher requires a Windows-Desktop // TFM (observed empirically as a "Timed out while connecting to the named pipe" diff --git a/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/ShadowDesignerBridge.cs b/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/ShadowDesignerBridge.cs index df5832c7d6..8ee9afeb7b 100644 --- a/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/ShadowDesignerBridge.cs +++ b/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/ShadowDesignerBridge.cs @@ -13,8 +13,10 @@ using System.IO; using System.Linq; using System.Reflection; +using System.Runtime.Versioning; using EnvDTE80; using Microsoft.CSharp; +using Microsoft.VisualStudio; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; using XSharp.CodeDom; @@ -171,7 +173,7 @@ public static bool TryOpen(string mainPrgPath, XProject xProject, Action refresh // A background deletion from the previous solution close may still be retrying on this folder ShadowDesignerCleanup.CancelPendingDelete(CompanionProjectWriter.ComputeCompanionDir(xProject.FileName)); var companion = CompanionProjectWriter.EnsureCompanionProject( - xProject.FileName, referencePaths, shadowCSharp, stubCSharp, className); + xProject.FileName, GetEvaluatedTargetFramework(xProject.FileName), referencePaths, shadowCSharp, stubCSharp, className); CompanionSaveWatcher.Watch(ServiceProvider.GlobalProvider, new CompanionLocation { @@ -328,6 +330,72 @@ private static bool EnsureBuilt(DTE2 dte, XProject xProject, out string error) } } + private static IVsHierarchy FindHierarchy(string projectFile) + { + ThreadHelper.ThrowIfNotOnUIThread(); + if (!(ServiceProvider.GlobalProvider.GetService(typeof(SVsSolution)) is IVsSolution solution)) + { + return null; + } + Guid ignored = Guid.Empty; + if (ErrorHandler.Failed(solution.GetProjectEnum((uint)__VSENUMPROJFLAGS.EPF_LOADEDINSOLUTION, ref ignored, out IEnumHierarchies hierarchies)) || hierarchies == null) + { + return null; + } + var buffer = new IVsHierarchy[1]; + while (hierarchies.Next(1, buffer, out uint fetched) == VSConstants.S_OK && fetched == 1) + { + if (buffer[0] is IVsProject project && + ErrorHandler.Succeeded(project.GetMkDocument(VSConstants.VSITEMID_ROOT, out string mkDocument)) && + string.Equals(mkDocument, projectFile, StringComparison.OrdinalIgnoreCase)) + { + return buffer[0]; + } + } + return null; + } + + /// + /// The active target framework of the loaded X# project in short form (net8.0, net48, ...), from the + /// hierarchy (VSHPROPID_TargetFrameworkMoniker): unlike the project file XML it covers + /// <TargetFrameworks> and a TargetFramework from Directory.Build.props. Null when not available; + /// the companion writer then reads the project file. + /// + private static string GetEvaluatedTargetFramework(string projectFile) + { + try + { + var hierarchy = FindHierarchy(projectFile); + if (hierarchy == null || + ErrorHandler.Failed(hierarchy.GetProperty(VSConstants.VSITEMID_ROOT, (int)__VSHPROPID4.VSHPROPID_TargetFrameworkMoniker, out object value)) || + !(value is string moniker) || string.IsNullOrEmpty(moniker)) + { + return null; + } + return ToShortTargetFramework(new FrameworkName(moniker)); + } + catch (Exception ex) + { + Logger.Exception(ex, "ShadowDesignerBridge.GetEvaluatedTargetFramework"); + return null; + } + } + + private static string ToShortTargetFramework(FrameworkName framework) + { + var version = framework.Version; + switch (framework.Identifier) + { + case ".NETFramework": + // v4.8 -> net48, v4.7.2 -> net472 + return "net" + version.ToString().Replace(".", ""); + case ".NETCoreApp": + return (version.Major >= 5 ? "net" : "netcoreapp") + version.Major + "." + version.Minor; + default: + return null; + } + } + /// /// The companion's Form1.cs-equivalent stub: the partial form class with the same base types and namespace /// imports as the generated designer code, generated by the same CSharpCodeProvider. Both partial From 2f621d0dfdb75a01e7cec600fdfb16c18fc43e7c Mon Sep 17 00:00:00 2001 From: Hansjoerg Petriffer Date: Mon, 28 Sep 2026 15:02:35 +0200 Subject: [PATCH 36/60] [VsIntegration] Shadow designer: wait for the project load instead of a blocking build When the package references of the X# project were not resolved yet, the bridge built the project with EnvDTE BuildProject(WaitForBuildToFinish: true) -- a nested message pump inside the async CPS command handler. Under CPS a build does not even refresh the code model's references: they come from the design-time build (XSharpProjectAdapter), which runs after the NuGet restore. ShadowDesignerBridge.EnsureReferencesAsync (called by the CPS commands before TryOpen) now first waits asynchronously for the IntelliSense stage of the project load (IVsOperationProgressStatusService, up to 60 s) and briefly for the adapter to process the result. Only when the references are still missing it offers a build as before, which now runs through IVsSolutionBuildManager2.StartSimpleUpdateProjectConfiguration and completes through IVsUpdateSolutionEvents. A second double click during the wait is swallowed. TryOpen loses its refreshReferences parameter (the MPFproj .rsp re-read); the class docs no longer mention the removed MPFproj SDK caller. Not tested in VS: the path only runs for a project with NuGet references before its restore has finished. Co-Authored-By: Claude Opus 5.5 --- .../ShadowDesigner/ShadowDesignerBridge.cs | 180 +++++++++++++----- .../XSharpShadowDesignerCommands.cs | 32 +++- 2 files changed, 155 insertions(+), 57 deletions(-) diff --git a/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/ShadowDesignerBridge.cs b/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/ShadowDesignerBridge.cs index 8ee9afeb7b..235f14aaf2 100644 --- a/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/ShadowDesignerBridge.cs +++ b/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/ShadowDesignerBridge.cs @@ -14,9 +14,11 @@ using System.Linq; using System.Reflection; using System.Runtime.Versioning; +using System.Threading.Tasks; using EnvDTE80; using Microsoft.CSharp; using Microsoft.VisualStudio; +using Microsoft.VisualStudio.OperationProgress; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; using XSharp.CodeDom; @@ -37,8 +39,8 @@ namespace XSharp.ProjectSystem.ShadowDesigner /// /// XSharpCodeParser/XSharpCodeDomHelper/XProject are called directly -- no reflection is /// needed to reach them. The bridge only needs the path of the .prg and the X# code model - /// (XProject) of its project, so it serves both project systems: the CPS project system - /// (XSharpShadowDesignerDefaultActionCommand, XSharpShadowDesignerViewFormCommand) and the MPFproj SDK project node (XSharpFileNode). + /// (XProject) of its project. Entry points: the CPS commands XSharpShadowDesignerDefaultActionCommand and + /// XSharpShadowDesignerViewFormCommand (MPFproj no longer loads SDK-style projects). /// /// Also exposes , used by /// and to locate an @@ -83,15 +85,14 @@ public static bool HasDesignerFile(string prgPath) /// /// Attempts to open the shadow Designer for (expected to /// be a SDK-style project's .prg file with a matching .Designer.prg), using the code model - /// of its project. is called - /// after a build that was needed to resolve the references (MPFproj reads them from the - /// response file); CPS projects pass null, their references come from the design-time build. Returns false with an + /// of its project. Call first: this method + /// does not wait for or build anything. Returns false with an /// error message on failure -- callers should fall back to whatever they'd otherwise /// have done (e.g. today's "does not support project" Designer error) rather than /// throwing, since a partially-set-up solution (mid-restore, no build yet) is a /// normal, recoverable condition, not a bug. /// - public static bool TryOpen(string mainPrgPath, XProject xProject, Action refreshReferences, out string error) + public static bool TryOpen(string mainPrgPath, XProject xProject, out string error) { ThreadHelper.ThrowIfNotOnUIThread(); try @@ -116,28 +117,6 @@ public static bool TryOpen(string mainPrgPath, XProject xProject, Action refresh return false; } - // A project that has never been built this session has no .rsp response file, - // so XProject.AssemblyReferences has nothing to resolve -- any 3rd-party type - // reference silently corrupts the generated code instead of failing loudly (a - // multi-segment member-access chain like "oControl1:SomeProperty := x" - // collapses to a bare "oControl1 = x"). Confirmed empirically that VS's own - // automatic design-time ("Sync") build does NOT resolve 3rd-party/NuGet - // references either (only plain framework reference-assembly paths show up) -- - // a real build is genuinely required, not just avoidable overhead. Ensure a - // build has happened before parsing. - if (IsMissingAnyPackageReference(xProject)) - { - if (!EnsureBuilt(dte, xProject, out error)) - { - return false; - } - // Don't trust that BuildEnded(true)'s own RefreshReferences() call (fired - // from XSharpIDEBuildLogger's MSBuild logger callback) has already - // completed by the time the synchronous BuildProject(...) call above - // returned -- force a synchronous re-read right now instead. - refreshReferences?.Invoke(); - } - // The parser resolves System.Windows.Forms.Form & co. through the project's assembly references. // Shortly after the solution loaded they can still be queued (XProject.ResolveReferences runs at // most every 15 seconds); unresolved types make the parser drop the right-hand side of assignments @@ -284,50 +263,151 @@ private static bool IsMissingAnyPackageReference(XProject xProject) return GetFilteredReferencePaths(xProject).Count == 0; } + // How long to wait for the project load (NuGet restore + design-time build) before offering a build + private static readonly TimeSpan ProjectLoadTimeout = TimeSpan.FromSeconds(60); + // The adapter processes a design-time build result asynchronously, shortly after the load stage completed + private static readonly TimeSpan ReferenceUpdateTimeout = TimeSpan.FromSeconds(3); + /// - /// Builds the real project via VS's own build pipeline (EnvDTE SolutionBuild, not a - /// separately-spawned dotnet.exe process) so XSharpIDEBuildLogger's BuildEnded hook - /// fires normally and refreshes XProject.AssemblyReferences the same way a manual - /// Build would. Prompts for confirmation first unless - /// XCustomEditorSettings.AutoBuildForShadowDesigner is set (Tools > Options > X# - /// Project System > Other Editor Options > Windows Forms Editor). + /// Makes sure that the assembly references of the project's packages are known to the code model before the + /// forms are parsed: without them any 3rd-party type reference silently corrupts the generated code instead + /// of failing loudly (a multi-segment member-access chain like "oControl1:SomeProperty := x" collapses to a + /// bare "oControl1 = x"). /// - private static bool EnsureBuilt(DTE2 dte, XProject xProject, out string error) + /// + /// Under CPS the references come from the design-time build (XSharpProjectAdapter), which runs after the NuGet + /// restore. When they are missing (shortly after the solution was opened, or while restoring) this waits + /// asynchronously for the IntelliSense stage of the project load. Only when they are still missing, a build is + /// offered (the build also restores); it runs asynchronously (IVsSolutionBuildManager), without the nested + /// message pump of EnvDTE's BuildProject(WaitForBuildToFinish: true) inside the CPS command handler. + /// Returns false with an error when the user declined the build or it could not be started. + /// + public static async Task<(bool Ok, string Error)> EnsureReferencesAsync(XProject xProject) { - error = null; + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + if (!IsMissingAnyPackageReference(xProject)) + { + return (true, null); + } + Logger.Information("ShadowDesignerBridge: package references not resolved yet, waiting for the project load"); + await WaitForReferencesAsync(xProject); + if (!IsMissingAnyPackageReference(xProject)) + { + return (true, null); + } + bool proceed = XCustomEditorSettings.AutoBuildForShadowDesigner; if (!proceed) { proceed = VsShellUtilities.ShowMessageBox(ServiceProvider.GlobalProvider, - "This project needs to be built at least once so X# can resolve its " + - "assembly references for the Designer.\n\nBuild now?", + "X# could not resolve the package references of this project for the Designer yet. " + + "Building the project restores and resolves them.\n\nBuild now?", "X# WinForms Designer", OLEMSGICON.OLEMSGICON_QUERY, OLEMSGBUTTON.OLEMSGBUTTON_OKCANCEL, OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST) == 1; // IDOK } if (!proceed) { - error = "Cancelled -- build the project manually, then try View Designer again."; - return false; + return (false, "Cancelled -- build the project manually, then try View Designer again."); + } + string buildError = await BuildAsync(xProject.FileName); + if (buildError != null) + { + return (false, buildError); } + await WaitForReferencesAsync(xProject); + // Like before: continue even when references are still missing (e.g. a failed build) + return (true, null); + } - var project = SolutionWiring.FindProjectByFullPath(dte, xProject.FileName); - if (project == null) + private static async Task WaitForReferencesAsync(XProject xProject) + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + if (await AsyncServiceProvider.GlobalProvider.GetServiceAsync(typeof(SVsOperationProgressStatusService)) is IVsOperationProgressStatusService progress) { - error = $"Could not find an open project matching {xProject.FileName} in the solution."; - return false; + var stage = progress.GetStageStatusForSolutionLoad(CommonOperationProgressStageIds.Intellisense); + if (stage != null && stage.IsInProgress) + { + await Task.WhenAny(stage.WaitForCompletionAsync(), Task.Delay(ProjectLoadTimeout)); + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + } + } + var deadline = DateTime.UtcNow + ReferenceUpdateTimeout; + while (IsMissingAnyPackageReference(xProject) && DateTime.UtcNow < deadline) + { + await Task.Delay(250); + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); } + } + + /// + /// Builds the X# project through the solution build manager and waits asynchronously for the build to end. + /// Returns an error message when the build could not be started, null otherwise (also for a failed build). + /// + private static async Task BuildAsync(string projectFile) + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + var hierarchy = FindHierarchy(projectFile); + if (hierarchy == null) + { + return $"Could not find an open project matching {projectFile} in the solution."; + } + if (!(await AsyncServiceProvider.GlobalProvider.GetServiceAsync(typeof(SVsSolutionBuildManager)) is IVsSolutionBuildManager2 buildManager)) + { + return "Could not obtain the solution build manager."; + } + if (ErrorHandler.Succeeded(buildManager.QueryBuildManagerBusy(out int busy)) && busy != 0) + { + return "A build is already running -- try View Designer again when it has finished."; + } + var completion = new BuildCompletion(); + ErrorHandler.ThrowOnFailure(buildManager.AdviseUpdateSolutionEvents(completion, out uint cookie)); try { - string configName = dte.Solution.SolutionBuild.ActiveConfiguration.Name; - dte.Solution.SolutionBuild.BuildProject(configName, project.UniqueName, WaitForBuildToFinish: true); - return true; + int hr = buildManager.StartSimpleUpdateProjectConfiguration(hierarchy, null, null, + (uint)VSSOLNBUILDUPDATEFLAGS.SBF_OPERATION_BUILD, 0, 0); + if (ErrorHandler.Failed(hr)) + { + return $"The build could not be started (0x{hr:X8})."; + } + bool succeeded = await completion.Task; + Logger.Information($"ShadowDesignerBridge: build of {projectFile} finished, succeeded: {succeeded}"); + return null; } - catch (Exception ex) + finally { - error = $"Build failed: {ex.Message}"; - return false; + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + buildManager.UnadviseUpdateSolutionEvents(cookie); + } + } + + /// + /// Completes when the next solution build (the one started by BuildAsync) ends. + /// + private sealed class BuildCompletion : IVsUpdateSolutionEvents + { + private readonly TaskCompletionSource completion = + new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + public Task Task => completion.Task; + + public int UpdateSolution_Begin(ref int pfCancelUpdate) => VSConstants.S_OK; + + public int UpdateSolution_Done(int fSucceeded, int fModified, int fCancelCommand) + { + completion.TrySetResult(fSucceeded != 0); + return VSConstants.S_OK; + } + + public int UpdateSolution_StartUpdate(ref int pfCancelUpdate) => VSConstants.S_OK; + + public int UpdateSolution_Cancel() + { + completion.TrySetResult(false); + return VSConstants.S_OK; } + + public int OnActiveProjectCfgChange(IVsHierarchy pIVsHierarchy) => VSConstants.S_OK; } private static IVsHierarchy FindHierarchy(string projectFile) diff --git a/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/XSharpShadowDesignerCommands.cs b/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/XSharpShadowDesignerCommands.cs index 500531944e..5780004f8f 100644 --- a/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/XSharpShadowDesignerCommands.cs +++ b/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/XSharpShadowDesignerCommands.cs @@ -38,19 +38,37 @@ public static bool TryGetFormFile(IImmutableSet nodes, out string return true; } + // Set while an open waits for the project load or a build (UI thread only): a second double click in that + // time is swallowed instead of starting a second wait/build. + private static bool opening; + public static async Task TryOpenAsync(IProjectThreadingService threading, string path) { await threading.SwitchToUIThread(); - var xProject = XSolution.FindFile(path)?.Project; - if (xProject == null) + if (opening) + return true; + opening = true; + try { - Logger.Information("XSharp ShadowDesigner: no X# code model for " + path); + var xProject = XSolution.FindFile(path)?.Project; + if (xProject == null) + { + Logger.Information("XSharp ShadowDesigner: no X# code model for " + path); + return false; + } + var (ok, error) = await ShadowDesignerBridge.EnsureReferencesAsync(xProject); + await threading.SwitchToUIThread(); + // The project can have been unloaded or reloaded while waiting + xProject = XSolution.FindFile(path)?.Project; + if (ok && xProject != null && ShadowDesignerBridge.TryOpen(path, xProject, out error)) + return true; + Logger.Information("XSharp ShadowDesigner: " + (error ?? "no X# code model for " + path)); return false; } - if (ShadowDesignerBridge.TryOpen(path, xProject, null, out var error)) - return true; - Logger.Information("XSharp ShadowDesigner: " + error); - return false; + finally + { + opening = false; + } } public static Task EnabledStatusAsync(string commandText) => From f11ac1d5fdd39c580e18ed255816ba101a6d9690 Mon Sep 17 00:00:00 2001 From: Hansjoerg Petriffer Date: Mon, 28 Sep 2026 15:02:39 +0200 Subject: [PATCH 37/60] [VsIntegration] Shadow designer: add new form files to an already loaded companion project When the companion project was already loaded (another form of the project was opened before), the bridge wrote the new form's .cs/.Designer.cs into its folder and opened the designer immediately. The companion includes its files through the SDK globs, but it did not pick them up in time -- verified in VS, not even after 10 s -- so the designer failed with "... is not contained within a project that supports code" and only worked on a second attempt. TryOpen is split into TryPrepareCompanion (parse, write, add to the solution) and TryOpenAsync: missing files are added with ProjectItems.AddFromFile, then it waits (up to 10 s) until IVsProject.IsDocumentInProject finds them, and only then opens the designer. For files covered by a glob CPS writes no explicit item into the companion .csproj (verified: the .csproj stays unchanged). Tested in VS: open a user control (loads the companion), then Form1 -> the designer opens on the first attempt. Co-Authored-By: Claude Opus 5.5 --- .../ShadowDesigner/CompanionSaveWatcher.cs | 2 +- .../ShadowDesigner/ShadowDesignerBridge.cs | 113 ++++++++++++++++-- .../ShadowDesigner/ShadowDesignerCleanup.cs | 4 +- .../XSharpShadowDesignerCommands.cs | 8 +- 4 files changed, 111 insertions(+), 16 deletions(-) diff --git a/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/CompanionSaveWatcher.cs b/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/CompanionSaveWatcher.cs index 3e03215f11..7ab099f127 100644 --- a/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/CompanionSaveWatcher.cs +++ b/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/CompanionSaveWatcher.cs @@ -66,7 +66,7 @@ private CompanionSaveWatcher(IVsRunningDocumentTable rdt, DTE2 dte) /// /// Registers a companion's paths for auto-sync-on-save, lazily advising the RDT the /// first time this is called in the session. Safe to call every time - /// ShadowDesignerBridge.TryOpen succeeds -- idempotent (overwrites any existing + /// ShadowDesignerBridge.TryOpenAsync prepares the companion -- idempotent (overwrites any existing /// entry for the same paths). /// public static void Watch(IServiceProvider services, ShadowDesignerBridge.CompanionLocation location) diff --git a/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/ShadowDesignerBridge.cs b/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/ShadowDesignerBridge.cs index 235f14aaf2..2aebcd92c4 100644 --- a/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/ShadowDesignerBridge.cs +++ b/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/ShadowDesignerBridge.cs @@ -86,15 +86,109 @@ public static bool HasDesignerFile(string prgPath) /// Attempts to open the shadow Designer for (expected to /// be a SDK-style project's .prg file with a matching .Designer.prg), using the code model /// of its project. Call first: this method - /// does not wait for or build anything. Returns false with an + /// does not wait for the references or build anything. Returns false with an /// error message on failure -- callers should fall back to whatever they'd otherwise /// have done (e.g. today's "does not support project" Designer error) rather than /// throwing, since a partially-set-up solution (mid-restore, no build yet) is a /// normal, recoverable condition, not a bug. /// - public static bool TryOpen(string mainPrgPath, XProject xProject, out string error) + public static async Task<(bool Ok, string Error)> TryOpenAsync(string mainPrgPath, XProject xProject) + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + if (!TryPrepareCompanion(mainPrgPath, xProject, out var location, out string error)) + { + return (false, error); + } + try + { + // The companion project includes its files through the SDK globs. When it was already loaded (another + // form of this project was opened before), it only picks up newly written files after its file watcher + // and a re-evaluation. Opening the designer before that fails with "... is not contained within a + // project that supports code" -- wait until the project contains the files. + await WaitForCompanionFilesAsync(location); + var dte = await AsyncServiceProvider.GlobalProvider.GetServiceAsync(typeof(SDTE)) as DTE2; + if (dte == null) + { + return (false, "Could not obtain the DTE service."); + } + bool opened = SolutionWiring.TryOpenInDesigner(dte, location.CompanionDesignerCsPath, out error); + if (!opened) + { + Logger.Error($"ShadowDesignerBridge.TryOpenAsync: {error}"); + } + return (opened, error); + } + catch (Exception ex) + { + Logger.Exception(ex, "ShadowDesignerBridge.TryOpenAsync"); + return (false, ex.ToString()); + } + } + + // How long to wait for an already loaded companion project to contain newly written files + private static readonly TimeSpan CompanionFilesTimeout = TimeSpan.FromSeconds(10); + + /// + /// Verified in VS: the companion project did not pick up new files through its globs even after 10 s. + /// So the missing files are added explicitly (ProjectItems.AddFromFile); for a file that a glob of the + /// SDK-style project already covers, CPS does not write an explicit item into the .csproj. + /// + private static async Task WaitForCompanionFilesAsync(CompanionLocation location) + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + var project = FindHierarchy(location.CompanionCsprojPath) as IVsProject; + if (project == null) + { + // Not loaded yet: a freshly added project (SolutionWiring.EnsureProjectInSolution) loads with its files + return; + } + var files = new[] { location.CompanionFormCsPath, location.CompanionDesignerCsPath }; + var missing = files.Where(f => !IsInProject(project, f)).ToList(); + if (missing.Count == 0) + { + return; + } + try + { + var dte = await AsyncServiceProvider.GlobalProvider.GetServiceAsync(typeof(SDTE)) as DTE2; + var dteProject = dte == null ? null : SolutionWiring.FindProjectByFullPath(dte, location.CompanionCsprojPath); + foreach (var file in missing) + { + Logger.Information($"ShadowDesignerBridge: adding {file} to the companion project"); + dteProject?.ProjectItems.AddFromFile(file); + } + } + catch (Exception ex) + { + Logger.Exception(ex, "ShadowDesignerBridge: could not add the files to the companion project"); + } + var deadline = DateTime.UtcNow + CompanionFilesTimeout; + while (!files.All(f => IsInProject(project, f))) + { + if (DateTime.UtcNow >= deadline) + { + Logger.Information($"ShadowDesignerBridge: {location.CompanionDesignerCsPath} is not part of the companion project after {CompanionFilesTimeout.TotalSeconds} s, opening anyway"); + return; + } + await Task.Delay(200); + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + } + } + + private static bool IsInProject(IVsProject project, string file) + { + var priority = new VSDOCUMENTPRIORITY[1]; + return ErrorHandler.Succeeded(project.IsDocumentInProject(file, out int found, priority, out uint _)) && found != 0; + } + + /// + /// Parses the form, writes the companion project and makes sure it is part of the solution + /// (everything except opening the designer, see ). + /// + private static bool TryPrepareCompanion(string mainPrgPath, XProject xProject, out CompanionLocation location, out string error) { ThreadHelper.ThrowIfNotOnUIThread(); + location = null; try { if (xProject == null) @@ -154,28 +248,25 @@ public static bool TryOpen(string mainPrgPath, XProject xProject, out string err var companion = CompanionProjectWriter.EnsureCompanionProject( xProject.FileName, GetEvaluatedTargetFramework(xProject.FileName), referencePaths, shadowCSharp, stubCSharp, className); - CompanionSaveWatcher.Watch(ServiceProvider.GlobalProvider, new CompanionLocation + location = new CompanionLocation { MainPrgPath = mainPrgPath, DesignerPrgPath = designerPrgPath, CompanionCsprojPath = companion.CsprojPath, CompanionFormCsPath = CompanionProjectWriter.ComputeFormCsPath(xProject.FileName, className), CompanionDesignerCsPath = companion.DesignerCsPath, - }); + }; + CompanionSaveWatcher.Watch(ServiceProvider.GlobalProvider, location); ShadowDesignerCleanup.Track(companion.CsprojPath); SolutionWiring.EnsureProjectInSolution(dte, companion.CsprojPath); - bool opened = SolutionWiring.TryOpenInDesigner(dte, companion.DesignerCsPath, out error); - if (!opened) - { - Logger.Error($"ShadowDesignerBridge.TryOpen: {error}"); - } - return opened; + error = null; + return true; } catch (Exception ex) { error = ex.ToString(); - Logger.Exception(ex, "ShadowDesignerBridge.TryOpen"); + Logger.Exception(ex, "ShadowDesignerBridge.TryPrepareCompanion"); return false; } } diff --git a/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/ShadowDesignerCleanup.cs b/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/ShadowDesignerCleanup.cs index afc734b5da..b0c0864330 100644 --- a/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/ShadowDesignerCleanup.cs +++ b/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/ShadowDesignerCleanup.cs @@ -21,7 +21,7 @@ namespace XSharp.ProjectSystem.ShadowDesigner /// the solution closes. Leaving stale companion projects/folders around between sessions /// has been a real, confusing source of test failures in this feature's own development /// (half-regenerated files, leftover references from an earlier attempt). Regeneration - /// is already proven idempotent -- ShadowDesignerBridge.TryOpen recreates the companion + /// is already proven idempotent -- ShadowDesignerBridge.TryOpenAsync recreates the companion /// project on demand -- so cleaning it up on close is safe: nothing is lost, it's purely /// auto-generated output. /// @@ -77,7 +77,7 @@ private ShadowDesignerCleanup(IServiceProvider serviceProvider) /// /// Registers a companion project for cleanup on solution close, lazily advising /// solution events the first time this is called. Safe to call every time - /// ShadowDesignerBridge.TryOpen succeeds -- idempotent (HashSet). + /// ShadowDesignerBridge.TryOpenAsync prepares the companion -- idempotent (HashSet). /// public static void Track(string companionCsprojPath) { diff --git a/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/XSharpShadowDesignerCommands.cs b/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/XSharpShadowDesignerCommands.cs index 5780004f8f..a2d75ea795 100644 --- a/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/XSharpShadowDesignerCommands.cs +++ b/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/XSharpShadowDesignerCommands.cs @@ -60,8 +60,12 @@ public static async Task TryOpenAsync(IProjectThreadingService threading, await threading.SwitchToUIThread(); // The project can have been unloaded or reloaded while waiting xProject = XSolution.FindFile(path)?.Project; - if (ok && xProject != null && ShadowDesignerBridge.TryOpen(path, xProject, out error)) - return true; + if (ok && xProject != null) + { + (ok, error) = await ShadowDesignerBridge.TryOpenAsync(path, xProject); + if (ok) + return true; + } Logger.Information("XSharp ShadowDesigner: " + (error ?? "no X# code model for " + path)); return false; } From 29188bd8f442f95915d8112e0ffe36362fd1d9e5 Mon Sep 17 00:00:00 2001 From: Hansjoerg Petriffer Date: Mon, 28 Sep 2026 15:51:33 +0200 Subject: [PATCH 38/60] [VsIntegration] Shadow designer: open forms added with "Add New Item" in the designer The item template wizard opens the new .prg itself, with the default editor for .prg (the X# code editor), so a new form or user control did not open in the designer. MPFproj redirected this in XSharpFileNode, which was removed with the MPFproj SDK path. CPS' own mechanism (IProjectSpecificEditorProvider with IsDefaultEditor) is not usable: it routes every open of the file to the project specific editor, also debugger and navigation opens (LOGVIEWID_Debugging/TextView), which the shadow designer cannot serve. New NewFormDesignerRedirect (IVsRunningDocTableEvents, advised when the first X# CPS project loads): on the first window of a document of a CPS X# project that belongs to a form .prg (a .Designer.prg exists) created within the last 30 s, it waits until the code model knows the file, opens the shadow designer and, only when that succeeded, closes the code window. Each file is redirected once per session, so a later View Code is not affected; Add Existing Item opens no window. XSharpShadowDesignerCommands.TryOpenAsync gets an overload without IProjectThreadingService for it. Tested in VS: Add New Item > UserControl in the CPS WinForms project opens the designer and closes the code editor (about 1.4 s); in the legacy (MPFproj) project the MPFproj designer opens as before, no redirect. Co-Authored-By: Claude Opus 5.5 --- .../LanguageService/XSharpProjectAdapter.cs | 3 + .../ShadowDesigner/NewFormDesignerRedirect.cs | 176 ++++++++++++++++++ .../XSharpShadowDesignerCommands.cs | 13 +- 3 files changed, 191 insertions(+), 1 deletion(-) create mode 100644 src/VisualStudio/ProjectSystemCPS/ShadowDesigner/NewFormDesignerRedirect.cs diff --git a/src/VisualStudio/ProjectSystemCPS/LanguageService/XSharpProjectAdapter.cs b/src/VisualStudio/ProjectSystemCPS/LanguageService/XSharpProjectAdapter.cs index b18f6eae59..363def2804 100644 --- a/src/VisualStudio/ProjectSystemCPS/LanguageService/XSharpProjectAdapter.cs +++ b/src/VisualStudio/ProjectSystemCPS/LanguageService/XSharpProjectAdapter.cs @@ -108,6 +108,9 @@ public async Task LoadAsync() model.FileWalkComplete += OnFileWalkComplete; } XSettings.Information("XSharpProjectAdapter: created code model for " + project.FullPath); + // Forms added with "Add New Item" open in the shadow designer instead of the code editor + await threading.JoinableTaskFactory.SwitchToMainThreadAsync(); + ShadowDesigner.NewFormDesignerRedirect.EnsureAdvised(threading.JoinableTaskFactory); errorList = new IntellisenseErrorList(threading.JoinableTaskFactory, DisplayName); errors.Changed = errorList.Update; taskList = new CommentTaskList(threading.JoinableTaskFactory, DisplayName); diff --git a/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/NewFormDesignerRedirect.cs b/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/NewFormDesignerRedirect.cs new file mode 100644 index 0000000000..515c62d1f4 --- /dev/null +++ b/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/NewFormDesignerRedirect.cs @@ -0,0 +1,176 @@ +// +// Copyright (c) XSharp B.V. All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +// + +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.InteropServices; +using System.Threading.Tasks; +using Microsoft.VisualStudio; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudio.Threading; +using XSharpModel; + +namespace XSharp.ProjectSystem.ShadowDesigner +{ + /// + /// Opens a form or user control that was just added with "Add New Item" in the shadow designer instead of the + /// code editor. + /// + /// + /// The item template wizard opens the new .prg itself (primary view), so it does not go through the shadow + /// designer commands (double click, View Designer); VS picks the default editor for .prg, the X# code editor. + /// MPFproj redirected this in XSharpFileNode, which is gone together with the MPFproj SDK path. + /// CPS' own mechanism for "forms open in the designer" (IProjectSpecificEditorProvider with IsDefaultEditor) is + /// not usable: it routes every open of the file to the project specific editor factory, also the debugger and + /// navigation (LOGVIEWID_Debugging/TextView), which the shadow designer cannot serve. + /// Instead this watches the first window of documents of CPS X# projects: when it belongs to a form .prg (a + /// .Designer.prg exists) whose file was created just now (by the template), the shadow designer is opened and, + /// once that succeeded, the code window is closed. Every file is redirected at most once per session, so a later + /// "View Code" is not affected. "Add Existing Item" opens no window, so it is not affected either. + /// + internal sealed class NewFormDesignerRedirect : IVsRunningDocTableEvents + { + // A file created longer ago is not a new item from a template + private static readonly TimeSpan NewFileAge = TimeSpan.FromSeconds(30); + // How long to wait for the code model to know the new file (evaluation of the project) + private static readonly TimeSpan CodeModelTimeout = TimeSpan.FromSeconds(10); + + private static NewFormDesignerRedirect _instance; + + private readonly IVsRunningDocumentTable _rdt; + private readonly JoinableTaskFactory _joinableTaskFactory; + private readonly HashSet _handled = new HashSet(StringComparer.OrdinalIgnoreCase); + + private NewFormDesignerRedirect(IVsRunningDocumentTable rdt, JoinableTaskFactory joinableTaskFactory) + { + _rdt = rdt; + _joinableTaskFactory = joinableTaskFactory; + } + + /// + /// Starts watching the document windows, once per session. Called when the first X# CPS project loads, with + /// the JoinableTaskFactory of the CPS threading service (runs the switch to the designer). + /// + public static void EnsureAdvised(JoinableTaskFactory joinableTaskFactory) + { + ThreadHelper.ThrowIfNotOnUIThread(); + if (_instance != null) + { + return; + } + if (!(ServiceProvider.GlobalProvider.GetService(typeof(SVsRunningDocumentTable)) is IVsRunningDocumentTable rdt)) + { + return; + } + var instance = new NewFormDesignerRedirect(rdt, joinableTaskFactory); + if (ErrorHandler.Succeeded(rdt.AdviseRunningDocTableEvents(instance, out uint _))) + { + // Process lifetime: the table outlives every solution, there is nothing to unadvise before shutdown + _instance = instance; + } + } + + public int OnBeforeDocumentWindowShow(uint docCookie, int fFirstShow, IVsWindowFrame pFrame) + { + ThreadHelper.ThrowIfNotOnUIThread(); + if (fFirstShow == 0 || pFrame == null) + { + return VSConstants.S_OK; + } + try + { + if (ErrorHandler.Failed(_rdt.GetDocumentInfo(docCookie, out uint _, out uint _, out uint _, out string moniker, + out IVsHierarchy hierarchy, out uint _, out IntPtr docData))) + { + return VSConstants.S_OK; + } + if (docData != IntPtr.Zero) + { + Marshal.Release(docData); + } + string mainPrg = GetMainPrg(moniker); + if (mainPrg == null || hierarchy == null || !hierarchy.IsCapabilityMatch(XSharpCapabilities.XSharpCps) || + !IsNewFile(mainPrg) || !_handled.Add(mainPrg)) + { + return VSConstants.S_OK; + } + Logger.Information($"NewFormDesignerRedirect: new form {mainPrg} opened in the code editor, switching to the designer"); + _joinableTaskFactory.RunAsync(() => SwitchToDesignerAsync(mainPrg, pFrame)) + .FileAndForget("XSharp/ProjectSystemCPS/NewFormDesignerRedirect"); + } + catch (Exception ex) + { + Logger.Exception(ex, "NewFormDesignerRedirect.OnBeforeDocumentWindowShow"); + } + return VSConstants.S_OK; + } + + /// + /// Form1.prg or Form1.Designer.prg -> Form1.prg, for files of an X# form (the template can open either). + /// + private static string GetMainPrg(string moniker) + { + if (string.IsNullOrEmpty(moniker) || !moniker.EndsWith(".prg", StringComparison.OrdinalIgnoreCase)) + { + return null; + } + const string designerSuffix = ".designer.prg"; + if (moniker.EndsWith(designerSuffix, StringComparison.OrdinalIgnoreCase)) + { + return moniker.Substring(0, moniker.Length - designerSuffix.Length) + ".prg"; + } + return moniker; + } + + private static bool IsNewFile(string path) + { + try + { + return File.Exists(path) && DateTime.UtcNow - File.GetCreationTimeUtc(path) < NewFileAge; + } + catch (Exception) + { + return false; + } + } + + private static async Task SwitchToDesignerAsync(string mainPrg, IVsWindowFrame codeFrame) + { + // Let the Add New Item operation finish; the .Designer.prg is written by the same template and the project + // (and with it the code model) only knows the new files after its next evaluation. + var deadline = DateTime.UtcNow + CodeModelTimeout; + while (!(ShadowDesignerBridge.HasDesignerFile(mainPrg) && XSolution.FindFile(mainPrg)?.Project != null)) + { + if (DateTime.UtcNow >= deadline) + { + Logger.Information($"NewFormDesignerRedirect: {mainPrg} is not known to the X# code model, keeping the code editor"); + return; + } + await Task.Delay(200); + } + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + if (!await XSharpShadowDesignerCommands.TryOpenAsync(mainPrg)) + { + // The designer could not be opened: the code editor stays, as without this redirect + return; + } + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + // A document from a template is not dirty; PromptSave only asks if the user already typed something + int hr = codeFrame.CloseFrame((uint)__FRAMECLOSE.FRAMECLOSE_PromptSave); + Logger.Information($"NewFormDesignerRedirect: closed the code editor of {mainPrg}, hr=0x{hr:X8}"); + } + + #region IVsRunningDocTableEvents members that are not used + public int OnAfterFirstDocumentLock(uint docCookie, uint dwRDTLockType, uint dwReadLocksRemaining, uint dwEditLocksRemaining) => VSConstants.S_OK; + public int OnBeforeLastDocumentUnlock(uint docCookie, uint dwRDTLockType, uint dwReadLocksRemaining, uint dwEditLocksRemaining) => VSConstants.S_OK; + public int OnAfterSave(uint docCookie) => VSConstants.S_OK; + public int OnAfterAttributeChange(uint docCookie, uint grfAttribs) => VSConstants.S_OK; + public int OnAfterDocumentWindowHide(uint docCookie, IVsWindowFrame pFrame) => VSConstants.S_OK; + #endregion + } +} diff --git a/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/XSharpShadowDesignerCommands.cs b/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/XSharpShadowDesignerCommands.cs index a2d75ea795..616e148eab 100644 --- a/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/XSharpShadowDesignerCommands.cs +++ b/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/XSharpShadowDesignerCommands.cs @@ -10,6 +10,7 @@ using System.Threading.Tasks; using Microsoft.VisualStudio; using Microsoft.VisualStudio.ProjectSystem; +using Microsoft.VisualStudio.Shell; using XSharpModel; namespace XSharp.ProjectSystem.ShadowDesigner @@ -45,6 +46,16 @@ public static bool TryGetFormFile(IImmutableSet nodes, out string public static async Task TryOpenAsync(IProjectThreadingService threading, string path) { await threading.SwitchToUIThread(); + return await TryOpenAsync(path); + } + + /// + /// Opens the shadow designer for the form ; false when it could not be opened. + /// Also used by for forms added with "Add New Item". + /// + public static async Task TryOpenAsync(string path) + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); if (opening) return true; opening = true; @@ -57,7 +68,7 @@ public static async Task TryOpenAsync(IProjectThreadingService threading, return false; } var (ok, error) = await ShadowDesignerBridge.EnsureReferencesAsync(xProject); - await threading.SwitchToUIThread(); + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); // The project can have been unloaded or reloaded while waiting xProject = XSolution.FindFile(path)?.Project; if (ok && xProject != null) From 19213e9def6b61a21da2ac3787c349c8612f12f5 Mon Sep 17 00:00:00 2001 From: Hansjoerg Petriffer Date: Mon, 28 Sep 2026 16:43:08 +0200 Subject: [PATCH 39/60] [VsIntegration] CPS project system: create the code model on the UI thread The projects of a solution load in parallel, and the adapters created their XProjects at the same time on background threads. That raced in the code model: with a new X# database the lazily created OrphanedFiles project was added twice and XDatabase.Read failed with SQLite "FOREIGN KEY constraint failed" (seen in the VS log after deleting the .vs folder). MPFproj creates its XProjects one after the other on the UI thread; the adapter now switches to the UI thread before it creates the model as well. Verified in VS with a fresh database: OrphanedFiles is added once, both models are created on the UI thread, no SQLite error. Co-Authored-By: Claude Opus 5.5 --- .../LanguageService/XSharpProjectAdapter.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/VisualStudio/ProjectSystemCPS/LanguageService/XSharpProjectAdapter.cs b/src/VisualStudio/ProjectSystemCPS/LanguageService/XSharpProjectAdapter.cs index 363def2804..3b7275b605 100644 --- a/src/VisualStudio/ProjectSystemCPS/LanguageService/XSharpProjectAdapter.cs +++ b/src/VisualStudio/ProjectSystemCPS/LanguageService/XSharpProjectAdapter.cs @@ -99,6 +99,11 @@ public async Task LoadAsync() XSettings.Information("XSharpProjectAdapter: X# solution model is not open, no code model for " + project.FullPath); return; } + // Create the code model on the UI thread, like MPFproj: the projects of a solution load in parallel, and + // creating two XProjects at the same time on background threads raced in the code model (the lazily + // created OrphanedFiles project was added twice: SQLite "FOREIGN KEY constraint failed" with a new + // X# database, verified in VS). + await threading.JoinableTaskFactory.SwitchToMainThreadAsync(); lock (gate) { if (model != null) @@ -109,7 +114,6 @@ public async Task LoadAsync() } XSettings.Information("XSharpProjectAdapter: created code model for " + project.FullPath); // Forms added with "Add New Item" open in the shadow designer instead of the code editor - await threading.JoinableTaskFactory.SwitchToMainThreadAsync(); ShadowDesigner.NewFormDesignerRedirect.EnsureAdvised(threading.JoinableTaskFactory); errorList = new IntellisenseErrorList(threading.JoinableTaskFactory, DisplayName); errors.Changed = errorList.Update; From 1803b79e2acff2c049c8724c2116b2eb1014cc70 Mon Sep 17 00:00:00 2001 From: Hansjoerg Petriffer Date: Mon, 28 Sep 2026 16:43:29 +0200 Subject: [PATCH 40/60] [VsIntegration] CPS project system: refresh the Task List when a file is saved The comment tasks (TODO etc.) of a CPS project only showed up after the next project walk, e.g. after a design-time build, not when the file was saved. MPFproj walks a changed file again in XSharpProjectNode.OnFileChanged (WalkFile(file, notify: true)), whose FileWalkComplete event refreshes the Task List; the CPS adapter had no counterpart. New ProjectFileSaveWatcher (IVsRunningDocTableEvents, advised when the first X# CPS project loads): after a save of a source file of an X# CPS project it walks the file again on a background thread, so the adapter's FileWalkComplete handler refreshes the Task List. It also passes the Task List comment tokens (Tools > Options) to the code model when nobody did yet: the X# project package only sets them when an MPFproj project opens or after the options dialog, so a solution with only CPS projects had no comment tasks at all. Verified in VS: the TODO entry appears right after saving (the save walk runs on a background thread); "4 comment tokens set" with a fresh database. Co-Authored-By: Claude Opus 5.5 --- .../LanguageService/ProjectFileSaveWatcher.cs | 144 ++++++++++++++++++ .../LanguageService/XSharpProjectAdapter.cs | 4 + 2 files changed, 148 insertions(+) create mode 100644 src/VisualStudio/ProjectSystemCPS/LanguageService/ProjectFileSaveWatcher.cs diff --git a/src/VisualStudio/ProjectSystemCPS/LanguageService/ProjectFileSaveWatcher.cs b/src/VisualStudio/ProjectSystemCPS/LanguageService/ProjectFileSaveWatcher.cs new file mode 100644 index 0000000000..2c0fcfcead --- /dev/null +++ b/src/VisualStudio/ProjectSystemCPS/LanguageService/ProjectFileSaveWatcher.cs @@ -0,0 +1,144 @@ +// +// Copyright (c) XSharp B.V. All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +// + +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Threading.Tasks; +using Microsoft.VisualStudio; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudio.Threading; +using XSharp.Settings; +using XSharpModel; + +namespace XSharp.ProjectSystem.LanguageService +{ + /// + /// Re-walks a source file of an X# CPS project after it was saved, so the code model (and with it the comment + /// tasks in the Task List) reflects the saved file. + /// + /// + /// MPFproj does this in XSharpProjectNode.OnFileChanged: WalkFile(file, notify: true), whose FileWalkComplete event + /// refreshes the Task List. Under CPS nothing re-walked a saved file: a new TODO only showed up after the next + /// project walk (e.g. after a design-time build), verified in VS. The walk runs on a background thread. + /// + internal sealed class ProjectFileSaveWatcher : IVsRunningDocTableEvents + { + private static ProjectFileSaveWatcher _instance; + + private readonly IVsRunningDocumentTable _rdt; + private readonly JoinableTaskFactory _joinableTaskFactory; + + private ProjectFileSaveWatcher(IVsRunningDocumentTable rdt, JoinableTaskFactory joinableTaskFactory) + { + _rdt = rdt; + _joinableTaskFactory = joinableTaskFactory; + } + + /// + /// Starts watching, once per session. Called when the first X# CPS project loads. + /// + public static void EnsureAdvised(JoinableTaskFactory joinableTaskFactory) + { + ThreadHelper.ThrowIfNotOnUIThread(); + if (_instance != null || + !(ServiceProvider.GlobalProvider.GetService(typeof(SVsRunningDocumentTable)) is IVsRunningDocumentTable rdt)) + { + return; + } + var instance = new ProjectFileSaveWatcher(rdt, joinableTaskFactory); + if (ErrorHandler.Succeeded(rdt.AdviseRunningDocTableEvents(instance, out uint _))) + { + // Process lifetime, like NewFormDesignerRedirect + _instance = instance; + } + } + + /// + /// Passes the comment tokens (Tools > Options > Environment > Task List) to the code model when nobody did yet. + /// + /// + /// The X# project package sets them when an MPFproj project opens and after the options dialog closes + /// (XSharpProjectPackage.SetCommentTokens). In a solution with only CPS projects the parser would otherwise + /// find no comment tasks at all. + /// + public static void EnsureCommentTokens() + { + ThreadHelper.ThrowIfNotOnUIThread(); + if (XSolution.CommentTokens.Count > 0 || + !(ServiceProvider.GlobalProvider.GetService(typeof(SVsTaskList)) is IVsCommentTaskInfo taskInfo) || + ErrorHandler.Failed(taskInfo.EnumTokens(out IVsEnumCommentTaskTokens enumTokens)) || enumTokens == null) + { + return; + } + var tokens = new List(); + var buffer = new IVsCommentTaskToken[1]; + while (enumTokens.Next(1, buffer, out uint fetched) == VSConstants.S_OK && fetched == 1) + { + var priority = new VSTASKPRIORITY[1]; + if (ErrorHandler.Succeeded(buffer[0].Text(out string text)) && !string.IsNullOrEmpty(text) && + ErrorHandler.Succeeded(buffer[0].Priority(priority))) + { + tokens.Add(new XCommentToken(text, (int)priority[0])); + } + } + if (tokens.Count > 0) + { + XSolution.SetCommentTokens(tokens); + XSettings.Information($"ProjectFileSaveWatcher: {tokens.Count} comment tokens set"); + } + } + + public int OnAfterSave(uint docCookie) + { + ThreadHelper.ThrowIfNotOnUIThread(); + try + { + if (ErrorHandler.Failed(_rdt.GetDocumentInfo(docCookie, out uint _, out uint _, out uint _, out string moniker, + out IVsHierarchy _, out uint _, out IntPtr docData))) + { + return VSConstants.S_OK; + } + if (docData != IntPtr.Zero) + { + Marshal.Release(docData); + } + var file = string.IsNullOrEmpty(moniker) ? null : XSolution.FindFile(moniker); + var project = file?.Project; + if (project == null || !(project.ProjectNode is XSharpProjectAdapter) || !file.IsSource) + { + return VSConstants.S_OK; + } + _joinableTaskFactory.RunAsync(async () => + { + await TaskScheduler.Default; + try + { + project.WalkFile(file, true); + } + catch (Exception e) + { + XSettings.Exception(e); + } + }).FileAndForget("XSharp/ProjectSystemCPS/ProjectFileSaveWatcher"); + } + catch (Exception e) + { + XSettings.Exception(e); + } + return VSConstants.S_OK; + } + + #region IVsRunningDocTableEvents members that are not used + public int OnAfterFirstDocumentLock(uint docCookie, uint dwRDTLockType, uint dwReadLocksRemaining, uint dwEditLocksRemaining) => VSConstants.S_OK; + public int OnBeforeLastDocumentUnlock(uint docCookie, uint dwRDTLockType, uint dwReadLocksRemaining, uint dwEditLocksRemaining) => VSConstants.S_OK; + public int OnAfterAttributeChange(uint docCookie, uint grfAttribs) => VSConstants.S_OK; + public int OnBeforeDocumentWindowShow(uint docCookie, int fFirstShow, IVsWindowFrame pFrame) => VSConstants.S_OK; + public int OnAfterDocumentWindowHide(uint docCookie, IVsWindowFrame pFrame) => VSConstants.S_OK; + #endregion + } +} diff --git a/src/VisualStudio/ProjectSystemCPS/LanguageService/XSharpProjectAdapter.cs b/src/VisualStudio/ProjectSystemCPS/LanguageService/XSharpProjectAdapter.cs index 3b7275b605..7bf99ca322 100644 --- a/src/VisualStudio/ProjectSystemCPS/LanguageService/XSharpProjectAdapter.cs +++ b/src/VisualStudio/ProjectSystemCPS/LanguageService/XSharpProjectAdapter.cs @@ -104,6 +104,8 @@ public async Task LoadAsync() // created OrphanedFiles project was added twice: SQLite "FOREIGN KEY constraint failed" with a new // X# database, verified in VS). await threading.JoinableTaskFactory.SwitchToMainThreadAsync(); + // Before the first walk, so the parser finds the comment tasks (TODO etc.) + ProjectFileSaveWatcher.EnsureCommentTokens(); lock (gate) { if (model != null) @@ -115,6 +117,8 @@ public async Task LoadAsync() XSettings.Information("XSharpProjectAdapter: created code model for " + project.FullPath); // Forms added with "Add New Item" open in the shadow designer instead of the code editor ShadowDesigner.NewFormDesignerRedirect.EnsureAdvised(threading.JoinableTaskFactory); + // Saved files are walked again (comment tasks), like XSharpProjectNode.OnFileChanged + ProjectFileSaveWatcher.EnsureAdvised(threading.JoinableTaskFactory); errorList = new IntellisenseErrorList(threading.JoinableTaskFactory, DisplayName); errors.Changed = errorList.Update; taskList = new CommentTaskList(threading.JoinableTaskFactory, DisplayName); From adbfd46c65f26bd5a096c64130a57df3419e4601 Mon Sep 17 00:00:00 2001 From: Hansjoerg Petriffer Date: Mon, 28 Sep 2026 16:43:30 +0200 Subject: [PATCH 41/60] [VsIntegration] CPS project system: make the image manifest assembly findable at startup The project and .prg icons of CPS projects were often empty. The image manifest refers to its PNGs with pack URIs that name XSharp.ProjectSystemCPS by its short name. The image library resolves them when it builds its cache at startup, usually before a CPS project has loaded the assembly; no X# package registered a binding path, so the lookup failed and the whole manifest was missing from ImageLibrary.cache (no GUID, no images, while e.g. the IntelliCode manifest was there). The icons only worked when a CPS project happened to load the assembly first. - [ProvideBindingPath] on XSharpProjectSystemPackage: the extension folder is probed for loads by name, the image library finds the assembly. - [assembly: ProvideCodeBase(AssemblyName = "XSharp.ProjectSystemCPS")] next to the sibling assemblies in ProjectPackage/ExternalAssemblies.cs: loads with the full identity work too, e.g. when ProjectPackage2022 uses ShadowDesignerBridge before a CPS project has loaded the assembly. Verified in VS: after StartExp (fresh cache) the cache contains the manifest GUID and both images, the project and document icons show. Co-Authored-By: Claude Opus 5.5 --- src/VisualStudio/ProjectPackage/ExternalAssemblies.cs | 3 +++ .../ProjectSystemCPS/XSharpProjectSystemPackage.cs | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/src/VisualStudio/ProjectPackage/ExternalAssemblies.cs b/src/VisualStudio/ProjectPackage/ExternalAssemblies.cs index 5a66fa155f..7a81e4877f 100644 --- a/src/VisualStudio/ProjectPackage/ExternalAssemblies.cs +++ b/src/VisualStudio/ProjectPackage/ExternalAssemblies.cs @@ -8,6 +8,9 @@ [assembly: ProvideCodeBase(AssemblyName = "XSharp.ProjectBase2022")] [assembly: ProvideCodeBase(AssemblyName = "XSharp.Debugger2022")] [assembly: ProvideCodeBase(AssemblyName = "XSharp.Support2022")] +// CPS project system: also needed by the image library, which loads the assembly by name for the pack URIs of +// XSharp.ProjectSystemCPS.imagemanifest when it builds its cache at startup (before a CPS project has loaded it) +[assembly: ProvideCodeBase(AssemblyName = "XSharp.ProjectSystemCPS")] #else [assembly: ProvideCodeBase(AssemblyName = "XSharp.AppDesigner")] [assembly: ProvideCodeBase(AssemblyName = "XSharp.LanguageService")] diff --git a/src/VisualStudio/ProjectSystemCPS/XSharpProjectSystemPackage.cs b/src/VisualStudio/ProjectSystemCPS/XSharpProjectSystemPackage.cs index f8fd1f7d7a..2e2c56777d 100644 --- a/src/VisualStudio/ProjectSystemCPS/XSharpProjectSystemPackage.cs +++ b/src/VisualStudio/ProjectSystemCPS/XSharpProjectSystemPackage.cs @@ -45,6 +45,13 @@ namespace XSharp.ProjectSystem // selector on the CPS project type makes such entries behave like the original ones: legacy projects and the // X# MSBuild support check still send projects to MPFproj. [ProvideProjectSelector(XSharpConstants.guidCpsProjectTypeString, XSharpConstants.guidProjectSelectorString)] + // The image manifest (XSharp.ProjectSystemCPS.imagemanifest) refers to its PNGs with pack URIs that name this + // assembly by its short name. The image library resolves them when it builds its cache at startup, usually before + // a CPS project has loaded the assembly: without the extension folder as binding path the lookup failed, the + // manifest was dropped from the cache and the project and .prg icons stayed empty (unless a CPS project happened + // to load first). The codeBase entry (ProvideCodeBase in ProjectPackage/ExternalAssemblies.cs) only helps loads + // with the full identity. + [ProvideBindingPath] public sealed class XSharpProjectSystemPackage : AsyncPackage { /// From 2488f7a0f2b930dbb0b86ad1473ccfba5e714eff Mon Sep 17 00:00:00 2001 From: Hansjoerg Petriffer Date: Tue, 29 Sep 2026 08:11:30 +0200 Subject: [PATCH 42/60] [VsIntegration] CPS project system: check the overridable managed design-time targets path XSharp.DesignTime.targets imports $(XSharpManagedDesignTimeTargetsPath), but the import gates in XSharp.CurrentVersion.targets and XSharp.CrossTargeting.targets checked the hard-coded path of Microsoft.Managed.DesignTime.targets. With the property overridden to a file that does not exist, the gate passed and the project failed to load (MSB4019). The property is now defined next to XSharpDesignTimeTargetsPath and the gate checks it. Verified from the command line: default -> design-time targets imported; override to a missing file -> not imported, no error (before: MSB4019); without XSharpCpsProjectSystem -> unchanged. Project loads in VS. Co-Authored-By: Claude Opus 5.5 --- .../Compiler/XSharpBuildTask/XSharp.CrossTargeting.targets | 4 +++- .../Compiler/XSharpBuildTask/XSharp.CurrentVersion.targets | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Compiler/src/Compiler/XSharpBuildTask/XSharp.CrossTargeting.targets b/src/Compiler/src/Compiler/XSharpBuildTask/XSharp.CrossTargeting.targets index f94c2e0cf4..28d70de313 100644 --- a/src/Compiler/src/Compiler/XSharpBuildTask/XSharp.CrossTargeting.targets +++ b/src/Compiler/src/Compiler/XSharpBuildTask/XSharp.CrossTargeting.targets @@ -16,9 +16,11 @@ Copyright (C) XSharp BV. All rights reserved. $(MSBuildThisFileDirectory)XSharp.DesignTime.targets + + $(MSBuildExtensionsPath)\Microsoft\VisualStudio\Managed\Microsoft.Managed.DesignTime.targets - + diff --git a/src/Compiler/src/Compiler/XSharpBuildTask/XSharp.CurrentVersion.targets b/src/Compiler/src/Compiler/XSharpBuildTask/XSharp.CurrentVersion.targets index 1c7d1fa67d..dcbbdf573e 100644 --- a/src/Compiler/src/Compiler/XSharpBuildTask/XSharp.CurrentVersion.targets +++ b/src/Compiler/src/Compiler/XSharpBuildTask/XSharp.CurrentVersion.targets @@ -376,9 +376,11 @@ Copyright (c) XSharp B.V. All Rights Reserved $(MSBuildThisFileDirectory)XSharp.DesignTime.targets + + $(MSBuildExtensionsPath)\Microsoft\VisualStudio\Managed\Microsoft.Managed.DesignTime.targets - + From 8468de2abe9ce1a6b2251c2870699387b00a2592 Mon Sep 17 00:00:00 2001 From: Hansjoerg Petriffer Date: Tue, 29 Sep 2026 08:11:33 +0200 Subject: [PATCH 43/60] [VsIntegration] CPS project system: hide the property page of the NativeResource and VOBinary rules The File rules of the X# item types were copied from the managed None rule without PropertyPagesHidden="true" and without the TargetPath property. Without PropertyPagesHidden the file property dialog shows an extra, empty page for .rc files and VO binaries. Both are added as in None.xaml. Tested in VS: Test.rc shows its properties (Build Action Native Resource), no empty extra page. Co-Authored-By: Claude Opus 5.5 --- .../XSharpBuildTask/Rules/NativeResource.xaml | 13 +++++++++++++ .../Compiler/XSharpBuildTask/Rules/VOBinary.xaml | 13 +++++++++++++ 2 files changed, 26 insertions(+) diff --git a/src/Compiler/src/Compiler/XSharpBuildTask/Rules/NativeResource.xaml b/src/Compiler/src/Compiler/XSharpBuildTask/Rules/NativeResource.xaml index 785ec2488e..5e28db5c84 100644 --- a/src/Compiler/src/Compiler/XSharpBuildTask/Rules/NativeResource.xaml +++ b/src/Compiler/src/Compiler/XSharpBuildTask/Rules/NativeResource.xaml @@ -3,6 +3,7 @@ Name="NativeResource" DisplayName="Native Resource" PageTemplate="generic" + PropertyPagesHidden="true" xmlns="http://schemas.microsoft.com/build/2009/properties"> + + + + + + + + + diff --git a/src/Compiler/src/Compiler/XSharpBuildTask/Rules/VOBinary.xaml b/src/Compiler/src/Compiler/XSharpBuildTask/Rules/VOBinary.xaml index 467cce8ab9..9add003e83 100644 --- a/src/Compiler/src/Compiler/XSharpBuildTask/Rules/VOBinary.xaml +++ b/src/Compiler/src/Compiler/XSharpBuildTask/Rules/VOBinary.xaml @@ -3,6 +3,7 @@ Name="VOBinary" DisplayName="VO Compatible Binary" PageTemplate="generic" + PropertyPagesHidden="true" xmlns="http://schemas.microsoft.com/build/2009/properties"> + + + + + + + + + From 3f29459d31f96b08cae2507e115d249f9655650d Mon Sep 17 00:00:00 2001 From: Hansjoerg Petriffer Date: Tue, 29 Sep 2026 08:11:35 +0200 Subject: [PATCH 44/60] [VsIntegration] CPS project system: map .vnsqs and .vnrep to VOBinary The X# code model treats .vnsqs as a VO SQL table and .vnrep as a VO report, like .xssql and .xsrep (FileTypeHelpers), and the icon provider already maps them, but ProjectItemsSchema.XSharp.xaml did not: such files got no X# item type when added to a CPS project. Co-Authored-By: Claude Opus 5.5 --- .../XSharpBuildTask/Rules/ProjectItemsSchema.XSharp.xaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Compiler/src/Compiler/XSharpBuildTask/Rules/ProjectItemsSchema.XSharp.xaml b/src/Compiler/src/Compiler/XSharpBuildTask/Rules/ProjectItemsSchema.XSharp.xaml index a066e89f27..b02cde9ef4 100644 --- a/src/Compiler/src/Compiler/XSharpBuildTask/Rules/ProjectItemsSchema.XSharp.xaml +++ b/src/Compiler/src/Compiler/XSharpBuildTask/Rules/ProjectItemsSchema.XSharp.xaml @@ -54,5 +54,7 @@ + + \ No newline at end of file From fed3f712fcf78d076c9d6a9a3f88d5ed840d1497 Mon Sep 17 00:00:00 2001 From: Hansjoerg Petriffer Date: Tue, 29 Sep 2026 08:11:37 +0200 Subject: [PATCH 45/60] [VsIntegration] CPS project system: SuppressRCWarnings on the Build page The MPFproj Build page has "Suppress Resource Compiler warnings" (SuppressRCWarnings, passed to the NativeResourceCompiler task, default true in XSharp.props); the CPS Build page had no rule for it. Added as a bool property in the "Errors and warnings" category with the MPFproj texts. Tested in VS: the checkbox shows ticked by default, unticking writes the property to the project file. Co-Authored-By: Claude Opus 5.5 --- .../XSharpBuildTask/Rules/BuildPropertyPage.XSharp.xaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Compiler/src/Compiler/XSharpBuildTask/Rules/BuildPropertyPage.XSharp.xaml b/src/Compiler/src/Compiler/XSharpBuildTask/Rules/BuildPropertyPage.XSharp.xaml index bdc28b430c..e63966cd22 100644 --- a/src/Compiler/src/Compiler/XSharpBuildTask/Rules/BuildPropertyPage.XSharp.xaml +++ b/src/Compiler/src/Compiler/XSharpBuildTask/Rules/BuildPropertyPage.XSharp.xaml @@ -40,6 +40,12 @@ DisplayName="5 - Most warnings, including less severe issues" /> + + + From fb73a5ac3095cc0ec78fc797290391f741d8df24 Mon Sep 17 00:00:00 2001 From: Hansjoerg Petriffer Date: Tue, 29 Sep 2026 09:22:41 +0200 Subject: [PATCH 46/60] [VsIntegration] CPS project system: do not hold the adapter lock during code model I/O The adapter's single lock protected both its file/reference sets and the calls into the code model. XProject.AddFile/RemoveFile do database I/O and ResolveReferences loads the referenced assemblies, so HasFileNode, which the VO designers call on the UI thread, could wait for that I/O. Two locks now, always taken in the same order: modelGate serializes the changes of the code model (the three subscriptions, AddFileNode/DeleteFileNode, load/unload) as before; gate only protects the file set and the model field, briefly, and HasFileNode only takes gate. ResolveReferences runs outside both locks: XProject guards it itself (_resolvingReferences), and the ModelWalker and the type lookups call it without these locks as well. Tested in VS: projects load, design-time builds apply, files added through Add New Item reach the code model, completion works. Co-Authored-By: Claude Opus 5.5 --- .../LanguageService/XSharpProjectAdapter.cs | 91 +++++++++++++------ 1 file changed, 61 insertions(+), 30 deletions(-) diff --git a/src/VisualStudio/ProjectSystemCPS/LanguageService/XSharpProjectAdapter.cs b/src/VisualStudio/ProjectSystemCPS/LanguageService/XSharpProjectAdapter.cs index 7bf99ca322..6fc8132845 100644 --- a/src/VisualStudio/ProjectSystemCPS/LanguageService/XSharpProjectAdapter.cs +++ b/src/VisualStudio/ProjectSystemCPS/LanguageService/XSharpProjectAdapter.cs @@ -62,6 +62,12 @@ internal sealed class XSharpProjectAdapter : IProjectDynamicLoadComponent, IXSha private readonly IntellisenseErrorStore errors = new IntellisenseErrorStore(); private readonly HashSet files = new HashSet(StringComparer.OrdinalIgnoreCase); private readonly HashSet projectReferences = new HashSet(StringComparer.OrdinalIgnoreCase); + // Two locks, always taken in this order (modelGate, then gate): + // - modelGate serializes the changes of the code model (the three subscriptions, AddFileNode/DeleteFileNode, + // load/unload); XProject.AddFile/RemoveFile do database I/O. + // - gate only protects the file set and the model field, briefly, so HasFileNode (called by the VO designers on + // the UI thread) never waits for that I/O. + private readonly object modelGate = new object(); private readonly object gate = new object(); private XProject model; @@ -106,13 +112,17 @@ public async Task LoadAsync() await threading.JoinableTaskFactory.SwitchToMainThreadAsync(); // Before the first walk, so the parser finds the comment tasks (TODO etc.) ProjectFileSaveWatcher.EnsureCommentTokens(); - lock (gate) + lock (modelGate) { if (model != null) return; - model = new XProject(this); - model.ProjectWalkComplete += OnProjectWalkComplete; - model.FileWalkComplete += OnFileWalkComplete; + var newModel = new XProject(this); + newModel.ProjectWalkComplete += OnProjectWalkComplete; + newModel.FileWalkComplete += OnFileWalkComplete; + lock (gate) + { + model = newModel; + } } XSettings.Information("XSharpProjectAdapter: created code model for " + project.FullPath); // Forms added with "Add New Item" open in the shadow designer instead of the code editor @@ -146,7 +156,7 @@ public async Task LoadAsync() public Task UnloadAsync() { XProject oldModel; - lock (gate) + lock (modelGate) { if (links != null) { @@ -160,13 +170,16 @@ public Task UnloadAsync() model.FileWalkComplete -= OnFileWalkComplete; } oldModel = model; - model = null; + lock (gate) + { + model = null; + files.Clear(); + } errors.Changed = null; errorList?.Dispose(); errorList = null; taskList?.Dispose(); taskList = null; - files.Clear(); projectReferences.Clear(); } oldModel?.Close(); @@ -229,9 +242,10 @@ private void OnSourceItemsChanged(IProjectVersionedValue(); var removed = new List(); - lock (gate) + lock (modelGate) { - if (model == null) + var current = model; + if (current == null) return; foreach (var change in update.Value.ProjectChanges) { @@ -248,16 +262,17 @@ private void OnSourceItemsChanged(IProjectVersionedValue toRemove, toAdd; + lock (gate) { - if (files.Remove(file)) - model.RemoveFile(file); - } - foreach (var file in added) - { - if (files.Add(file)) - model.AddFile(file); + toRemove = removed.Where(files.Remove).ToList(); + toAdd = added.Where(files.Add).ToList(); } + // Database I/O, outside gate + foreach (var file in toRemove) + current.RemoveFile(file); + foreach (var file in toAdd) + current.AddFile(file); } if (added.Count > 0 || removed.Count > 0) { @@ -276,7 +291,7 @@ private void OnEvaluationChanged(IProjectVersionedValue Date: Tue, 29 Sep 2026 09:23:30 +0200 Subject: [PATCH 47/60] [VsIntegration] CPS project system: less file system access in the tree properties provider XSharpProjectTreePropertiesProvider runs synchronously on the CPS tree thread for every node on every tree update. It called File.Exists for every .prg (is there a .Designer.prg?), read form files completely (File.ReadAllText) to infer Form/UserControl from the base class, and its cache grew without limit. - The designer file check uses a listing of the *.Designer.prg files per folder, reused for 250 ms and read again when the folder's timestamp changes (on NTFS: a file was created, deleted or renamed). 250 ms covers the burst of one tree update; longer could miss a .Designer.prg created by Add New Item. ShadowDesignerBridge.HasDesignerFile stays uncached (the commands and NewFormDesignerRedirect need the current state). - The base class check reads the file in 4 KB blocks only until the first CLASS ... INHERIT declaration, at most 64 KB. - Both caches are cleared when they exceed 1000 entries. Verified outside VS (reflection): Form1.prg -> Form, a UserControl whose declaration follows after ~6 KB -> UserControl, a new .Designer.prg is found after 0.3 s. Tested in VS: project, document, form and user control icons, also for a user control added with Add New Item. Co-Authored-By: Claude Opus 5.5 --- .../XSharpProjectTreePropertiesProvider.cs | 99 ++++++++++++++++++- 1 file changed, 95 insertions(+), 4 deletions(-) diff --git a/src/VisualStudio/ProjectSystemCPS/Imaging/XSharpProjectTreePropertiesProvider.cs b/src/VisualStudio/ProjectSystemCPS/Imaging/XSharpProjectTreePropertiesProvider.cs index 351d3f75c2..0d9c42269c 100644 --- a/src/VisualStudio/ProjectSystemCPS/Imaging/XSharpProjectTreePropertiesProvider.cs +++ b/src/VisualStudio/ProjectSystemCPS/Imaging/XSharpProjectTreePropertiesProvider.cs @@ -9,10 +9,12 @@ using System.Collections.Generic; using System.ComponentModel.Composition; using System.IO; +using System.Linq; +using System.Text; using System.Text.RegularExpressions; using Microsoft.VisualStudio.Imaging; using Microsoft.VisualStudio.ProjectSystem; -using XSharp.ProjectSystem.ShadowDesigner; +using XSharp.CodeDom; namespace XSharp.ProjectSystem.Imaging { @@ -55,10 +57,26 @@ internal sealed class XSharpProjectTreePropertiesProvider : IProjectTreeProperti { ".vnrep", Known(KnownImageIds.Report) }, }; + // This provider runs synchronously on the CPS tree thread for every node, on every tree update, so it keeps + // file system access low: the designer files are looked up in a cached list per folder, and a form's file is + // only read until its CLASS declaration. Both caches are cleared when they grow beyond MaxCacheEntries. + private const int MaxCacheEntries = 1000; + // A folder listing is reused for this long, then again as long as the folder's timestamp is unchanged + // (on NTFS it changes when a file in it is created, deleted or renamed). Short: it only has to cover the burst + // of one tree update (all nodes within milliseconds); a longer interval could miss a .Designer.prg that was + // just created (Add New Item) and leave the form icon generic until the next tree update. + private static readonly TimeSpan FolderRecheckInterval = TimeSpan.FromMilliseconds(250); + // Longest part of a form's file that is searched for the CLASS declaration + private const int MaxClassSearchLength = 64 * 1024; + // Result of the base class inference per file, invalidated by the file's timestamp private static readonly ConcurrentDictionary inferredSubTypes = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + // *.Designer.prg file names per folder + private static readonly ConcurrentDictionary Names)> designerFilesByFolder = + new ConcurrentDictionary)>(StringComparer.OrdinalIgnoreCase); + private static readonly Regex BaseClassRegex = new Regex(@"\bCLASS\s+\S+\s+INHERIT\s+([A-Za-z_][\w.]*)", RegexOptions.IgnoreCase | RegexOptions.Compiled); @@ -112,11 +130,59 @@ private string GetDesignerSubType(IProjectTreeCustomizablePropertyContext proper string path = null; if (propertyContext.Metadata == null || !propertyContext.Metadata.TryGetValue("FullPath", out path) || string.IsNullOrEmpty(path)) path = Path.Combine(Path.GetDirectoryName(project.FullPath), propertyContext.ItemName); - if (!ShadowDesignerBridge.HasDesignerFile(path)) + if (!HasDesignerFile(path)) return null; return InferSubTypeFromBaseClass(path); } + /// + /// Like ShadowDesignerBridge.HasDesignerFile, but with the cached folder listing (see above). The bridge + /// itself stays uncached: the commands and NewFormDesignerRedirect need the current state. + /// + private static bool HasDesignerFile(string prgPath) + { + try + { + if (!string.Equals(Path.GetExtension(prgPath), ".prg", StringComparison.OrdinalIgnoreCase) || + prgPath.EndsWith(".designer.prg", StringComparison.OrdinalIgnoreCase)) + { + return false; + } + var folder = Path.GetDirectoryName(prgPath); + var designerName = Path.GetFileName(XSharpCodeDomHelper.BuildDesignerFileName(prgPath)); + if (string.IsNullOrEmpty(folder) || string.IsNullOrEmpty(designerName)) + return false; + return GetDesignerFileNames(folder).Contains(designerName); + } + catch (Exception) + { + return false; + } + } + + private static HashSet GetDesignerFileNames(string folder) + { + var now = DateTime.UtcNow; + if (designerFilesByFolder.TryGetValue(folder, out var cached) && now - cached.Checked < FolderRecheckInterval) + return cached.Names; + var stamp = Directory.GetLastWriteTimeUtc(folder); + HashSet names; + if (cached.Names != null && cached.Stamp == stamp) + { + names = cached.Names; + } + else + { + names = new HashSet( + Directory.EnumerateFiles(folder, "*.designer.prg").Select(Path.GetFileName), + StringComparer.OrdinalIgnoreCase); + } + if (designerFilesByFolder.Count > MaxCacheEntries) + designerFilesByFolder.Clear(); + designerFilesByFolder[folder] = (now, stamp, names); + return names; + } + /// /// "CLASS Foo INHERIT System.Windows.Forms.Form" -> Form, "... INHERIT UserControl" -> UserControl. /// Same rule as XSharpFileNode.InferSubTypeFromBaseClass (MPFproj). @@ -129,8 +195,8 @@ private static string InferSubTypeFromBaseClass(string path) if (inferredSubTypes.TryGetValue(path, out var cached) && cached.Stamp == stamp) return cached.SubType; string subType = null; - var match = BaseClassRegex.Match(File.ReadAllText(path)); - if (match.Success) + var match = FindClassDeclaration(path); + if (match != null) { var baseType = match.Groups[1].Value; if (baseType.Equals("Form", StringComparison.OrdinalIgnoreCase) || baseType.EndsWith(".Form", StringComparison.OrdinalIgnoreCase)) @@ -138,6 +204,8 @@ private static string InferSubTypeFromBaseClass(string path) else if (baseType.Equals("UserControl", StringComparison.OrdinalIgnoreCase) || baseType.EndsWith(".UserControl", StringComparison.OrdinalIgnoreCase)) subType = "UserControl"; } + if (inferredSubTypes.Count > MaxCacheEntries) + inferredSubTypes.Clear(); inferredSubTypes[path] = (stamp, subType); return subType; } @@ -147,5 +215,28 @@ private static string InferSubTypeFromBaseClass(string path) return null; } } + + /// + /// The first "CLASS ... INHERIT ..." of the file. Reads the file in blocks and stops at the first match (a form's + /// class is near the top) or after characters, instead of reading it all. + /// + private static Match FindClassDeclaration(string path) + { + var text = new StringBuilder(); + var buffer = new char[4096]; + using (var reader = new StreamReader(path, detectEncodingFromByteOrderMarks: true)) + { + int read; + while (text.Length < MaxClassSearchLength && (read = reader.Read(buffer, 0, buffer.Length)) > 0) + { + text.Append(buffer, 0, read); + // The whole text so far: a declaration can span two blocks + var match = BaseClassRegex.Match(text.ToString()); + if (match.Success) + return match; + } + } + return null; + } } } From beddd38ebeb318cb0f0d33cb0017bb82b4b29150 Mon Sep 17 00:00:00 2001 From: Hansjoerg Petriffer Date: Tue, 29 Sep 2026 09:23:44 +0200 Subject: [PATCH 48/60] [VsIntegration] CPS project system: do not copy all intellisense errors on every Add IntellisenseErrorStore built the complete error list of the project on every Add and handed it to IntellisenseErrorList, which only applies the last one per (coalesced) UI refresh. The store now only reports the change; the Error List fetches the errors with IntellisenseErrorStore.GetAll once per refresh (after resetting its scheduled flag, so a later change schedules the next one). The code model does not report intellisense errors at the moment (see the CPS migration spec, WP-6), so this matters once it does again. Co-Authored-By: Claude Opus 5.5 --- .../LanguageService/IntellisenseErrorList.cs | 14 +++++---- .../LanguageService/IntellisenseErrorStore.cs | 30 ++++++++++--------- .../LanguageService/XSharpProjectAdapter.cs | 2 +- 3 files changed, 25 insertions(+), 21 deletions(-) diff --git a/src/VisualStudio/ProjectSystemCPS/LanguageService/IntellisenseErrorList.cs b/src/VisualStudio/ProjectSystemCPS/LanguageService/IntellisenseErrorList.cs index 8f7a0c5d44..4d25ea6297 100644 --- a/src/VisualStudio/ProjectSystemCPS/LanguageService/IntellisenseErrorList.cs +++ b/src/VisualStudio/ProjectSystemCPS/LanguageService/IntellisenseErrorList.cs @@ -26,24 +26,25 @@ internal sealed class IntellisenseErrorList : IDisposable { private readonly JoinableTaskFactory joinableTaskFactory; private readonly string projectName; + private readonly Func> getErrors; private ErrorListProvider provider; - private IReadOnlyList pending; private int refreshScheduled; // Written by Dispose on any thread, read by RefreshAsync on the UI thread private volatile bool disposed; - public IntellisenseErrorList(JoinableTaskFactory joinableTaskFactory, string projectName) + /// Returns the current errors of the project; called once per UI refresh. + public IntellisenseErrorList(JoinableTaskFactory joinableTaskFactory, string projectName, Func> getErrors) { this.joinableTaskFactory = joinableTaskFactory; this.projectName = projectName; + this.getErrors = getErrors; } /// - /// Replace the errors of the project with . + /// The errors of the project changed: refresh the Error List (coalesced, on the UI thread). /// - public void Update(IReadOnlyList errors) + public void Update() { - Volatile.Write(ref pending, errors); if (Interlocked.Exchange(ref refreshScheduled, 1) == 0) { joinableTaskFactory.RunAsync(RefreshAsync).FileAndForget("XSharp/ProjectSystemCPS/ErrorList"); @@ -56,7 +57,8 @@ private async System.Threading.Tasks.Task RefreshAsync() Interlocked.Exchange(ref refreshScheduled, 0); if (disposed) return; - var errors = Volatile.Read(ref pending) ?? Array.Empty(); + // Fetched after refreshScheduled was reset: a change after this point schedules the next refresh + var errors = getErrors() ?? Array.Empty(); if (provider == null) { provider = new ErrorListProvider(ServiceProvider.GlobalProvider) diff --git a/src/VisualStudio/ProjectSystemCPS/LanguageService/IntellisenseErrorStore.cs b/src/VisualStudio/ProjectSystemCPS/LanguageService/IntellisenseErrorStore.cs index 3bd6222cf7..6355bd326c 100644 --- a/src/VisualStudio/ProjectSystemCPS/LanguageService/IntellisenseErrorStore.cs +++ b/src/VisualStudio/ProjectSystemCPS/LanguageService/IntellisenseErrorStore.cs @@ -33,9 +33,22 @@ internal sealed class IntellisenseErrorStore private readonly Dictionary> buildErrors = new Dictionary>(StringComparer.OrdinalIgnoreCase); /// - /// Called with all intellisense errors of the project after each change. + /// Called after each change of the intellisense errors. The receiver fetches the errors with + /// when it needs them (IntellisenseErrorList coalesces the changes and fetches once per + /// UI refresh), so an Add does not copy all errors of the project. /// - public Action> Changed { get; set; } + public Action Changed { get; set; } + + /// + /// All intellisense errors of the project (a copy). + /// + public IReadOnlyList GetAll() + { + lock (errors) + { + return errors.Values.SelectMany(l => l).ToList(); + } + } public void Add(XError error) { @@ -78,18 +91,7 @@ public void SetBuildErrors(string configuration, IEnumerable } } - private void OnChanged() - { - var changed = Changed; - if (changed == null) - return; - List all; - lock (errors) - { - all = errors.Values.SelectMany(l => l).ToList(); - } - changed(all); - } + private void OnChanged() => Changed?.Invoke(); public List Get(string fileName) { diff --git a/src/VisualStudio/ProjectSystemCPS/LanguageService/XSharpProjectAdapter.cs b/src/VisualStudio/ProjectSystemCPS/LanguageService/XSharpProjectAdapter.cs index 6fc8132845..cbf623736d 100644 --- a/src/VisualStudio/ProjectSystemCPS/LanguageService/XSharpProjectAdapter.cs +++ b/src/VisualStudio/ProjectSystemCPS/LanguageService/XSharpProjectAdapter.cs @@ -129,7 +129,7 @@ public async Task LoadAsync() ShadowDesigner.NewFormDesignerRedirect.EnsureAdvised(threading.JoinableTaskFactory); // Saved files are walked again (comment tasks), like XSharpProjectNode.OnFileChanged ProjectFileSaveWatcher.EnsureAdvised(threading.JoinableTaskFactory); - errorList = new IntellisenseErrorList(threading.JoinableTaskFactory, DisplayName); + errorList = new IntellisenseErrorList(threading.JoinableTaskFactory, DisplayName, errors.GetAll); errors.Changed = errorList.Update; taskList = new CommentTaskList(threading.JoinableTaskFactory, DisplayName); From 7c258ddd0cd57ed5fcaf2f74766bdd2670abd499 Mon Sep 17 00:00:00 2001 From: Hansjoerg Petriffer Date: Tue, 29 Sep 2026 09:46:05 +0200 Subject: [PATCH 49/60] [VsIntegration] CPS test bed: SDK-style VO project for the VO designers New VO\VOTestBed.xsproj in SelectorTestBed.sln (classic X# project type GUID, the project selector sends it to CPS), made from the VO SDI Application template (ProjectPackage/Templates/Projects/Windows/VOSdiApp): a VO window (Help About.HELPABOUT.xsfrm), two menus (SDI Menus.*.xsmnu), their .prg/.rc files, the VO designer templates in Properties\ and the resources. Differences from the template: SDK-style (net48, x86, VO dialect and the template's compiler options), no AssemblyInfo.prg (the SDK generates the attributes), no explicit items (the X# SDK globs add .prg/.rc/VO binaries with DependentUpon), and the X# runtime and VO SDK assemblies referenced with a HintPath into the X# installation (XSharpPath from the registry). CpsTestBed.xsproj keeps VO\** out of its globs, including the NativeResource and VOBinary items. StartExp.cmd describes the test projects. Verified: builds from the command line; in VS the selector loads it with CPS, the code model gets all 23 files, the VO window editor opens the window and saving regenerates the .xsfrm and .rc without changing the project file. Co-Authored-By: Claude Opus 5.5 --- .../CpsTestBed.xsproj | 6 +- .../SelectorTestBed.sln | 6 + .../ProjectSystemCPS.TestBed/StartExp.cmd | 4 +- .../VO/Help About.HELPABOUT.xsfrm | 89 ++ .../VO/Help About.HelpAbout.rc | 37 + .../VO/Help About.POWXSHARPBMP.rc | 2 + .../VO/Help About.prg | 89 ++ .../VO/Properties/cavoded.tpl | 225 +++++ .../VO/Properties/cavofed.tpl | 33 + .../VO/Properties/cavowed.inf | 955 ++++++++++++++++++ .../VO/Properties/cavowed.tpl | 464 +++++++++ .../VO/Resources/HelpAbt.BMP | Bin 0 -> 48654 bytes .../VO/Resources/XSharp.ICO | Bin 0 -> 9662 bytes .../VO/Resources/cctl6.man | 23 + .../VO/SDI Menus.EmptySDIMenu.rc | 40 + .../VO/SDI Menus.EmptySDIMenu.xsmnu | 226 +++++ .../VO/SDI Menus.EmptySDIMenu_Accelerator.rc | 27 + .../VO/SDI Menus.StandardSDIMenu.rc | 76 ++ .../VO/SDI Menus.StandardSDIMenu.xsmnu | 515 ++++++++++ .../SDI Menus.StandardSDIMenu_Accelerator.rc | 50 + .../VO/SDI Menus.VS_VERSION_INFO.rc | 31 + .../ProjectSystemCPS.TestBed/VO/SDI Menus.prg | 240 +++++ .../VO/Standard SDI.IDI_STANDARDICON.rc | 1 + .../VO/Standard SDI.Manifest.rc | 4 + .../VO/Standard SDI.prg | 118 +++ .../ProjectSystemCPS.TestBed/VO/Start.prg | 24 + .../VO/VOTestBed.xsproj | 52 + 27 files changed, 3335 insertions(+), 2 deletions(-) create mode 100644 src/VisualStudio/ProjectSystemCPS.TestBed/VO/Help About.HELPABOUT.xsfrm create mode 100644 src/VisualStudio/ProjectSystemCPS.TestBed/VO/Help About.HelpAbout.rc create mode 100644 src/VisualStudio/ProjectSystemCPS.TestBed/VO/Help About.POWXSHARPBMP.rc create mode 100644 src/VisualStudio/ProjectSystemCPS.TestBed/VO/Help About.prg create mode 100644 src/VisualStudio/ProjectSystemCPS.TestBed/VO/Properties/cavoded.tpl create mode 100644 src/VisualStudio/ProjectSystemCPS.TestBed/VO/Properties/cavofed.tpl create mode 100644 src/VisualStudio/ProjectSystemCPS.TestBed/VO/Properties/cavowed.inf create mode 100644 src/VisualStudio/ProjectSystemCPS.TestBed/VO/Properties/cavowed.tpl create mode 100644 src/VisualStudio/ProjectSystemCPS.TestBed/VO/Resources/HelpAbt.BMP create mode 100644 src/VisualStudio/ProjectSystemCPS.TestBed/VO/Resources/XSharp.ICO create mode 100644 src/VisualStudio/ProjectSystemCPS.TestBed/VO/Resources/cctl6.man create mode 100644 src/VisualStudio/ProjectSystemCPS.TestBed/VO/SDI Menus.EmptySDIMenu.rc create mode 100644 src/VisualStudio/ProjectSystemCPS.TestBed/VO/SDI Menus.EmptySDIMenu.xsmnu create mode 100644 src/VisualStudio/ProjectSystemCPS.TestBed/VO/SDI Menus.EmptySDIMenu_Accelerator.rc create mode 100644 src/VisualStudio/ProjectSystemCPS.TestBed/VO/SDI Menus.StandardSDIMenu.rc create mode 100644 src/VisualStudio/ProjectSystemCPS.TestBed/VO/SDI Menus.StandardSDIMenu.xsmnu create mode 100644 src/VisualStudio/ProjectSystemCPS.TestBed/VO/SDI Menus.StandardSDIMenu_Accelerator.rc create mode 100644 src/VisualStudio/ProjectSystemCPS.TestBed/VO/SDI Menus.VS_VERSION_INFO.rc create mode 100644 src/VisualStudio/ProjectSystemCPS.TestBed/VO/SDI Menus.prg create mode 100644 src/VisualStudio/ProjectSystemCPS.TestBed/VO/Standard SDI.IDI_STANDARDICON.rc create mode 100644 src/VisualStudio/ProjectSystemCPS.TestBed/VO/Standard SDI.Manifest.rc create mode 100644 src/VisualStudio/ProjectSystemCPS.TestBed/VO/Standard SDI.prg create mode 100644 src/VisualStudio/ProjectSystemCPS.TestBed/VO/Start.prg create mode 100644 src/VisualStudio/ProjectSystemCPS.TestBed/VO/VOTestBed.xsproj diff --git a/src/VisualStudio/ProjectSystemCPS.TestBed/CpsTestBed.xsproj b/src/VisualStudio/ProjectSystemCPS.TestBed/CpsTestBed.xsproj index 8314ab1fbb..ca42eae950 100644 --- a/src/VisualStudio/ProjectSystemCPS.TestBed/CpsTestBed.xsproj +++ b/src/VisualStudio/ProjectSystemCPS.TestBed/CpsTestBed.xsproj @@ -11,10 +11,14 @@ CpsTestBed - + + + + + \ No newline at end of file diff --git a/src/VisualStudio/ProjectSystemCPS.TestBed/SelectorTestBed.sln b/src/VisualStudio/ProjectSystemCPS.TestBed/SelectorTestBed.sln index 0f89ae0ee9..02d6f9a475 100644 --- a/src/VisualStudio/ProjectSystemCPS.TestBed/SelectorTestBed.sln +++ b/src/VisualStudio/ProjectSystemCPS.TestBed/SelectorTestBed.sln @@ -8,6 +8,8 @@ Project("{AA6C8D78-22FF-423A-9C7C-5F2393824E04}") = "LegacyTestBed", "Legacy\Leg EndProject Project("{AA6C8D78-22FF-423A-9C7C-5F2393824E04}") = "WinFormsTestBed", "WinForms\WinFormsTestBed.xsproj", "{6804AF7F-4D24-45EE-B0B3-6CD0758A7641}" EndProject +Project("{AA6C8D78-22FF-423A-9C7C-5F2393824E04}") = "VOTestBed", "VO\VOTestBed.xsproj", "{361B6896-6AD2-496B-A0F8-1520BFED63C8}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -26,6 +28,10 @@ Global {6804AF7F-4D24-45EE-B0B3-6CD0758A7641}.Debug|Any CPU.Build.0 = Debug|Any CPU {6804AF7F-4D24-45EE-B0B3-6CD0758A7641}.Release|Any CPU.ActiveCfg = Release|Any CPU {6804AF7F-4D24-45EE-B0B3-6CD0758A7641}.Release|Any CPU.Build.0 = Release|Any CPU + {361B6896-6AD2-496B-A0F8-1520BFED63C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {361B6896-6AD2-496B-A0F8-1520BFED63C8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {361B6896-6AD2-496B-A0F8-1520BFED63C8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {361B6896-6AD2-496B-A0F8-1520BFED63C8}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/VisualStudio/ProjectSystemCPS.TestBed/StartExp.cmd b/src/VisualStudio/ProjectSystemCPS.TestBed/StartExp.cmd index b803a443b7..35f90186c3 100644 --- a/src/VisualStudio/ProjectSystemCPS.TestBed/StartExp.cmd +++ b/src/VisualStudio/ProjectSystemCPS.TestBed/StartExp.cmd @@ -7,7 +7,9 @@ rem copies the installed X# MsBuild folder to Artifacts\CpsMsBuild, overlays the rem repository and starts devenv with XSharpMsBuildDir pointing to that folder. The installed X# is not changed. rem rem Usage: StartExp.cmd [solution] (default CpsTestBed.sln; SelectorTestBed.sln tests the project selector: -rem both projects use the classic X# project type GUID, the SDK-style one must be loaded by CPS) +rem all projects use the classic X# project type GUID, the SDK-style ones must be loaded by CPS: +rem CpsTestBed, WinForms\WinFormsTestBed (shadow WinForms designer), VO\VOTestBed (VO designers); +rem Legacy\LegacyTestBed stays on MPFproj) rem rem Prerequisite: a Debug build of VisualStudio\ProjectPackage\ProjectPackage2022.csproj, which deploys the rem VSIX (including XSharp.ProjectSystemCPS) to the experimental instance. diff --git a/src/VisualStudio/ProjectSystemCPS.TestBed/VO/Help About.HELPABOUT.xsfrm b/src/VisualStudio/ProjectSystemCPS.TestBed/VO/Help About.HELPABOUT.xsfrm new file mode 100644 index 0000000000..60c0c248f8 --- /dev/null +++ b/src/VisualStudio/ProjectSystemCPS.TestBed/VO/Help About.HELPABOUT.xsfrm @@ -0,0 +1,89 @@ + + + + + HelpAbout + FORM:DIALOGWINDOW + About Standard Application + 0 + 5 + 5 + 453 + 146 + 0 + 0 + No + No + 0 + 3234332800 + 0 + + ,"About Standard Application" + "HelpAbout" + + + + PushButton1 + CONTROL:TEXTCONTROL:BUTTON:PUSHBUTTON + OK + 2 + 362 + 112 + 80 + 24 + -1 + 0 + No + No + 0 + 1073807361 + 0 + + ,"OK" + "PushButton1" + + + + AboutText + CONTROL:TEXTCONTROL:FIXEDTEXT + VO SDI Application + 1 + 195 + 10 + 245 + 93 + -1 + 0 + No + No + 0 + 1073741952 + 131072 + + ,"VO SDI Application" + "AboutText" + + + + FixedBitmap1 + CONTROL:FIXEDBITMAP + POWXSHARPBMP + 3 + 6 + 10 + 180 + 90 + -1 + 0 + No + No + 0 + 1073741838 + 131072 + + ,"POWXSHARPBMP" + "FixedBitmap1" + + + + \ No newline at end of file diff --git a/src/VisualStudio/ProjectSystemCPS.TestBed/VO/Help About.HelpAbout.rc b/src/VisualStudio/ProjectSystemCPS.TestBed/VO/Help About.HelpAbout.rc new file mode 100644 index 0000000000..3ea2b6722c --- /dev/null +++ b/src/VisualStudio/ProjectSystemCPS.TestBed/VO/Help About.HelpAbout.rc @@ -0,0 +1,37 @@ + +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime version: 4.0.30319.42000 +// Generator : XSharp.FormEditor 2.1.0.0 +// Timestamp : 27/1/2020 18:11:09 ìì +// +// Changes to this file may cause incorrect behavior and may be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +#define HELPABOUT_ABOUTTEXT 100 +#define HELPABOUT_PUSHBUTTON1 101 +#define HELPABOUT_FIXEDBITMAP1 102 +#define BS_DEFPUSHBUTTON 0x00000001 +#define DS_MODALFRAME 0x00000080 +#define SS_BITMAP 0x0000000E +#define SS_NOPREFIX 0x00000080 +#define WS_CAPTION 0x00C00000 +#define WS_CHILD 0x40000000 +#define WS_EX_STATICEDGE 0x00020000 +#define WS_POPUP 0x80000000 +#define WS_SYSMENU 0x00080000 +#define WS_TABSTOP 0x00010000 +#define WS_VISIBLE 0x10000000 + +HelpAbout DIALOGEX 3, 3, 301, 89 +STYLE WS_POPUP|WS_CAPTION|WS_SYSMENU|DS_MODALFRAME +CAPTION "About Standard Application" +FONT 8, "MS Shell Dlg" +BEGIN + CONTROL "VO SDI Application", HELPABOUT_ABOUTTEXT, "STATIC", WS_CHILD|SS_NOPREFIX, 130, 6, 163, 57, WS_EX_STATICEDGE + CONTROL "OK", HELPABOUT_PUSHBUTTON1, "BUTTON", WS_CHILD|WS_TABSTOP|BS_DEFPUSHBUTTON, 241, 69, 53, 15 + CONTROL "POWXSHARPBMP", HELPABOUT_FIXEDBITMAP1, "STATIC", WS_CHILD|SS_BITMAP, 4, 6, 120, 55, WS_EX_STATICEDGE +END diff --git a/src/VisualStudio/ProjectSystemCPS.TestBed/VO/Help About.POWXSHARPBMP.rc b/src/VisualStudio/ProjectSystemCPS.TestBed/VO/Help About.POWXSHARPBMP.rc new file mode 100644 index 0000000000..f871f39b66 --- /dev/null +++ b/src/VisualStudio/ProjectSystemCPS.TestBed/VO/Help About.POWXSHARPBMP.rc @@ -0,0 +1,2 @@ + POWXSHARPBMP BITMAP "Resources\\HelpAbt.BMP" + diff --git a/src/VisualStudio/ProjectSystemCPS.TestBed/VO/Help About.prg b/src/VisualStudio/ProjectSystemCPS.TestBed/VO/Help About.prg new file mode 100644 index 0000000000..cdba8a6a45 --- /dev/null +++ b/src/VisualStudio/ProjectSystemCPS.TestBed/VO/Help About.prg @@ -0,0 +1,89 @@ +#region DEFINES +static define HELPABOUT_ABOUTTEXT := 100 +static define HELPABOUT_PUSHBUTTON1 := 101 +static define HELPABOUT_FIXEDBITMAP1 := 102 +#endregion + +partial class HelpAbout inherit DIALOGWINDOW + protect oDCAboutText as FIXEDTEXT + protect oCCPushButton1 as PUSHBUTTON + protect oDCFixedBitmap1 as FIXEDBITMAP + + // {{%UC%}} User code starts here (DO NOT remove this line) + +constructor(oParent,uExtra) + + self:PreInit(oParent,uExtra) + + super(oParent , ResourceID{"HelpAbout" , _GetInst()} , true) + + self:oDCAboutText := FIXEDTEXT{self , ResourceID{ HELPABOUT_ABOUTTEXT , _GetInst() } } + SELF:oDCAboutText:HyperLabel := HyperLabel{#AboutText , "VO SDI Application" , NULL_STRING , NULL_STRING} + + self:oCCPushButton1 := PUSHBUTTON{self , ResourceID{ HELPABOUT_PUSHBUTTON1 , _GetInst() } } + self:oCCPushButton1:HyperLabel := HyperLabel{#PushButton1 , "OK" , null_string , null_string} + + self:oDCFixedBitmap1 := FIXEDBITMAP{self , ResourceID{ HELPABOUT_FIXEDBITMAP1 , _GetInst() } } + self:oDCFixedBitmap1:HyperLabel := HyperLabel{#FixedBitmap1 , "POWXSHARPBMP" , null_string , null_string} + + self:Caption := "About Standard Application" + self:HyperLabel := HyperLabel{#HelpAbout , "About Standard Application" , null_string , null_string} + + self:PostInit(oParent,uExtra) + +return + + +method PostInit(oParent,uExtra) + local sVer as string + local oSysLink as SysLink + local oFT1 as FixedText + local oHL1 as HyperLink + local oFont1 as Font + local s as string + + sVer := Version() + sVer := SubStr(sVer, RAt2(" ", sVer)+1) + oDCAboutText:CurrentText := _CHR(13)+" VOTestBed"+_CHR(13)+_CHR(13); + +" X# Version "+sVer+_CHR(13)+_CHR(13); + +" Copyright (c) XSharp BV 2015-2022" + + var point := oCCPushButton1:Origin + point:x := self:oDCFixedBitmap1:Origin:x + var size := Dimension{300,20} + + + if IsThemeEnabled() + s := "Visit X# on the web!" + oSysLink := SysLink{self, -1, point, size, s} + oSysLink:Show() + else + + s := "Visit X# on the web:" + oFT1 := FixedText{self, -1, point, dimension{200,20}, s} + oFT1:show() + + oFont1 := Font{,8,"Microsoft Sans Serif"} + oFont1:Underline := true + + oHL1 := HyperLink{self,-1,point,size,"https://www.xsharp.eu"} + oHL1:font(oFont1) + oHL1:size := size + oHL1:textcolor := color{COLORBLUE} + oHL1:font():underline := true + oHL1:show() + + endif + + + return nil + + +method PushButton1() + + self:EndDialog() + +return self + +end class diff --git a/src/VisualStudio/ProjectSystemCPS.TestBed/VO/Properties/cavoded.tpl b/src/VisualStudio/ProjectSystemCPS.TestBed/VO/Properties/cavoded.tpl new file mode 100644 index 0000000000..627763cb84 --- /dev/null +++ b/src/VisualStudio/ProjectSystemCPS.TestBed/VO/Properties/cavoded.tpl @@ -0,0 +1,225 @@ +[CLASS] +CLASS %classname% INHERIT %superclass% + INSTANCE cDBFPath := "%dbfpath%" AS STRING + INSTANCE cName := "%dbfname%" AS STRING + INSTANCE xDriver := %rdd% AS USUAL + INSTANCE lReadOnlyMode:= %ro% AS LOGIC + INSTANCE lSharedMode := %share% AS USUAL + INSTANCE nOrder := %order% AS INT + %declarations% + +[INIT] +METHOD Init(cDBF, lShare, lRO, xRdd) CLASS %classname% + LOCAL oFS AS FILESPEC + LOCAL i AS DWORD + LOCAL nFields AS DWORD + LOCAL aFieldDesc AS ARRAY + LOCAL aIndex AS ARRAY + LOCAL nIndexCount AS DWORD + LOCAL oFSIndex AS FILESPEC + LOCAL nPos AS DWORD + LOCAL lTemp AS LOGIC + LOCAL oFSTemp AS FILESPEC + + + IF IsLogic(lShare) + SELF:lSharedMode := lShare + ELSE + IF !IsLogic(SELF:lSharedMode) + SELF:lSharedMode := !SetExclusive() + ENDIF + ENDIF + + IF IsLogic(lRO) + SELF:lReadOnlyMode := lRO + ENDIF + + IF IsString(xRdd) .OR. IsArray(xRdd) + SELF:xDriver := xRdd + ENDIF + + SELF:PreInit() + + IF IsString(cDBF) + // UH 01/18/2000 + oFSTemp := FileSpec{SELF:cDBFPath + SELF:cName} + oFS := FileSpec{cDBF} + + IF SLen(oFS:Drive) = 0 + oFS:Drive := CurDrive() + ENDIF + IF SLen(oFS:Path) = 0 + oFS:Path := "\" + CurDir() + ENDIF + + IF SLen(oFS:FileName) = 0 + oFS:Filename := SELF:cName + ENDIF + + IF oFS:FullPath == oFSTemp:Fullpath + lTemp := .T. + ELSE + IF Left(cDBF, 2) =='\\' // Unc path, for example \\Server\Share\FileName.DBF + SELF:cDBFPath := oFS:Path + ELSE + SELF:cDBFPath := oFS:Drive + oFS:Path + ENDIF + SELF:cName := oFS:FileName + oFS:Extension + oFS := FileSpec{SELF:cDBFPath + SELF:cName} + ENDIF + ELSE + oFS := FileSpec{SELF:cName} + oFS:Path := SELF:cDBFPath + ENDIF + + + SUPER:Init(oFS, SELF:lSharedMode, SELF:lReadOnlyMode , SELF:xDriver ) + + oHyperLabel := HyperLabel{#%hlname%, "%hlcaption%", "%hldescription%", "%hlhelpcontext%"} + + IF oHLStatus = NIL + nFields := ALen(aFieldDesc := SELF:FieldDesc) + FOR i:=1 UPTO nFields + nPos := SELF:FieldPos( aFieldDesc[i][DBC_NAME] ) + + SELF:SetDataField( nPos,; + DataField{aFieldDesc[i][DBC_SYMBOL],aFieldDesc[i][DBC_FIELDSPEC]} ) + + IF String2Symbol(aFieldDesc[i][DBC_NAME]) != aFieldDesc[i][DBC_SYMBOL] + SELF:FieldInfo(DBS_ALIAS, nPos, Symbol2String(aFieldDesc[i][DBC_SYMBOL]) ) + ENDIF + NEXT + + nIndexCount := ALen(aIndex:=SELF:IndexList) + + FOR i:=1 UPTO nIndexCount + oFSIndex := FileSpec{ aIndex[i][DBC_INDEXNAME] } + oFSIndex:Path := SELF:cDBFPath + + IF lTemp .AND. !Empty( aIndex[i][DBC_INDEXPATH] ) + oFSIndex:Path := aIndex[i][DBC_INDEXPATH] + ENDIF + + IF oFSIndex:Find() + lTemp := SELF:SetIndex( oFSIndex ) + ENDIF + NEXT + + // UH 01/18/2000 + // SELF:nOrder > 0 + IF lTemp .AND. SELF:nOrder > 0 + SELF:SetOrder(SELF:nOrder) + ENDIF + + SELF:GoTop() + ENDIF + + SELF:PostInit() + + RETURN SELF + + +[FIELDDESC] +ACCESS FieldDesc CLASS %classname% + // + // Describes all fields selected by DBServer-Editor + // + LOCAL aRet AS ARRAY + LOCAL nFields AS DWORD + + nFields := %fieldcount% + + IF nFields > 0 + aRet := ArrayCreate(nFields) + + // + // The following code creates an array of field + // descriptors with these items for each + // selected field: + // + // { , , } + // + // Use following predefined constants to access + // each subarray: + // + // DBC_SYMBOL + // DBC_NAME + // DBC_FIELDSPEC + // + %fielddesc% + + ELSE + aRet := {} + ENDIF + + + RETURN aRet + + +[INDEXLIST] +ACCESS IndexList CLASS %classname% + // + // Describes all index files created or selected + // by DBServer-Editor + // + LOCAL aRet AS ARRAY + LOCAL nIndexCount AS DWORD + + nIndexCount := %indexcount% + + IF nIndexCount > 0 + aRet := ArrayCreate(nIndexCount) + + // + // The following code creates an array of index + // file descriptors with these items for each + // selected index file: + // + // { , , } + // + // Use following predefined constants to access + // each subarray: + // + // DBC_INDEXNAME + // DBC_INDEXPATH + // DBC_ORDERS + // + // Array contains an array of + // order descriptors with these items for each + // order: + // + // { , , , , } + // + // Use following predefined constants to access + // aOrder subarrays: + // + // DBC_TAGNAME + // DBC_DUPLICATE + // DBC_ASCENDING + // DBC_KEYEXP + // DBC_FOREXP + // + %indexlist% + + ELSE + aRet := {} + ENDIF + + RETURN aRet + + +[PREINIT] +METHOD PreInit() class %classname% + //Put your PreInit additions here + RETURN NIL + +[POSTINIT] +METHOD PostInit() class %classname% + //Put your PostInit additions here + RETURN NIL + +[MACROS] +FieldDesc=\r\n\t\taRet[%pos%] := { #%hlname%, \"%fldname%\", %classname%{}} +Index=\r\n\t\taRet[%pos%] := { \"%indexname%\", \"%dbfpath%\",; \r\n \t\t\t\t\t{%orderlist% } } +Order={ \"%tag%\", %duplicate%, %ascending%, [%keyexp%], [%forexp%] } +Declaration=\r\n\tDECLARE ACCESS %hlname%\r\n\tDECLARE ASSIGN %hlname% diff --git a/src/VisualStudio/ProjectSystemCPS.TestBed/VO/Properties/cavofed.tpl b/src/VisualStudio/ProjectSystemCPS.TestBed/VO/Properties/cavofed.tpl new file mode 100644 index 0000000000..11665a502f --- /dev/null +++ b/src/VisualStudio/ProjectSystemCPS.TestBed/VO/Properties/cavofed.tpl @@ -0,0 +1,33 @@ +[CLASS] +CLASS %classname% INHERIT %superclass% + + +[INIT] +METHOD Init() CLASS %classname% + LOCAL cPict AS STRING + + SUPER:Init( HyperLabel{#%hlname%, "%hlcaption%", "%hldescription%", "%hlhelpcontext%" }, "%type%", %len%, %dec% ) + cPict := "%picture" + IF SLen(cPict) > 0 + SELF:Picture := cPict + ENDIF + + RETURN SELF + + + +[DBSERVER] +Method GetFieldInfo%hlname%() CLASS %servername% +RETURN {#%hlname%, "%hlcaption%","%hldescription%","%hlhelpcontext%"} + +[DBSERVER] +ACCESS %hlname% AS %usualtype% PASCAL CLASS %servername% + + RETURN SELF:FieldGet(#%hlname%) + + +[DBSERVER] +ASSIGN %hlname%(uValue AS %usualtype%) AS %usualtype% PASCAL CLASS %servername% + + RETURN SELF:FieldPut(#%hlname%, uValue) + diff --git a/src/VisualStudio/ProjectSystemCPS.TestBed/VO/Properties/cavowed.inf b/src/VisualStudio/ProjectSystemCPS.TestBed/VO/Properties/cavowed.inf new file mode 100644 index 0000000000..07d5788d18 --- /dev/null +++ b/src/VisualStudio/ProjectSystemCPS.TestBed/VO/Properties/cavowed.inf @@ -0,0 +1,955 @@ +[FORM] +Assign01=Name,HyperLabel(%Name%,%Caption%,,) +Assign02=Caption,HyperLabel(,lstring,,) +Assign03=Description,HyperLabel(,,string,) +Assign04=Help Context,HyperLabel(,,,string) +Assign05=Clipper Keys,ClipperKeys(BOOL) +Assign06=Large Icon,Icon(CLASS:ICON) +Assign07=Small Icon,IconSm(CLASS:ICON) +Assign08=Accelerator Table,Accelerator(CLASS:Accelerator) +Assign09=Background Color,Background(COLOR) +Assign10=Menu,Menu(CLASS:Menu) +Assign11=Context Menu,ContextMenu(CLASS:Menu) +Assign12=Help File Name,HelpDisplay(string) +Assign13=Prevent Auto Layout,PreventAutoLayout(BOOL) +Assign14=Defer USE,DeferUse(BOOL) +Assign15=Quit On Close,QuitOnClose(BOOL) +Assign16=Automated,Automated(BOOL) +Assign17=Allow Server Close,AllowServerClose(BOOL) + +Method01=Activate,Activate(CODE:Activate) +Method02=ButtonClick,ButtonClick(CODE:ButtonClick) +Method03=ButtonDoubleClick,ButtonDoubleClick(CODE:ButtonDoubleClick) +Method04=Close,Close(CODE:Close) +Method05=Deactivate,Deactivate(CODE:Deactivate) +Method06=Draw,Draw(CODE:Draw) +Method07=EditChange,EditChange(CODE:EditChange) +Method08=EditFocusChange,EditFocusChange(CODE:EditFocusChange) +Method09=EditScroll,EditScroll(CODE:EditScroll) +Method10=Expose,Expose(CODE:Expose) +Method11=FocusChange,FocusChange(CODE:FocusChange) +Method12=HelpRequest,HelpRequest(CODE:HelpRequest) +Method13=HorizontalScroll,HorizontalScroll(CODE:HorizontalScroll) +Method14=Keydown,Keydown(CODE:Keydown) +Method15=Keyup,Keyup(CODE:Keyup) +Method16=ListBoxClick,ListBoxClick(CODE:ListBoxClick) +Method17=ListBoxSelect,Show(CODE:ListBoxSelect) +Method18=MenuCommand,MenuCommand(CODE:MenuCommand) +Method19=MenuInit,MenuInit(CODE:MenuInit) +Method20=MenuSelect,MenuSelect(CODE:MenuSelect) +Method21=MouseButtonDoubleClick,MouseButtonDoubleClick(CODE:MouseButtonDoubleClick) +Method22=MouseButtonDown,MouseButtonDown(CODE:MouseButtonDown) +Method23=MouseButtonUp,MouseButtonUp(CODE:MouseButtonUp) +Method24=MouseDrag,MouseDrag(CODE:MouseDrag) +Method25=MouseMove,MouseMove(CODE:MouseMove) +Method26=Move,Move(CODE:Move) +Method27=QueryClose,QueryClose(CODE:QueryClose) +Method28=Resize,Resize(CODE:Resize) +Method29=VerticalScroll,VerticalScroll(CODE:VerticalScroll) +Method30=View As,ViewAs(VIEWS) +Method31=Data Server,Use(CLASS:DBSERVER:+SQLTable:+JDataServer) +Method32=Font,Font(FONT) +Method33=Notify,Notify(CODE:Notify) +Method34=PreValidate,PreValidate(CODE:PreValidate) +Method35=HorizontalSlide,HorizontalSlide(CODE:HorizontalSlide) +Method36=VerticalSlide,VerticalSlide(CODE:VerticalSlide) +Method37=HorizontalSpin,HorizontalSpin(CODE:HorizontalSpin) +Method38=VerticalSpin,VerticalSpin(CODE:VerticalSpin) +Method39=PreInit Actions,PreInit(CODE:PreInit) +Method40=PostInit Actions,PostInit(CODE:PostInit) +Method41=StatusBar,EnableStatusBar(BOOL) +Method42=AnimationStart,AnimationStart(CODE:AnimationStart) +Method43=AnimationStop,AnimationStop(CODE:AnimationStop) +Method44=RichEditDropFiles,RichEditDropFiles(CODE:RichEditDropFiles) +Method45=RichEditProtected,RichEditProtected(CODE:RichEditProtected) +Method46=RichEditSelChange,RichEditSelChange(CODE:RichEditSelectionChange) +Method47=RichEditUndoLost,RichEditUndoLost(CODE:RichEditUndoLost) +Method48=ListViewItemDrag,ListViewItemDrag(CODE:ListViewItemDrag) +Method49=ListViewItemEdit,ListViewItemEdit(CODE:ListViewItemEdit) +Method50=ListViewColumnClick,ListViewColumnClick(CODE:ListViewColumnClick) +Method51=ListViewItemDelete,ListItemDelete(CODE:ListViewItemDelete) +Method52=ListViewKeyDown,ListKeyDown(CODE:ListViewKeyDown) +Method53=TreeViewMouseDown,TreeViewMouseButtonDown(CODE:TreeViewMouseButtonDown) +Method54=ListViewMouseDown,ListViewMouseButtonDown(CODE:ListViewMouseButtonDown) +Method55=TreeViewMouseDoubleClick,TreeViewMouseDoubleClick(CODE:TreeViewMouseButtonDoubleClick) +Method56=ListViewMouseDoubleClick,ListViewMouseDoubleClick(CODE:ListViewMouseButtonDoubleClick) +Method57=TabSelect,TabSelect(CODE:TabSelect) +Method58=TabSelChanging,TabSelChanging(CODE:TabSelectionChanging) +Method59=TabKeyDown,TabKeyDown(CODE:TabKeyDown) +Method60=TreeViewItemDrag,TreeViewItemDrag(CODE:TreeViewItemDrag) +Method61=TreeViewItemEdit,TreeViewItemEdit(CODE:TreeViewItemEdit) +Method62=TreeViewItemDelete,TreeViewItemDelete(CODE:TreeViewItemDelete) +Method63=TreeViewItemExpanded,TreeViewItemExpanded(CODE:TreeViewItemExpanded) +Method64=TreeViewItemExpanding,TreeViewItemExpanding(CODE:TreeViewItemExpanding) +Method65=TreeViewKeyDown,TreeViewKeyDown(CODE:TreeViewKeyDown) +Method66=TreeViewSelChanged,TreeViewSelChanged(CODE:TreeViewSelectionChanged) +Method67=TreeViewSelChanging,TreeViewSelChanging(CODE:TreeViewSelectionChanging) +Method68=Class Declaration,ClassDeclaration(CODE:ClassDeclaration) +Method69=MonthCalSelChanged,MonthCalSelChanged(CODE:MonthCalSelectionChanged) +Method70=DateTimeSelChanged,DateTimeSelChanged(CODE:DateTimeSelectionChanged) +Method90=No Access/Assign,NoAcc(LOGIC) +Method91=Pixel Positions,PixPos(LOGIC) +Method92=Export Controls,ExpCtls(LOGIC) + +WindowStyle01=Tab Key,WS_CHILD:WS_CHILD|DS_CONTROL(TABKEY) + +[FORM:DATAWINDOW] +ContTitle=Data Window +text=DataWindow Caption +InitMethod=DATAWINDOW(oWindow,iCtlID,oServer,uExtra) +InheritClassName=Inherit from Class,InheritClassName(CLASS:DataWindow) +Name=MyDataWindow +Size=420,320 +DialogTemplate=1 +Position=5,5 +Style=WS_CHILD +HelpID=2065 +PropTabs=HyperLabel,DataWindow,Mouse Events,Window Events,Control Events,Menu Events,CommonControl Events,TreeView Events,ListView Events +Assign18=Owner Alignment,OwnerAlignment(OWNERALIGNMENT) + +[FORM:DATADIALOG] +ContTitle=Data Dialog +text=DataDialog Caption +InitMethod=DATADIALOG(oWindow,iCtlID,oServer,uExtra) +InheritClassName=Inherit from Class,InheritClassName(CLASS:DataDialog) +Name=MyDataDialog +Size=400,240 +DialogTemplate=1 +Position=5,5 +Style=WS_CHILD +HelpID=2490 +PropTabs=HyperLabel,DataDialog,Mouse Events,Window Events,Control Events,Menu Events,CommonControl Events,TreeView Events,ListView Events + +[FORM:DATAWINDOW:OLEDATAWINDOW] +ContTitle=OLE Data Window +text=OLE DataWindow Caption +InitMethod=OLEDATAWINDOW(oWindow,iCtlID,oServer,uExtra) +InheritClassName=Inherit from Class,InheritClassName(CLASS:OleDataWindow) +Name=MyOLEDataWindow +Size=420,320 +DialogTemplate=1 +Position=5,5 +Style=WS_CHILD +HelpID=2065 +PropTabs=HyperLabel,DataWindow,Mouse Events,Window Events,Control Events,Menu Events,CommonControl Events,TreeView Events,ListView Events + + +[FORM:SHELLWINDOW] +Assign01=Mouse Pointer,Pointer(CLASS:POINTER) +Assign02=Menu,Menu(CLASS:Menu) +Assign03=Help File Name,HelpDisplay(string) +ContTitle=Shell Window +text=ShellWindow Caption +InitMethod=SHELLWINDOW(oParent,uExtra) +InheritClassName=Inherit from Class,InheritClassName(CLASS:ShellWindow) +Name=TheShellWindow +Size=400,300 +Position=5,5 +Style=WS_OVERLAPPEDWINDOW +HelpID=2060 +PropTabs=HyperLabel,ShellWindow,Mouse Events,Window Events,Control Events,Menu Events + +[FORM:DIALOGWINDOW] +Method01=Font,Font(FONT) +Method02=Modeless,Modeless(LOGIC) +Assign01=Background Color,Background(COLOR) +Assign02=Mouse Pointer,Pointer(CLASS:POINTER) +Assign03=Help File Name,HelpDisplay(string) +WindowStyle01=Disabled,WS_DISABLED(BOOL) +WindowStyle03=Clip Siblings,WS_CLIPSIBLINGS(BOOL) +WindowStyle04=Local Edit,DS_LOCALEDIT(BOOL) +WindowStyle05=Absolute Align,DS_ABSALIGN(BOOL) +WindowStyle06=Clip Children,WS_CLIPCHILDREN(BOOL) +WindowStyle09=System Modal,DS_SYSMODAL(BOOL) +WindowStyle10=No Idle Message,DS_NOIDLEMSG(BOOL) +WindowStyle11=Resizable,WS_THICKFRAME:DS_MODALFRAME(BOOL) +WindowStyle12=Caption Bar,WS_NULL:WS_CAPTION:WS_CAPTION|WS_SYSMENU:WS_CAPTION|WS_SYSMENU|WS_MINIMIZEBOX:WS_CAPTION|WS_SYSMENU|WS_MAXIMIZEBOX:WS_CAPTION|WS_SYSMENU|WS_MINIMIZEBOX|WS_MAXIMIZEBOX(CAPTIONBAR) +WindowStyle13=Fixed Font,DS_FIXEDSYS(BOOL) +WindowStyle14=Position,WS_NULL:DS_CENTER:DS_CENTERMOUSE(DIALOGPOSITON) +WindowStyle15=Type,WS_POPUP:WS_CHILD|DS_CONTROL(DIALOGTYPE) +ContTitle=Dialog Window +DialogTemplate=1 +text=Dialog Caption +InitMethod=DialogWindow(oParent,uExtra) +InheritClassName=Inherit from Class,InheritClassName(CLASS:DialogWindow) +Name=TheDialog +Size=400,240 +Position=5,5 +Style=DS_MODALFRAME|WS_POPUP|WS_CAPTION|WS_SYSMENU|DS_3DLOOK +ExStyle=WS_EX_DLGMODALFRAME +HelpID=2070 +PropTabs=HyperLabel,DlgWindow,DlgWndStyles:Styles,Mouse Events,Window Events,Control Events,CommonControl Events,TreeView Events,ListView Events,Menu Events + +[CONTROL] +Assign01=Name,HyperLabel(%Name%,%Caption%,,) +Assign02=Caption,HyperLabel(,lstring,,) +Assign03=Description,HyperLabel(,,string,) +Assign04=Help Context,HyperLabel(,,,string) +Assign05=Background Color,Background(COLOR) +Assign06=Context Menu,ContextMenu(CLASS:Menu) +Assign07=Tooltip,TooltipText(string) +Assign08=Owner Alignment,OwnerAlignment(OWNERALIGNMENT) +WindowStyle01=Disabled,WS_DISABLED(BOOL) +WindowStyle02=Tab Stop,WS_TABSTOP(BOOL) +WindowStyle03=Group,WS_GROUP(BOOL) +WindowStyle04=Border,WS_BORDER(BOOL) +WindowStyle05=Vertical Scrollbar,WS_VSCROLL(BOOL) +WindowStyle06=Horizontal Scrollbar,WS_HSCROLL(BOOL) +WindowStyle07=Clip Siblings,WS_CLIPSIBLINGS(BOOL) +ExWindowStyle01=Modal Frame,WS_EX_DLGMODALFRAME(BOOL) +ExWindowStyle02=Static Edge,WS_EX_STATICEDGE(BOOL) +ExWindowStyle03=Client Edge,WS_EX_CLIENTEDGE(BOOL) +ExWindowStyle04=Transparent,WS_EX_TRANSPARENT(BOOL) +ExWindowStyle05=Accept Files,WS_EX_ACCEPTFILES(BOOL) +ExWindowStyle06=No Parent Notify,WS_EX_NOPARENTNOTIFY(BOOL) +ExWindowStyle07=Right-To-Left Reading,WS_EX_RTLREADING(BOOL) +ExWindowStyle08=ExAlignment,WS_EX_LEFT:WS_EX_RIGHT(EXALIGNMENT) + +[CONTROL:CUSTOMCONTROL] +Assign01=Use for Tooltip,UseHLforToolTip(BOOL) +ContTitle=Custom Control +Name=CustomControl +ClassName=static +OptClassName=_VOCustomControl +Size=240,140 +HelpID=2915 +Style=WS_CHILD|WS_BORDER +PropTabs=HyperLabel,GenCustCtrl:General,CustomCtlStyles:Styles,ExStyles +InitMethod=CustomControl(PARENT,ID,POSITION,SIZE) +InheritClassName=Inherit from Class,InheritClassName(CLASS:CustomControl) + +[CONTROL:MULTIMEDIACONTAINER] +Assign01=File Name,FileName(STRING) +Assign02=Field Spec,FieldSpec(CLASS:FIELDSPEC) +ContTitle=MulitMedia Container +Name=MultiMediaContainer +ClassName=__PTSTBCN__ +OptClassName=_VOMMContainer +Size=240,140 +;HelpID=2915 +Style=WS_CHILD|WS_BORDER +PropTabs=HyperLabel,GenMMCont:General,MMContStyles:Styles,ExStyles +InitMethod=MultiMediaContainer(PARENT,ID,POSITION,SIZE) +InheritClassName=Inherit from Class,InheritClassName(CLASS:MultiMediaContainer) + +[CONTROL:OLEGEN] +Assign01=Use for Tooltip,UseHLforToolTip(BOOL) +Assign02=AllowResize,AllowResize(BOOL) +Assign03=AutoSizeOnCreate,AutoSizeOnCreate(BOOL) +Assign04=ReadOnly,ReadOnly(BOOL) +Assign05=AllowDocView,AllowDocView(BOOL) + +[CONTROL:OLEGEN:OLEOBJECT] +Assign01=AllowInPlace,AllowInPlace(BOOL) +Assign02=ActivateOnDblClk,ActivateOnDblClk(BOOL) +Assign03=Field Spec,FieldSpec(CLASS:FIELDSPEC) +ContTitle=OLE Object +Name=OLEObject +ClassName=__PTSTBCN__ +OptClassName=_VOOleContainer +Size=240,140 +PropTabs=HyperLabel,GenOleObject:General,OLEObjStyles:Styles,ExStyles +Style=WS_CHILD|WS_BORDER +InitMethod=OLEObject(PARENT,ID,POSITION,SIZE) +InheritClassName=Inherit from Class,InheritClassName(CLASS:OLEObject) + +[CONTROL:OLEGEN:OLECONTROL] +ContTitle=OLE Control +Name=OLEControl +ClassName=__PTSTBCN__ +OptClassName=_VOOleContainer +Size=240,140 +Style=WS_CHILD +HelpID=2290 +PropTabs=HyperLabel,GenOleControl:General,OCXProperties:Properties,OCXEvents:Events,OLEObjStyles:Styles,ExStyles +InitMethod=OLEControl(PARENT,ID,POSITION,SIZE) +InheritClassName=Inherit from Class,InheritClassName(CLASS:OLEControl) + +[CONTROL:SUBDATAWINDOW] +ContTitle=Sub-Data Window +ClassName=__PSBDWN__ +Name=Sub_Form +Size=240,140 +Style=WS_CHILD|WS_BORDER +HelpID=2225 +PropTabs=GenSubData:General,SubDataStyles:Styles +Method1=Relation String,RelationString(string) +Method2=Order,Order(string) + +[CONTROL:FIXEDICON] +Method01=Icon,SetIcon(CLASS:ICON) +ContTitle=Fixed Icon +ClassName=static +Name=FixedIcon +Size=32,32 +GenerateCode=1 +Style=WS_CHILD|SS_ICON|WS_BORDER +HelpID=2205 +PropTabs=HyperLabel,GenFixedImage:General,FixedImageStyles:Styles,ExStyles +InitMethod=FIXEDICON(PARENT,ID,POSITION,SIZE) +InheritClassName=Inherit from Class,InheritClassName(CLASS:FixedIcon) +WindowStyle08=Real Size Image,SS_REALSIZEIMAGE(BOOL) +WindowStyle09=Center Image,SS_CENTERIMAGE(BOOL) +WindowStyle10=Right adjust,SS_RIGHTJUST(BOOL) + +[CONTROL:FIXEDBITMAP] +Method01=Bitmap,SetBitmap(CLASS:BITMAP) +ContTitle=Fixed Bitmap +ClassName=static +Name=FixedBitmap +Size=64,64 +GenerateCode=1 +Style=WS_CHILD|SS_BITMAP|WS_BORDER +HelpID=2565 +PropTabs=HyperLabel,GenFixedImage:General,FixedImageStyles:Styles,ExStyles +InitMethod=FIXEDBITMAP(PARENT,ID,POSITION,SIZE) +InheritClassName=Inherit from Class,InheritClassName(CLASS:FixedBitmap) +WindowStyle09=Center Image,SS_CENTERIMAGE(BOOL) +WindowStyle10=Right adjust,SS_RIGHTJUST(BOOL) + +[CONTROL:SCROLLBAR] +Assign01=Use for Tooltip,UseHLforToolTip(BOOL) +Assign02=Field Spec,FieldSpec(CLASS:FIELDSPEC) +Assign03=Min Value,Range(numeric,) +Assign04=Max Value,Range(,numeric) +WindowStyle01=AutoSize,SBS_SIZEBOXTOPLEFTALIGN(BOOL) +WindowStyle02=Size Grip,SBS_SIZEGRIP|SBS_SIZEBOXBOTTOMRIGHTALIGN(BOOL) + +[CONTROL:SCROLLBAR:HORIZONTALSCROLLBAR] +ContTitle=Horizontal Scroll Bar +ClassName=scrollbar +Name=HorzScroll +Size=140,20 +Style=WS_CHILD|SBS_HORZ +HelpID=2215 +PropTabs=HyperLabel,GenScrollbar:General,ScrollBarStyles:Styles,ExStyles +InitMethod=HorizontalScrollBar(PARENT,ID,POSITION,SIZE) +InheritClassName=Inherit from Class,InheritClassName(CLASS:HorizontalScrollBar) + +[CONTROL:SCROLLBAR:VERTICALSCROLLBAR] +ContTitle=Vertical Scroll Bar +ClassName=scrollbar +Name=VertScroll +Size=20,140 +Style=WS_CHILD|SBS_VERT +HelpID=2220 +PropTabs=HyperLabel,GenScrollbar:General,ScrollBarStyles:Styles,ExStyles +InitMethod=VerticalScrollBar(PARENT,ID,POSITION,SIZE) +InheritClassName=Inherit from Class,InheritClassName(CLASS:VerticalScrollBar) + +[CONTROL:TEXTCONTROL] +Method01=Font,Font(FONT) +Method02=Input Method Editor,Ime(OLOGIC) +Assign01=Text Color,TextColor(COLOR) +Assign02=Field Spec,FieldSpec(CLASS:FIELDSPEC) + +[CONTROL:TEXTCONTROL:EDIT] +Assign01=Use for Tooltip,UseHLforToolTip(BOOL) +Assign02=Text Limit,TextLimit(numeric) +WindowStyle09=Password,ES_PASSWORD(BOOL) +WindowStyle10=Retain Selection,ES_NOHIDESEL(BOOL) +WindowStyle11=OEM Conversion,ES_OEMCONVERT(BOOL) +WindowStyle12=Auto HScroll,ES_AUTOHSCROLL(BOOL) +WindowStyle13=Read Only,ES_READONLY(BOOL) +WindowStyle14=Conversion,WS_NULL:ES_UPPERCASE:ES_LOWERCASE(EDITCONVERT) +WindowStyle15=Numbers Only,ES_NUMBER(BOOL) +WindowStyle19=Alignment,ES_LEFT:ES_CENTER:ES_RIGHT(EDITALIGN) + +[CONTROL:TEXTCONTROL:EDIT:MULTILINEEDIT] +WindowStyle17=Auto VScroll,ES_AUTOVSCROLL(BOOL) +WindowStyle18=Return Key,ES_WANTRETURN(BOOL) +ContTitle=Multi-Line Edit +ClassName=edit +Name=MultiLineEdit +Size=80,60 +Style=WS_CHILD|ES_MULTILINE|WS_TABSTOP|WS_BORDER|ES_AUTOVSCROLL|ES_AUTOHSCROLL|ES_WANTRETURN +ExStyle=WS_EX_CLIENTEDGE +HelpID=2190 +PropTabs=HyperLabel,GenEdit:General,MultiLineStyles:Styles,ExStyles +InitMethod=MultiLineEdit(PARENT,ID,POSITION,SIZE) +InheritClassName=Inherit from Class,InheritClassName(CLASS:MultiLineEdit) + +[CONTROL:TEXTCONTROL:EDIT:SINGLELINEEDIT] +Assign01=Block,Block(string) +Assign02=Block Owner,BlockOwner(STRING) +Assign03=OverWrite,OverWrite(OVERWRITE) +Assign04=Picture,Picture(STRING) +Assign05=Scroll Mode,ScrollMode(SCRMODE) +Assign06=Focus Select,FocusSelect(FSELECT) +Assign07=Auto Focus Change,AutoFocusChange(BOOL) +ContTitle=Single-Line Edit +ClassName=edit +Name=SingleLineEdit +Size=80,20 +Style=WS_CHILD|WS_TABSTOP|WS_BORDER|ES_AUTOHSCROLL +ExStyle=WS_EX_CLIENTEDGE +HelpID=2185 +PropTabs=HyperLabel,GenSingleEdit:General,SingleLineStyles:Styles,ExStyles +InitMethod=SingleLineEdit(PARENT,ID,POSITION,SIZE) +InheritClassName=Inherit from Class,InheritClassName(CLASS:SingleLineEdit) + +[CONTROL:TEXTCONTROL:BASELISTBOX] +Assign01=Use for Tooltip,UseHLforToolTip(BOOL) +Assign02=Field Spec,FieldSpec(CLASS:FIELDSPEC) +Assign03=Text Color,TextColor(COLOR) +Method01=Fill Using,FillUsing( ) + +[CONTROL:TEXTCONTROL:BASELISTBOX:LISTBOX] +WindowStyle22=Sort,LBS_SORT(BOOL) +WindowStyle23=Notify Parent,LBS_NOTIFY(BOOL) +WindowStyle24=Use Tab Stops,LBS_USETABSTOPS(BOOL) +WindowStyle25=No Redraw,LBS_NOREDRAW(BOOL) +WindowStyle26=Multi Column,LBS_MULTICOLUMN(BOOL) +WindowStyle27=No Integral Height,LBS_NOINTEGRALHEIGHT(BOOL) +WindowStyle28=Want Keyboard Input,LBS_WANTKEYBOARDINPUT(BOOL) +WindowStyle29=Multiple Selection,LBS_MULTIPLESEL(BOOL) +WindowStyle30=Extended Selection,LBS_EXTENDEDSEL(BOOL) +WindowStyle31=Disable if no Scroll,LBS_DISABLENOSCROLL(BOOL) +WindowStyle32=Owner Draw,WS_NULL:LBS_OWNERDRAWFIXED:LBS_OWNERDRAWVARIABLE(OWNERDRAW) +ContTitle=List Box +ClassName=listbox +Name=ListBox +Size=80,80 +Style=WS_TABSTOP|WS_CHILD|WS_VSCROLL|LBS_NOTIFY|WS_BORDER|LBS_SORT|LBS_NOINTEGRALHEIGHT|LBS_DISABLENOSCROLL +ExStyle=WS_EX_CLIENTEDGE +HelpID=2160 +PropTabs=HyperLabel,GenListbox:General,ListBoxStyles:Styles,ExStyles +InitMethod=ListBox(PARENT,ID,POSITION,SIZE) +InheritClassName=Inherit from Class,InheritClassName(CLASS:ListBox:-ComboBox) + +[CONTROL:TEXTCONTROL:BASELISTBOX:COMBOBOX] +WindowStyle32=ComboBox Type,CBS_SIMPLE:CBS_DROPDOWN:CBS_DROPDOWNLIST(CBTYPE) +WindowStyle33=Sort,CBS_SORT(BOOL) +WindowStyle34=Auto HScroll,CBS_AUTOHSCROLL(BOOL) +WindowStyle35=No Integral Height,CBS_NOINTEGRALHEIGHT(BOOL) +WindowStyle36=OEM Convert,CBS_OEMCONVERT(BOOL) +WindowStyle37=Disable if no Scroll,CBS_DISABLENOSCROLL(BOOL) +WindowStyle38=OwnerDraw,WS_NULL:CBS_OWNERDRAWFIXED:CBS_OWNERDRAWVARIABLE(OWNERDRAW) +ContTitle=Combo Box +ClassName=combobox +Name=ComboBox +Size=80,120 +Style=WS_TABSTOP|WS_CHILD|CBS_DROPDOWN|WS_VSCROLL|CBS_SORT|CBS_DISABLENOSCROLL +HelpID=2165 +PropTabs=HyperLabel,GenListbox:General,ComboBoxStyles:Styles,ExStyles +InitMethod=combobox(PARENT,ID,POSITION,SIZE) +InheritClassName=Inherit from Class,InheritClassName(CLASS:ComboBox) + + +[CONTROL:TEXTCONTROL:BUTTON] +assign01=Use for Tooltip,UseHLforToolTip(BOOL) +assign02=Image,Image(CLASS:Icon:+Bitmap) +WindowStyle06=Text Left,BS_LEFTTEXT(BOOL) +WindowStyle07=Multiline,BS_MULTILINE(BOOL) +WindowStyle08=Flat,BS_FLAT(BOOL) +WindowStyle09=Horizontal Alignment,WS_NULL:BS_LEFT:BS_RIGHT:BS_CENTER(BUTTONHALIGN) +WindowStyle10=Vertical Alignment,WS_NULL:BS_TOP:BS_BOTTOM:BS_VCENTER(BUTTONVALIGN) +WindowStyle11=Image Type,WS_NULL:BS_ICON:BS_BITMAP:WS_NULL(BUTTONIMAGETYPE) +WindowStyle12=Push Like,BS_PUSHLIKE(BOOL) + +[CONTROL:TEXTCONTROL:BUTTON:CHECKBOX] +Assign01=Field Spec,FieldSpec(CLASS:FIELDSPEC) +Assign02=Text Color,TextColor(COLOR) +WindowStyle05=Selection,BS_CHECKBOX:BS_AUTOCHECKBOX:BS_3STATE:BS_AUTO3STATE(CBAUTOSTYLE) +ContTitle=Check Box +ClassName=button +Name=CheckBox +Text=CheckBox +Size=120,18 +HelpID=2155 +Style=WS_TABSTOP|WS_CHILD|BS_AUTOCHECKBOX +PropTabs=HyperLabel,GenCheckbox:General,CheckBoxStyles:Styles,ExStyles +InitMethod=CheckBox(PARENT,ID,POSITION,SIZE) +InheritClassName=Inherit from Class,InheritClassName(CLASS:CheckBox) + +[CONTROL:TEXTCONTROL:BUTTON:PUSHBUTTON] +WindowStyle04=Default Button,BS_DEFPUSHBUTTON(BOOL) +Event01=Click Event,MyBtnName( ) +ContTitle=Push Button +Type=CommandControl +ClassName=button +Name=PushButton +text=Push +Size=80,20 +Style=WS_CHILD|WS_TABSTOP +PropTabs=HyperLabel,GenPushButton:General,PushButtonStyles:Styles,ExStyles +HelpID=2170 +InitMethod=PushButton(PARENT,ID,POSITION,SIZE) +InheritClassName=Inherit from Class,InheritClassName(CLASS:PushButton) + +[CONTROL:TEXTCONTROL:BUTTON:RADIOBUTTON] +Method01=Group Value,Value(numeric) +Assign01=Text Color,TextColor(COLOR) +WindowStyle07=AutoSelect,BS_AUTORADIOBUTTON:BS_RADIOBUTTON(BOOL) +ContTitle=Radio Button +Type=CommandControl +ClassName=button +Name=RadioButton +text=RadioButton +Size=120,18 +Style=WS_CHILD|BS_AUTORADIOBUTTON +HelpID=2175 +PropTabs=HyperLabel,GenRadioButton:General,RadioButtonStyles:Styles,ExStyles +InitMethod=RadioButton(PARENT,ID,POSITION,SIZE) +InheritClassName=Inherit from Class,InheritClassName(CLASS:RadioButton) + +[CONTROL:TEXTCONTROL:FIXEDTEXT] +Assign01=Text Color,TextColor(COLOR) +WindowStyle20=No Prefix,SS_NOPREFIX(BOOL) +WindowStyle21=Alignment,SS_LEFT:SS_CENTER:SS_RIGHT:SS_SIMPLE:SS_LEFTNOWORDWRAP(FIXEDALIGN) +WindowStyle22=Center vertically,SS_CENTERIMAGE(BOOL) +ContTitle=Fixed Text +InitMethod=FixedText(PARENT,ID,POSITION,SIZE) +InheritClassName=Inherit from Class,InheritClassName(CLASS:FixedText) +ClassName=Static +text=Fixed Text +Name=FixedText +Size=80,20 +Style=WS_CHILD +HelpID=2210 +PropTabs=HyperLabel,GenFixedText:General,FixedTextStyles:Styles,ExStyles + +[CONTROL:TEXTCONTROL:GROUPBOX] +assign01=Use for Tooltip,UseHLforToolTip(BOOL) +Assign02=Text Color,TextColor(COLOR) +WindowStyle01=Flat,BS_FLAT(BOOL) +ContTitle=Group Box +ClassName=Button +Text=GroupBox +Name=GroupBox +Size=100,80 +Style=WS_CHILD|BS_GROUPBOX|WS_GROUP +HelpID=2200 +PropTabs=HyperLabel,GenGroupbox:General,GroupBoxStyles:Styles,ExStyles +InitMethod=GroupBox(PARENT,ID,POSITION,SIZE) +InheritClassName=Inherit from Class,InheritClassName(CLASS:GroupBox:-RadioButtonGroup) + +[CONTROL:TEXTCONTROL:RADIOBUTTONGROUP] +Assign01=Use for Tooltip,UseHLforToolTip(BOOL) +Assign02=Field Spec,FieldSpec(CLASS:FIELDSPEC) +Assign03=Text Color,TextColor(COLOR) +ContTitle=Radio Button Group +ClassName=Button +Text=RadioButtonGrp +Name=RadioButtonGroup +Size=100,80 +Style=WS_CHILD|BS_GROUPBOX|WS_GROUP +PropTabs=HyperLabel,GenRadioButtonGroup:General,GroupBoxStyles:Styles,ExStyles +HelpID=2180 +InitMethod=RadioButtonGroup(PARENT,ID,POSITION,SIZE) +InheritClassName=Inherit from Class,InheritClassName(CLASS:RadioButtonGroup) + +[CONTROL:COMMONCONTROL] +Assign01=Use for Tooltip,UseHLforToolTip(BOOL) +Method01=Font,Font(FONT) + +[CONTROL:COMMONCONTROL:PROGRESSBAR] +WindowStyle01=Smooth,PBS_SMOOTH(BOOL) +WindowStyle02=Vertical,PBS_VERTICAL(BOOL) +Assign01=Min Value,Range(numeric,) +Assign02=Max Value,Range(,numeric) +Assign03=Bar Color,BarColor(COLOR) +Assign04=Background,BackgroundColor(COLOR) +ContTitle=Progress Bar +ClassName=msctls_progress32 +Name=ProgressBar +Size=140,20 +Style=WS_CHILD +HelpID=2260 +PropTabs=HyperLabel,GenProgBar:General,ProgressStyles:Styles,ExStylesNoStaticEdge:ExStyles +InitMethod=ProgressBar(PARENT,ID,POSITION,SIZE) +InheritClassName=Inherit from Class,InheritClassName(CLASS:ProgressBar) + +[CONTROL:SCROLLBAR:SLIDER] +WindowStyle38=Auto Ticks,TBS_AUTOTICKS(BOOL) +WindowStyle39=No Ticks,TBS_NOTICKS(BOOL) +WindowStyle40=Enable Selection Range,TBS_ENABLESELRANGE(BOOL) +WindowStyle41=Fixed Length,TBS_FIXEDLENGTH(BOOL) +WindowStyle42=No Thumb,TBS_NOTHUMB(BOOL) +WindowStyle43=Tooltips,TBS_TOOLTIPS(BOOL) + + +[CONTROL:SCROLLBAR:SLIDER:HORIZONTALSLIDER] +ContTitle=Horizontal Slider +ClassName=msctls_trackbar32 +Name=HorizontalSlider +Size=100,30 +Style=WS_CHILD|TBS_AUTOTICKS +HelpID=2265 +PropTabs=HyperLabel,GenScrollbar:General,SliderStyles:Styles,ExStyles +InitMethod=HorizontalSlider(PARENT,ID,POSITION,SIZE) +InheritClassName=Inherit from Class,InheritClassName(CLASS:HorizontalSlider) + +[CONTROL:SCROLLBAR:SLIDER:VERTICALSLIDER] +ContTitle=Vertical Slider +ClassName=msctls_trackbar32 +Name=VerticalSlider +Size=30,100 +Style=WS_CHILD|TBS_AUTOTICKS|TBS_VERT +HelpID=2560 +PropTabs=HyperLabel,GenScrollbar:General,SliderStyles:Styles,ExStyles +InitMethod=VerticalSlider(PARENT,ID,POSITION,SIZE) +InheritClassName=Inherit from Class,InheritClassName(CLASS:VerticalSlider) + +[CONTROL:SCROLLBAR:SPINNER] +WindowStyle43=Wrap,UDS_WRAP(BOOL) +WindowStyle44=Alignment,WS_NULL:UDS_ALIGNLEFT:UDS_ALIGNRIGHT(UPDOWNALIGN) +WindowStyle45=Auto Buddy,UDS_AUTOBUDDY(BOOL) +WindowStyle46=Arrow Keys,UDS_ARROWKEYS(BOOL) +WindowStyle47=No Thousands,UDS_NOTHOUSANDS(BOOL) +WindowStyle48=Set Buddy Int,UDS_SETBUDDYINT(BOOL) + +[CONTROL:SCROLLBAR:SPINNER:VERTICALSPINNER] +ContTitle=Vertical Spinner +ClassName=msctls_updown32 +Name=VerticalSpinner +Size=20,30 +Style=WS_CHILD|UDS_AUTOBUDDY +HelpID=2555 +PropTabs=HyperLabel,GenScrollbar:General,SpinnerStyles:Styles,ExStyles +InitMethod=VerticalSpinner(PARENT,ID,POSITION,SIZE) +InheritClassName=Inherit from Class,InheritClassName(CLASS:VerticalSpinner) + +[CONTROL:SCROLLBAR:SPINNER:HORIZONTALSPINNER] +ContTitle=Horizontal Spinner +ClassName=msctls_updown32 +Name=HorizontalSpinner +Size=30,20 +Style=WS_CHILD|UDS_AUTOBUDDY|UDS_HORZ +HelpID=2555 +PropTabs=HyperLabel,GenScrollbar:General,SpinnerStyles:Styles,ExStyles +InitMethod=HorizontalSpinner(PARENT,ID,POSITION,SIZE) +InheritClassName=Inherit from Class,InheritClassName(CLASS:HorizontalSpinner) + +[CONTROL:COMMONCONTROL:TABCONTROL] +Assign01=AutoSize,AutoSize(BOOL) +WindowStyle01=Force Icon Left,TCS_FORCEICONLEFT(BOOL) +WindowStyle02=Force Label Left,TCS_FORCELABELLEFT(BOOL) +WindowStyle03=Buttons,TCS_BUTTONS(BOOL) +WindowStyle04=Multiline,TCS_MULTILINE(BOOL) +WindowStyle05=Focus On Button Down,TCS_FOCUSONBUTTONDOWN(BOOL) +WindowStyle06=Has ToolTips,TCS_TOOLTIPS(BOOL) +WindowStyle07=Focus Never,TCS_FOCUSNEVER(BOOL) +WindowStyle08=Tab Position,WS_NULL:TCS_VERTICAL:TCS_BOTTOM:TCS_VERTICAL|TCS_RIGHT(TABPOSITION) +WindowStyle09=Hot Track,TCS_HOTTRACK(BOOL) +ContTitle=Tab Control +ClassName=SysTabControl32 +Name=TabControl +Size=180,100 +Style=WS_CHILD +HelpID=2280 +PropTabs=HyperLabel,GenTabControl:General,TCStyles:Styles,ExStyles +InitMethod=TabControl(PARENT,ID,POSITION,SIZE) +InheritClassName=Inherit from Class,InheritClassName(CLASS:TabControl) + +[CONTROL:COMMONCONTROL:TREEVIEW] +WindowStyle55=Has Buttons,TVS_HASBUTTONS(BOOL) +WindowStyle56=Has Lines,TVS_HASLINES(BOOL) +WindowStyle57=Lines at Root,TVS_LINESATROOT(BOOL) +WindowStyle58=Disable Drag&Drop,TVS_DISABLEDRAGDROP(BOOL) +WindowStyle59=Show Selection Always,TVS_SHOWSELALWAYS(BOOL) +WindowStyle60=Edit Labels,TVS_EDITLABELS(BOOL) +ContTitle=Tree View +ClassName=SysTreeView32 +Name=TreeView +Size=150,100 +Style=WS_CHILD|WS_BORDER|TVS_HASBUTTONS|TVS_HASLINES|TVS_LINESATROOT|TVS_EDITLABELS +HelpID=2240 +PropTabs=HyperLabel,GenComControls:General,TVStyles:Styles,ExStyles +InitMethod=TreeView(PARENT,ID,POSITION,SIZE) +InheritClassName=Inherit from Class,InheritClassName(CLASS:TreeView) + +[CONTROL:COMMONCONTROL:LISTVIEW] +WindowStyle60=List View Type,LVS_ICON:LVS_SMALLICON:LVS_REPORT:LVS_LIST(LVTYPE) +WindowStyle61=Single Selection,LVS_SINGLESEL(BOOL) +WindowStyle62=Show Selection Always,LVS_SHOWSELALWAYS(BOOL) +WindowStyle63=Sort,WS_NULL:LVS_SORTASCENDING:LVS_SORTDESCENDING(LVSORT) +WindowStyle64=No Label Wrap,LVS_NOLABELWRAP(BOOL) +WindowStyle65=Auto Arrange,LVS_AUTOARRANGE(BOOL) +WindowStyle66=Edit Labels,LVS_EDITLABELS(BOOL) +WindowStyle67=No Scroll,LVS_NOSCROLL(BOOL) +WindowStyle68=Align Left,LVS_ALIGNLEFT(BOOL) +WindowStyle69=No Column Header,LVS_NOCOLUMNHEADER(BOOL) +WindowStyle70=No Sort Header,LVS_NOSORTHEADER(BOOL) +Assign01=Background,BackgroundColor(COLOR) +Assign02=Grid Lines,GridLines(BOOL) +Assign03=Header Drag&Drop,HeaderDragDrop(BOOL) +Assign04=Full Row Select,FullRowSelect(BOOL) +Assign05=Checkboxes,CheckBoxes(BOOL) +Assign06=Track Selection,TrackSelection(BOOL) +ContTitle=List View +ClassName=SysListView32 +Name=ListView +Size=150,120 +Style=WS_CHILD|WS_BORDER|LVS_REPORT|LVS_EDITLABELS +HelpID=2235 +PropTabs=HyperLabel,GenListView:General,LVStyles:Styles,LVExStyles:ExStyles +InitMethod=ListView(PARENT,ID,POSITION,SIZE) +InheritClassName=Inherit from Class,InheritClassName(CLASS:ListView) + +[CONTROL:COMMONCONTROL:LISTVIEW:DATALISTVIEW] +Assign01=Server,Server(CLASS:DBSERVER:+SQLTable) +ContTitle=Data List View +ClassName=SysListView32 +Name=DataListView +Size=150,120 +Style=WS_CHILD|WS_BORDER|LVS_REPORT|WS_HSCROLL|LVS_SINGLESEL|LVS_SHOWSELALWAYS|LVS_OWNERDATA +HelpID=2920 +PropTabs=HyperLabel,GenListView:General,DLVStyles:Styles,LVExStyles:ExStyles +InitMethod=DataListView(PARENT,ID,POSITION,SIZE) +InheritClassName=Inherit from Class,InheritClassName(CLASS:DataListView) + +[CONTROL:TEXTCONTROL:EDIT:MULTILINEEDIT:RICHEDIT] +WindowStyle74=Save Selection,ES_SAVESEL(BOOL) +WindowStyle75=Sunken,ES_SUNKEN(BOOL) +WindowStyle76=Disable No Scroll,ES_DISABLENOSCROLL(BOOL) +WindowStyle77=Align to Parent,ES_SELECTIONBAR(BOOL) +Assign01=Background,BackgroundColor(COLOR) +ContTitle=Rich Edit Control +ClassName=RICHEDIT20A +Name=RichEdit +Size=100,100 +Style=WS_CHILD|WS_BORDER|ES_MULTILINE|ES_WANTRETURN|ES_AUTOVSCROLL +HelpID=2245 +PropTabs=HyperLabel,GenRTFEdit:General,RichEditStyles:Styles,ExStylesNoClientEdge:ExStyles +InitMethod=RichEdit(PARENT,ID,POSITION,SIZE) +InheritClassName=Inherit from Class,InheritClassName(CLASS:RichEdit) + +[CONTROL:TEXTCONTROL:HOTKEYEDIT] +ContTitle=HotKey Edit +ClassName=msctls_hotkey32 +Name=HotKeyEdit +Size=90,20 +Style=WS_CHILD +HelpID=2255 +PropTabs=HyperLabel,GenComControls:General,Styles,ExStylesNoClientEdge:ExStyles +InitMethod=HotKeyEdit(PARENT,ID,POSITION,SIZE) +InheritClassName=Inherit from Class,InheritClassName(CLASS:HotKeyEdit) + +[CONTROL:COMMONCONTROL:ANIMATIONCONTROL] +WindowStyle71=Center,ACS_CENTER(BOOL) +WindowStyle72=Transparent Background,ACS_TRANSPARENT(BOOL) +WindowStyle73=Auto Play,ACS_AUTOPLAY(BOOL) +Method01=Resource,OpenResource(numeric) +Assign01=AVI File,FileSpec(STRING) +ContTitle=Animation Control +ClassName=SysAnimate32 +Name=Animation +Size=60,60 +Style=WS_CHILD|WS_BORDER +HelpID=2250 +PropTabs=HyperLabel,GenAniControls:General,AnimStyles:Styles,ExStyles +InitMethod=AnimationControl(PARENT,ID,POSITION,SIZE) +InheritClassName=Inherit from Class,InheritClassName(CLASS:AnimationControl) + +[CONTROL:TEXTCONTROL:DATETIMEPICKER] +Assign01=Use for Tooltip,UseHLforToolTip(BOOL) +Assign02=Format,Format(STRING) +WindowStyle01=Type,WS_NULL:DTS_LONGDATEFORMAT:DTS_TIMEFORMAT(DTPTYPE) +WindowStyle02=App Can Parse,DTS_APPCANPARSE(BOOL) +WindowStyle03=Right Align MC,DTS_RIGHTALIGN(BOOL) +WindowStyle04=Show None,DTS_SHOWNONE(BOOL) +WindowStyle05=Up-Down,DTS_UPDOWN(BOOL) +ContTitle=Date Time Picker Control +ClassName=SysDateTimePick32 +Name=DateTimePicker +Size=85,22 +Style=WS_CHILD|DTS_TIMEFORMAT|WS_TABSTOP +HelpID=2925 +PropTabs=HyperLabel,GenDTPick:General,DTPStyles:Styles,ExStylesNoClientEdge:ExStyles +InitMethod=DateTimePicker(PARENT,ID,POSITION,SIZE) +InheritClassName=Inherit from Class,InheritClassName(CLASS:DateTimePicker) + +[CONTROL:TEXTCONTROL:MONTHCALENDAR] +Assign01=Use for Tooltip,UseHLforToolTip(BOOL) +WindowStyle01=Day State,MCS_DAYSTATE(BOOL) +WindowStyle02=Multi Select,MCS_MULTISELECT(BOOL) +WindowStyle03=No Today,MCS_NOTODAY(BOOL) +WindowStyle04=No Today Circle,MCS_NOTODAYCIRCLE(BOOL) +WindowStyle05=Week Numbers,MCS_WEEKNUMBERS(BOOL) +ContTitle=Month Calendar Control +ClassName=SysMonthCal32 +Name=MonthCalendar +Size=180,160 +Style=WS_CHILD|WS_TABSTOP +ExStyle=WS_EX_CLIENTEDGE +HelpID=2935 +PropTabs=HyperLabel,GenMonthCal:General,MCStyles:Styles,ExStyles +InitMethod=MonthCalendar(PARENT,ID,POSITION,SIZE) +InheritClassName=Inherit from Class,InheritClassName(CLASS:MonthCalendar) + +[CONTROL:TEXTCONTROL:BASELISTBOX:COMBOBOX:COMBOBOXEX] +ContTitle=ComboBoxEx +ClassName=ComboBoxEx32 +Name=ComboBoxEx +Size=80,120 +Style=WS_TABSTOP|WS_CHILD|CBS_DROPDOWN +HelpID=2910 +PropTabs=HyperLabel,GenListbox:General,ComboBoxStyles:Styles,ExStyles +InitMethod=comboboxex(PARENT,ID,POSITION,SIZE) +InheritClassName=Inherit from Class,InheritClassName(CLASS:ComboBoxEx) + +[CONTROL:TEXTCONTROL:IPADDRESS] +Assign01=Use for Tooltip,UseHLforToolTip(BOOL) +ContTitle=IP Address +ClassName=SysIPAddress32 +Text=IPAddress +Name=IPAddress +Size=100,20 +Style=WS_CHILD|WS_TABSTOP +HelpID=2930 +PropTabs=HyperLabel,GenIPAddress:General,Styles,ExStyles +InitMethod=IPAddress(PARENT,ID,POSITION,SIZE) +InheritClassName=Inherit from Class,InheritClassName(CLASS:IPAddress) + +[AssignMap] +BackgroundColor=Color +BarColor=Color +Background=Brush +TextLimit= +TextColor=Color +Caption= +AllowInPlace= +ActivateOnDblClk= +AllowResize= +AutoSizeOnCreate= +ReadOnly= +ClipperKeys= +AutoSize= +OverWrite= +Picture= +PreventAutoLayout= +TooltipText= +UseHLforTooltip= +AllowDocView= +DeferUse= +GridLines= +HeaderDragDrop= +FullRowSelect= +CheckBoxes= +TrackSelection= +QuitOnClose= +Automated= +Format= +AllowServerClose= +ScrollMode= +FocusSelect= +AutoFocusChange= +OwnerAlignment= +FileName= + +[StdProperties] +;**** Types +FONT=FONTSYSTEM8,FONTMODERN8,FONTMODERN10,FONTMODERN12,FONTSWISS8,FONTSWISS10,FONTSWISS12,FONTSWISS14,FONTSWISS18,FONTSWISS24,FONTROMAN8,FONTROMAN10,FONTROMAN12,FONTROMAN14,FONTROMAN18,FONTROMAN24 +BRUSH=BRUSHWHITE,BRUSHBLACK,BRUSHDARK,BRUSHMEDIUM,BRUSHLIGHT,BRUSHHOLLOW,BRUSHCLEAR +COLOR=COLORBLACK,COLORBLUE,COLORCYAN,COLORGREEN,COLORMAGENTA,COLORRED,COLORYELLOW,COLORWHITE +LOGIC=Yes +BOOL=True,False +OLOGIC=#ON,#OFF +VIEWS=#FormView,#BrowseView +BUTTONIMAGESTYLE=No Image,Icon,Bitmap +BUTTONHALIGN=Auto,Left,Right,Center +BUTTONVALIGN=Auto,Top,Bottom,Center +BUTTONIMAGETYPE=No Image,Icon,Bitmap +CBAUTOSTYLE=No AutoSelect,AutoSelect,3 State,AutoSelect 3 State +EDITCONVERT=No Conversion,Uppercase,Lowercase +EDITALIGN=Left,Center,Right +FIXEDALIGN=Left,Center,Right,Simple,Left No Wrap, +UPDOWNALIGN=No Alignment,Left,Right +OWNERDRAW=No Ownerdraw,Fixed,Variable +CBTYPE=Simple,Drop Down,Drop Down List +LVTYPE=Icons,Small Icons,Report,List +LVSORT=No Sort,Ascending,Descending +CAPTIONBAR=No Caption Bar,Caption Bar Only,System Menu,Minimize Box,Maximize Box,Both Boxes +DIALOGPOS=Auto,Center,Center Mouse +DIALOGTYPE=Popup Dialog,Tab Page +TABKEY=Form Only,Include Parent +OVERWRITE=OVERWRITE_NEVER,OVERWRITE_ONKEY,OVERWRITE_ALWAYS +SCRMODE=SCRMODE_FULL,SCRMODE_PART,SCRMODE_NO +FSELECT=FSEL_ALL,FSEL_TRIM,FSEL_HOME,FSEL_END,FSEL_TRIMEND +DTPTYPE=Short Date,Long Date,Time +TABPOSITION=Top,Left,Bottom,Right +EXALIGNMENT=Left,Right +OWNERALIGNMENT=OA_LEFT,OA_TOP,OA_RIGHT,OA_BOTTOM,OA_CENTER,OA_LEFT_AUTOSIZE,OA_TOP_AUTOSIZE,OA_RIGHT_AUTOSIZE,OA_BOTTOM_AUTOSIZE,OA_FULL_SIZE,OA_X,OA_X_Y,OA_X_PY, OA_X_PY_PHEIGHT,OA_X_HEIGHT,OA_X_PHEIGHT,OA_PX,OA_PX_Y,OA_PX_Y_PWIDTH,OA_PX_PY_PHEIGHT,OA_PX_PY_PWIDTH_PHEIGHT,OA_PX_PWIDTH,OA_PX_PWIDTH_HEIGHT,OA_PX_PWIDTH_PHEIGHT,OA_PX_HEIGHT,OA_PX_PHEIGHT,OA_Y,OA_Y_WIDTH,OA_Y_PWIDTH,OA_PY,OA_PY_WIDTH_PHEIGHT,OA_PY_PWIDTH_PHEIGHT,OA_PY_PHEIGHT,OA_WIDTH,OA_WIDTH_HEIGHT,OA_WIDTH_PHEIGHT,OA_PWIDTH,OA_PWIDTH_HEIGHT,OA_PWIDTH_PHEIGHT,OA_HEIGHT,OA_PHEIGHT + +;**** Association between Tabs and methods/assigns/styles +HyperLabel=(Hyperlabel Info)Caption,Name,Description,Help Context,Use for Tooltip +DataWindow=(DataWindow Properties)Inherit from Class,_DBInhFrom,_DCInhFrom,Class Declaration,No Access/Assign,Export Controls,Pixel Positions,Automated,Large Icon,Small Icon,Accelerator Table,Background Color,Menu,Context Menu,Help File Name,View As,Clipper Keys,Tab Key,Data Server,Prevent Auto Layout,Allow Server Close,Defer USE,Quit On Close,Font,StatusBar,Notify,PreValidate,PreInit Actions,PostInit Actions,_Width,_Height,Owner Alignment +DataDialog=(DataDialog Properties)Inherit from Class,_DBInhFrom,_DCInhFrom,Class Declaration,No Access/Assign,Export Controls,Pixel Positions,Automated,Large Icon,Small Icon,Accelerator Table,Background Color,Context Menu,Help File Name,View As,Clipper Keys,Data Server,Prevent Auto Layout,Allow Server Close,Defer USE,Font,Notify,PreValidate,PreInit Actions,PostInit Actions,_Width,_Height +ShellWindow=(ShellWindow Properties)Inherit from Class,Class Declaration,Large Icon,Small Icon,Mouse Pointer,Menu,Context Menu,Help File Name,PreInit Actions,PostInit Actions,_Width,_Height +DlgWindow=(DialogWindow Properties)Inherit from Class,Class Declaration,Type,Export Controls,Pixel Positions,Automated,Large Icon,Small Icon,Font,Modeless,Background Color,Mouse Pointer,Clipper Keys,Tab Key,Help File Name,PreInit Actions,PostInit Actions,_Width,_Height +Mouse Events=(Window Mouse Events)MouseButtonDoubleClick,MouseButtonDown,MouseButtonUp,MouseDrag,MouseMove +Window Events=(Windows Events)Activate,Close,Deactivate,Draw,Expose,FocusChange,HelpRequest,Move,QueryClose,Resize +Menu Events=(Window Menu Events)MenuCommand,MenuInit,MenuSelect +Control Events=(Window Control Events)ButtonClick,ButtonDoubleClick,EditChange,EditFocusChange,EditScroll,Keydown,Keyup,ListBoxClick,ListBoxSelect,HorizontalScroll,VerticalScroll,HorizontalSlide,VerticalSlide,HorizontalSpin,VerticalSpin +CommonControl Events=(CommonControl Events)AnimationStart,AnimationStop,RichEditDropFiles,RichEditProtected,RichEditSelChange,RichEditUndoLost,TabSelect,TabSelChanging,TabKeyDown,MonthCalSelChanged,DateTimeSelChanged +TreeView Events=(TreeView Events)TreeViewMouseDown,TreeViewMouseDoubleClick,TreeViewItemDrag,TreeViewItemEdit,TreeViewItemDelete,TreeViewItemExpanded,TreeViewItemExpanding,TreeViewKeyDown,TreeViewSelChanged,TreeViewSelChanging +ListView Events=(ListView Events)ListViewItemDrag,ListViewItemEdit,ListViewColumnClick,ListViewItemDelete,ListViewKeyDown,ListViewMouseDown,ListViewMouseDoubleClick +GenMultiSel=(General Multiple Selection Properties)Context Menu,Font,_Visible,_GenCode,Owner Alignment,_Width,_Height,_Left,_Top +GenCustCtrl=(General CustomControl Properties)Inherit from Class,Context Menu,Tooltip,_Visible,_GenCode,Owner Alignment,_Width,_Height,_Left,_Top +GenMMCont=(General MultiMedia Container Properties)Field Spec,Inherit from Class,Context Menu,Tooltip,_Visible,_GenCode,Owner Alignment,File Name,_Width,_Height,_Left,_Top +GenSubData=(General SubDataWindow Properties)Name,Relation String,Order,_GenCode,Owner Alignment,_Width,_Height,_Left,_Top +GenPushButton=(General PushButton Properties)Click Event,Inherit from Class,Context Menu,Tooltip,Font,Input Method Editor,_Visible,_GenCode,Image,Owner Alignment,_Width,_Height,_Left,_Top +GenRadioButton=(General RadioButton Properties)Group Value,Text Color,Background Color,Inherit from Class,Context Menu,Tooltip,Font,Input Method Editor,_Visible,_GenCode,Image,Owner Alignment,_Width,_Height,_Left,_Top +GenCheckbox=(General CheckBox Properties)Field Spec,Text Color,Background Color,Inherit from Class,Context Menu,Tooltip,Font,Input Method Editor,_Visible,_GenCode,Image,Owner Alignment,_Width,_Height,_Left,_Top +GenScrollbar=(General ScrollBar Properties)Field Spec,Min Value,Max Value,Background Color,Inherit from Class,Context Menu,Tooltip,_Visible,_GenCode,Owner Alignment,_Width,_Height,_Left,_Top +GenEdit=(General Edit Properties)Field Spec,Text Limit,Text Color,Background Color,Inherit from Class,Context Menu,Tooltip,Font,Input Method Editor,_Visible,_GenCode,Owner Alignment,_Width,_Height,_Left,_Top +GenSingleEdit=(General SinglelineEdit Properties)Block,Block Owner,Field Spec,Text Limit,Text Color,Background Color,Inherit from Class,Context Menu,Tooltip,Font,Picture,Overwrite,Scroll Mode,Focus Select,Auto Focus Change,Input Method Editor,_Visible,_GenCode,Owner Alignment,_Width,_Height,_Left,_Top +GenListbox=(General ListBox Properties)Fill Using,Field Spec,Text Color,Background Color,Inherit from Class,Context Menu,Tooltip,Font,Input Method Editor,_GenCode,_Visible,Owner Alignment,_Width,_Height,_Left,_Top +GenFixedText=(General FixedText Properties)Text Color,Background Color,Inherit from Class,Context Menu,Font,Input Method Editor,_Visible,_GenCode,Owner Alignment,_Width,_Height,_Left,_Top +GenFixedImage=(General FixedImage Properties)Background Color,Inherit from Class,Context Menu,Icon,Bitmap,_Visible,_GenCode,Owner Alignment,_Width,_Height,_Left,_Top +GenGroupbox=(General GroupBox Properties)Text Color,Background Color,Inherit from Class,Context Menu,Tooltip,Font,Input Method Editor,_Visible,_GenCode,Owner Alignment,_Width,_Height,_Left,_Top +GenRadioButtonGroup=(General RadioButtonGroup Properties)Field Spec,Text Color,Background Color,Inherit from Class,Context Menu,Tooltip,Font,Input Method Editor,_Visible,_GenCode,Owner Alignment,_Width,_Height,_Left,_Top +GenComControls=(General CommonControl Properties)Background Color,Inherit from Class,Context Menu,Tooltip,Font,_Visible,_GenCode,Owner Alignment,_Width,_Height,_Left,_Top +GenAniControls=(General AnimationControl Properties)Background Color,Inherit from Class,Context Menu,Tooltip,Resource,AVI File,_Visible,_GenCode,Owner Alignment,_Width,_Height,_Left,_Top +GenTabControl=(General TabControl Properties)Inherit from Class,Context Menu,Font,AutoSize,_Visible,_GenCode,_IsDataPage,_PageCaption,_PageName,Owner Alignment,_Width,_Height,_Left,_Top +GenListView=(General CommonControl Properties)Background,Inherit from Class,Server,Context Menu,Tooltip,Font,_Visible,_GenCode,Owner Alignment,_Width,_Height,_Left,_Top +GenProgBar=(General ProgressBar Properties)Inherit From Class,Context Menu,Tooltip,Background,Bar Color,Min Value,Max Value,_Visible,_GenCode,Owner Alignment,_Width,_Height,_Left,_Top +GenRTFEdit=(General Edit Properties)Field Spec,Text Limit,Text Color,Background,Inherit from Class,Context Menu,Tooltip,Font,Input Method Editor,_Visible,_GenCode,Owner Alignment,_Width,_Height,_Left,_Top +GenDTPick=(General Date Time Picker Properties)Field Spec,Text Color,Background Color,Inherit from Class,Context Menu,Tooltip,Font,Format,_Visible,_GenCode,Owner Alignment,_Width,_Height,_Left,_Top +GenMonthCal=(General Month Calendar Properties)Field Spec,Text Color,Background,Inherit from Class,Context Menu,Tooltip,Font,_Visible,_GenCode,Owner Alignment,_Width,_Height,_Left,_Top +GenOleObject=(General OLE Object Properties)Field Spec,Inherit from Class,Context Menu,Tooltip,_Visible,_GenCode,AllowInPlace,ActivateOnDblClk,AllowResize,AutoSizeOnCreate,ReadOnly,AllowDocView,Owner Alignment,_Width,_Height,_Left,_Top +GenOleControl=(General OLE Control Properties)Inherit from Class,Context Menu,Tooltip,_Visible,_GenCode,AllowInPlace,ActivateOnDblClk,AllowResize,AutoSizeOnCreate,ReadOnly,Owner Alignment,_Width,_Height,_Left,_Top +GenIPAddress=(General IPAddress Properties)Field Spec,Inherit from Class,Context Menu,ToolTip,Font,_Visible,_GenCode,Owner Alignment,_Width,_Height,_Left,_Top +OCXProperties=(OLE Control Properties)_OCXPropertyPages,_OCXPropertyList +OCXEvents=(OLE Control Events)_OCXEventList +Styles=(Window Styles)Disabled,Tab Stop,Group +ExStyles=(Extended Window Styles)Clip Siblings,Right-To-Left Reading,ExAlignment,No Parent Notify,Accept Files,Transparent,Client Edge,Static Edge,Modal Frame +ExStylesNoStaticEdge=(Extended Window Styles)Clip Siblings,Right-To-Left Reading,ExAlignment,No Parent Notify,Accept Files,Transparent,Client Edge,Modal Frame +ExStylesNoClientEdge=(Extended Window Styles)Clip Siblings,Right-To-Left Reading,ExAlignment,No Parent Notify,Accept Files,Transparent,Static Edge,Modal Frame +SubDataStyles=(Sub Data Window Styles)Tab Stop,Border +OLEObjStyles=(OLE Object Window Styles)Disabled,Tab Stop,Group,Border +CustomCtlStyles=(Custom Control Window Styles)Disabled,Tab Stop,Group,Border +MMContStyles=(MulitMedia Container Window Styles)Disabled,Tab Stop,Group,Border +FixedTextStyles=(FixedText Window Styles)Disabled,Tab Stop,Group,Border,No Prefix,Alignment,Center vertically +FixedImageStyles=(FixedImage Window Styles)Disabled,Tab Stop,Group,Border,Real Size Image,Center Image,Right adjust +PushButtonStyles=(PushButton Window Styles)Disabled,Tab Stop,Group,Default Button,Multiline,Flat,Horizontal Alignment,Vertical Alignment,Image Type +CheckBoxStyles=(CheckBox Window Styles)Disabled,Tab Stop,Group,Text Left,Selection,Multiline,Flat,Horizontal Alignment,Vertical Alignment,Push Like,Image Type +RadioButtonStyles=(RadioButton Window Styles)Disabled,Tab Stop,Group,Text Left,Flat,AutoSelect,Multiline,Horizontal Alignment,Vertical Alignment,Push Like,Image Type +GroupBoxStyles=(GroupBox Window Styles)Disabled,Tab Stop,Group,Flat +ProgressStyles=(ProgressBar Styles)Disabled,Tab Stop,Group,Smooth,Vertical +SingleLineStyles=(SingleLine Edit Window Styles)Disabled,Tab Stop,Group,Border,Password,Retain Selection,OEM Conversion,Auto HScroll,Read Only,Conversion,Numbers Only,Alignment +MultiLineStyles=(MultiLine Edit Window Styles)Disabled,Tab Stop,Group,Border,Password,Retain Selection,OEM Conversion,Vertical Scrollbar,Horizontal Scrollbar,Auto VScroll,Auto HScroll,Return Key,Read Only,Conversion,Alignment +ListBoxStyles=(ListBox Window Styles)Disabled,Tab Stop,Group,Border,Sort,Notify Parent,Vertical Scrollbar,Horizontal Scrollbar,Use Tab Stops,No Redraw,Multi Column,No Integral Height,Want Keyboard Input,Multiple Selection,Extended Selection,Disable if no Scroll,Owner Draw +ComboBoxStyles=(ComboBox Window Styles)Disabled,Tab Stop,Group,ComboBox Type,Sort,Vertical Scrollbar,Auto HScroll,No Integral Height,OEM Convert,Disable if no Scroll,OwnerDraw +ScrollBarStyles=(ScrollBar Window Styles)Disabled,Tab Stop,Group,AutoSize,Size Grip +SliderStyles=(Slider Window Styles)Disabled,Tab Stop,Group,Auto Ticks,No Ticks,Enable Selection Range,Fixed Length,No Thumb,Tooltips +SpinnerStyles=(Spinner Window Styles)Disabled,Tab Stop,Group,Wrap,Alignment,Auto Buddy,Set Buddy Int,Arrow Keys,No Thousands +TCStyles=(TabControl Window Styles)Disabled,Tab Stop,Group,Force Icon Left,Force Label Left,Buttons,Multiline,Focus On Button Down,Has ToolTips,Focus Never,Tab Position,Hot Track +TVStyles=(TreeView Window Styles)Disabled,Tab Stop,Group,Border,Has Buttons,Has Lines,Lines at Root,Disable Drag&Drop,Show Selection Always,Edit Labels +LVStyles=(ListView Window Styles)Disabled,Tab Stop,Group,Border,List View Type,Single Selection,Show Selection Always,Sort,No Label Wrap,Auto Arrange,Edit Labels,No Scroll,Align Left,No Column Header,No Sort Header +DLVStyles=(ListView Window Styles)Disabled,Tab Stop,Group,Border,Label Wrap,Edit Labels,No Scroll,Align Left,No Column Header,No Sort Header +AnimStyles=(AnimationControl Window Styles)Disabled,Tab Stop,Border,Group,Center,Transparent Background,Auto Play +RichEditStyles=(RichEdit Control Window Styles)Disabled,Tab Stop,Group,Border,Retain Selection,Vertical Scrollbar,Horizontal Scrollbar,Auto VScroll,Auto HScroll,Return Key,Read Only,Alignment,Save Selection,Sunken,Disable No Scroll,Align to Parent +DlgWndStyles=(DialogWindow Styles)Disabled,Caption Bar,Clip Siblings,Clip Children,Absolute Align,System Modal,No Idle Message,Resizable,Position +MCStyles=(Month Calendar Styles)Disabled,Tab Stop,Group,Day State,Multi Select,No Today,No Today Circle,Week Numbers +DTPStyles=(Date Time Picker Styles)Disabled,Tab Stop,Group,Type,Right Align MC,Show None,Up-Down +LVExStyles=(Extended ListView Styles)Clip Siblings,Right-To-Left Reading,No Parent Notify,Accept Files,Transparent,Client Edge,Static Edge,Modal Frame,Grid Lines,Full Row Select,Header Drag&Drop,Checkboxes,Track Selection + +[SupplementalFiles] +File00= +File01= +File02= +File03= +File04= + diff --git a/src/VisualStudio/ProjectSystemCPS.TestBed/VO/Properties/cavowed.tpl b/src/VisualStudio/ProjectSystemCPS.TestBed/VO/Properties/cavowed.tpl new file mode 100644 index 0000000000..aa43daa7fd --- /dev/null +++ b/src/VisualStudio/ProjectSystemCPS.TestBed/VO/Properties/cavowed.tpl @@ -0,0 +1,464 @@ +; +; syntax of templates: +; +; [
] +; +; +; where
is the name of the section. This name can be used +; in CODE:
syntax of description file +; +; consists of code. Use \t for a tab character. Other +; substitution keywords are: +; +; %CONTROL:% for a control property +; %FORM:% for a form property +; +; ie: %CONTROL:NAME% for control's name +; %CONTROL:CLASSNAME% for control's class name +; +; %INITPARAMS% for the parameters of the INIT method +; +; Note: semicolon (';') in first column designates a comment line + + +[ClassDeclaration] +class %FORM:NAME% inherit %FORM:CLASSNAME% + +[Activate] +method Activate(oEvent) class %FORM:NAME% +\tsuper:Activate(oEvent) +\t//Put your changes here +\treturn NIL + +[ButtonClick] +method ButtonClick(oControlEvent) class %FORM:NAME% +\tlocal oControl as Control +\toControl := IIf(oControlEvent == NULL_OBJECT, NULL_OBJECT, oControlEvent:Control) +\tsuper:ButtonClick(oControlEvent) +\t//Put your changes here +\treturn NIL + +[ButtonDoubleClick] +method ButtonDoubleClick(oControlEvent) class %FORM:NAME% +\tlocal oControl as Control +\toControl := IIf(oControlEvent == NULL_OBJECT, NULL_OBJECT, oControlEvent:Control) +\tsuper:ButtonDoubleClick(oControlEvent) +\t//Put your changes here +\treturn NIL + +[Close] +method Close(oEvent) class %FORM:NAME% +\tsuper:Close(oEvent) +\t//Put your changes here +\treturn NIL + +[Deactivate] +method Deactivate(oEvent) class %FORM:NAME% +\tsuper:Deactivate(oEvent) +\t//Put your changes here +\treturn NIL + +[Draw] +method Draw(oDrawObject) class %FORM:NAME% +\tsuper:Draw(oDrawObject) +\t//Put your changes here +return NIL + +[EditChange] +method EditChange(oControlEvent) class %FORM:NAME% +\tlocal oControl as Control +\toControl := IIf(oControlEvent == NULL_OBJECT, NULL_OBJECT, oControlEvent:Control) +\tsuper:EditChange(oControlEvent) +\t//Put your changes here +\treturn NIL\r\n + +[EditFocusChange] +method EditFocusChange(oEditFocusChangeEvent) class %FORM:NAME% +\tlocal oControl as Control +\tlocal lGotFocus as logic +\toControl := IIf(oEditFocusChangeEvent == NULL_OBJECT, NULL_OBJECT, oEditFocusChangeEvent:Control) +\tlGotFocus := IIf(oEditFocusChangeEvent == NULL_OBJECT, FALSE, oEditFocusChangeEvent:GotFocus) +\tsuper:EditFocusChange(oEditFocusChangeEvent) +\t//Put your changes here +\treturn NIL + +[EditScroll] +method EditScroll(oControlEvent) class %FORM:NAME% +\tlocal oControl as Control +\toControl := IIf(oControlEvent == NULL_OBJECT, NULL_OBJECT, oControlEvent:Control) +\tsuper:EditScroll(oControlEvent) +\t//Put your changes here +\treturn NIL + +[Expose] +method Expose(oExposeEvent) class %FORM:NAME% +\tlocal oBoundingBox as BoundingBox +\toBoundingBox := IIf(oExposeEvent == NULL_OBJECT, NULL_OBJECT, oExposeEvent:ExposedArea) +\tsuper:Expose(oExposeEvent) +\t//Put your changes here +\treturn NIL\r\n + +[FocusChange] +method FocusChange(oFocusChangeEvent) class %FORM:NAME% +\tlocal lGotFocus as logic +\tlGotFocus := IIf(oFocusChangeEvent == NULL_OBJECT, FALSE, oFocusChangeEvent:GotFocus) +\tsuper:FocusChange(oFocusChangeEvent) +\t//Put your changes here +\treturn NIL + +[PreInit] +method PreInit(%INITPARAMS%) class %FORM:NAME% +\t//Put your PreInit additions here +\treturn NIL + +[PostInit] +method PostInit(%INITPARAMS%) class %FORM:NAME% +\t//Put your PostInit additions here +\treturn NIL + +[PreInitCall] +; this section is inserted at the end of the INIT method of the form +; use %INITPARAMS% to insert the parameters of INIT +self:PreInit(%INITPARAMS%) + +[PostInitCall] +; this section is inserted at the end of the INIT method the form +; use %INITPARAMS% to insert the parameters of INIT +self:PostInit(%INITPARAMS%) + +[HelpRequest] +method HelpRequest(oHelpRequestEvent) class %FORM:NAME% +\tsuper:HelpRequest(oHelpRequestEvent) +\t//Put your changes here +\treturn NIL + +[HorizontalScroll] +method HorizontalScroll(oScrollEvent) class %FORM:NAME% +\tsuper:HorizontalScroll(oScrollEvent) +\t//Put your changes here +\treturn NIL + +[Keydown] +method KeyDown(oKeyEvent) class %FORM:NAME% +\tlocal nKeyCode as int +\tnKeyCode := IIf(oKeyEvent = NULL_OBJECT, 0, IIf(IsNil(oKeyEvent:ASCIIChar), oKeyEvent:KeyCode, oKeyEvent:ASCIIChar)) +\tsuper:KeyDown(oKeyEvent) +\t//Put your changes here +\treturn NIL + +[Keyup] +method KeyUp(oKeyEvent) class %FORM:NAME% +\tlocal nKeyCode as int +\tnKeyCode := IIf(oKeyEvent = NULL_OBJECT, 0, IIf(IsNil(oKeyEvent:ASCIIChar), oKeyEvent:KeyCode, oKeyEvent:ASCIIChar)) +\tsuper:KeyUp(oKeyEvent) +\t//Put your changes here +\treturn NIL + +[ListBoxClick] +method ListBoxClick(oControlEvent) class %FORM:NAME% +\tlocal oControl as Control +\toControl := IIf(oControlEvent == NULL_OBJECT, NULL_OBJECT, oControlEvent:Control) +\tsuper:ListBoxClick(oControlEvent) +\t//Put your changes here +\treturn NIL + +[ListBoxSelect] +method ListBoxSelect(oControlEvent) class %FORM:NAME% +\tlocal oControl as Control +\toControl := IIf(oControlEvent == NULL_OBJECT, NULL_OBJECT, oControlEvent:Control) +\tsuper:ListBoxSelect(oControlEvent) +\t//Put your changes here +\treturn NIL + +[MenuCommand] +method MenuCommand(oMenuCommandEvent) class %FORM:NAME% +\tsuper:MenuCommand(oMenuCommandEvent) +\t//Put your changes here +\treturn NIL + +[MenuInit] +method MenuInit(oMenuInitEvent) class %FORM:NAME% +\tsuper:MenuInit(oMenuInitEvent) +\t//Put your changes here +\treturn NIL + +[MenuSelect] +method MenuSelect(oMenuSelectEvent) class %FORM:NAME% +\tsuper:MenuSelect(oMenuSelectEvent) +\t//Put your changes here +\treturn NIL + +[MouseButtonDoubleClick] +method MouseButtonDoubleClick(oMouseEvent) class %FORM:NAME% +\tlocal nButtonID as int +\tnButtonID := IIf(oMouseEvent == NULL_OBJECT, 0, oMouseEvent:ButtonID) +\tsuper:MouseButtonDoubleClick(oMouseEvent) +\t//Put your changes here +\treturn NIL + +[MouseButtonDown] +method MouseButtonDown(oMouseEvent) class %FORM:NAME% +\tlocal nButtonID as int +\tnButtonID := IIf(oMouseEvent == NULL_OBJECT, 0, oMouseEvent:ButtonID) +\tsuper:MouseButtonDown(oMouseEvent) +\t//Put your changes here +\treturn NIL + +[MouseButtonUp] +method MouseButtonUp(oMouseEvent) class %FORM:NAME% +\tlocal nButtonID as int +\tnButtonID := IIf(oMouseEvent == NULL_OBJECT, 0, oMouseEvent:ButtonID) +\tsuper:MouseButtonUp(oMouseEvent) +\t//Put your changes here +\treturn NIL + +[MouseDrag] +method MouseDrag(oMouseEvent) class %FORM:NAME% +\tlocal nButtonID as int +\tnButtonID := IIf(oMouseEvent == NULL_OBJECT, 0, oMouseEvent:ButtonID) +\tsuper:MouseDrag(oMouseEvent) +\t//Put your changes here +\treturn NIL + +[MouseMove] +method MouseMove(oMouseEvent) class %FORM:NAME% +\tlocal nButtonID as int +\tnButtonID := IIf(oMouseEvent == NULL_OBJECT, 0, oMouseEvent:ButtonID) +\tsuper:MouseMove(oMouseEvent) +\t//Put your changes here +\treturn NIL + +[Move] +method Move(oMoveEvent) class %FORM:NAME% +\tsuper:Move(oMoveEvent) +\t//Put your changes here +\treturn NIL + +[Notify] +method Notify(kNotifyName, uDescription) class %FORM:NAME% +\tlocal uValue as usual +\tuValue := super:Notify(kNotifyName, uDescription) +\t//Put your changes here +\treturn uValue + +[PreValidate] +method PreValidate() class %FORM:NAME% +\tsuper:PreValidate() +\t//Put your changes here +\treturn NIL + +[QueryClose] +method QueryClose(oEvent) class %FORM:NAME% +\tlocal lAllowClose as logic +\tlAllowClose := super:QueryClose(oEvent) +\t//Put your changes here +\treturn lAllowClose + +[Resize] +method Resize(oResizeEvent) class %FORM:NAME% +\tsuper:Resize(oResizeEvent) +\t//Put your changes here +\treturn NIL + +[VerticalScroll] +method VerticalScroll(oScrollEvent) class %FORM:NAME% +\tsuper:VerticalScroll(oScrollEvent) +\t//Put your changes here +\treturn NIL + +[HorizontalSlide] +method HorizontalSlide(oSlideEvent) class %FORM:NAME% +\tsuper:HorizontalSlide(oSlideEvent) +\t//Put your changes here +\treturn NIL + +[VerticalSlide] +method VerticalSlide(oSlideEvent) class %FORM:NAME% +\tsuper:VerticalSlide(oSlideEvent) +\t//Put your changes here +\treturn NIL + +[HorizontalSpin] +method HorizontalSpin(oSpinEvent) class %FORM:NAME% +\tsuper:HorizontalSpin(oSpinEvent) +\t//Put your changes here +\treturn NIL + +[VerticalSpin] +method VerticalSpin(oSpinEvent) class %FORM:NAME% +\tsuper:VerticalSpin(oSpinEvent) +\t//Put your changes here +\treturn NIL + +[AnimationStart] +method AnimationStart(oControlNotifyEvent) class %FORM:NAME% +\tlocal oControl as Control +\toControl := IIf(oControlNotifyEvent == NULL_OBJECT, NULL_OBJECT, oControlNotifyEvent:Control) +\tsuper:AnimationStart(oControlNotifyEvent) +\t//Put your changes here +\treturn NIL + +[AnimationStop] +method AnimationStop(oControlNotifyEvent) class %FORM:NAME% +\tlocal oControl as Control +\toControl := IIf(oControlNotifyEvent == NULL_OBJECT, NULL_OBJECT, oControlNotifyEvent:Control) +\tsuper:AnimationStop(oControlNotifyEvent) +\t//Put your changes here +\treturn NIL + +[RichEditDropFiles] +method RichEditDropFiles(oRichEditDropEvent) class %FORM:NAME% +\tsuper:RichEditDropFiles(oRichEditDropEvent) +\t//Put your changes here +\treturn NIL + +[RichEditProtected] +method RichEditProtected(oRichEditProtectEvent) class %FORM:NAME% +\tsuper:RichEditProtected(oRichEditProtectEvent) +\t//Put your changes here +\treturn NIL + +[RichEditSelectionChange] +method RichEditSelectionChange(oRichEditSelectionEvent) class %FORM:NAME% +\tsuper:RichEditSelectionChange(oRichEditSelectionEvent) +\t//Put your changes here +\treturn NIL + +[RichEditUndoLost] +method RichEditUndoLost(oControlNotifyEvent) class %FORM:NAME% +\tsuper:RichEditUndoLost(oControlNotifyEvent) +\t//Put your changes here +\treturn NIL + +[ListViewItemDrag] +method ListViewItemDrag(oListViewDragEvent) class %FORM:NAME% +\tsuper:ListViewItemDrag(oListViewDragEvent) +\t//Put your changes here +\treturn NIL + +[ListViewItemEdit] +method ListViewItemEdit(oListViewEditEvent) class %FORM:NAME% +\tsuper:ListViewItemEdit(oListViewEditEvent) +\t//Put your changes here +\treturn NIL + +[ListViewColumnClick] +method ListViewColumnClick(oListViewColumnClickEvent) class %FORM:NAME% +\tsuper:ListViewColumnClick(oListViewColumnClickEvent) +\t//Put your changes here +\treturn NIL + +[ListViewItemDelete] +method ListViewItemDelete(oListViewDeleteEvent) class %FORM:NAME% +\tsuper:ListViewItemDelete(oListViewDeleteEvent) +\t//Put your changes here +\treturn NIL + +[ListViewKeyDown] +method ListViewKeyDown(oListViewKeyEvent) class %FORM:NAME% +\tsuper:ListViewKeyDown(oListViewKeyEvent) +\t//Put your changes here +\treturn NIL + +[TreeViewMouseButtonDown] +method TreeViewMouseButtonDown(oTreeViewMouseEvent) class %FORM:NAME% +\tsuper:TreeViewMouseButtonDown(oTreeViewMouseEvent) +\t//Put your changes here +\treturn NIL + +[ListViewMouseButtonDown] +method ListViewMouseButtonDown(oListViewMouseEvent) class %FORM:NAME% +\tsuper:ListViewMouseButtonDown(oListViewMouseEvent) +\t//Put your changes here +\treturn NIL + +[TreeViewMouseButtonDoubleClick] +method TreeViewMouseButtonDoubleClick(oTreeViewMouseEvent) class %FORM:NAME% +\tsuper:TreeViewMouseButtonDoubleClick(oTreeViewMouseEvent) +\t//Put your changes here +\treturn NIL + +[ListViewMouseButtonDoubleClick] +method ListViewMouseButtonDoubleClick(oListViewMouseEvent) class %FORM:NAME% +\tsuper:ListViewMouseButtonDoubleClick(oListViewMouseEvent) +\t//Put your changes here +\treturn NIL + +[TabSelect] +method TabSelect(oControlNotifyEvent) class %FORM:NAME% +\tsuper:TabSelect(oControlNotifyEvent) +\t//Put your changes here +\treturn NIL + +[TabSelectionChanging] +method TabSelectionChanging(oControlNotifyEvent) class %FORM:NAME% +\tsuper:TabSelectionChanging(oControlNotifyEvent) +\t//Put your changes here +\treturn NIL + +[TabKeyDown] +method TabKeyDown(oControlNotifyEvent) class %FORM:NAME% +\tsuper:TabKeyDown(oControlNotifyEvent) +\t//Put your changes here +\treturn NIL + +[TreeViewItemDrag] +method TreeViewItemDrag(oTreeViewDragEvent) class %FORM:NAME% +\tsuper:TreeViewItemDrag(oTreeViewDragEvent) +\t//Put your changes here +\treturn NIL + +[TreeViewItemEdit] +method TreeViewItemEdit(oTreeViewEditEvent) class %FORM:NAME% +\tsuper:TreeViewItemEdit(oTreeViewEditEvent) +\t//Put your changes here +\treturn NIL + +[TreeViewItemDelete] +method TreeViewItemDelete(oTreeViewDeleteEvent) class %FORM:NAME% +\tsuper:TreeViewItemDelete(oTreeViewDeleteEvent) +\t//Put your changes here +\treturn NIL + +[TreeViewItemExpanded] +method TreeViewItemExpanded(oTreeViewExpandedEvent) class %FORM:NAME% +\tsuper:TreeViewItemExpanded(oTreeViewExpandedEvent) +\t//Put your changes here +\treturn NIL + +[TreeViewItemExpanding] +method TreeViewItemExpanding(oTreeViewExpandingEvent) class %FORM:NAME% +\tsuper:TreeViewItemExpanding(oTreeViewExpandingEvent) +\t//Put your changes here +\treturn NIL + +[TreeViewKeyDown] +method TreeViewKeyDown(oTreeViewKeyEvent) class %FORM:NAME% +\tsuper:TreeViewKeyDown(oTreeViewKeyEvent) +\t//Put your changes here +\treturn NIL + +[TreeViewSelectionChanged] +method TreeViewSelectionChanged(oTreeViewSelectionEvent) class %FORM:NAME% +\tsuper:TreeViewSelectionChanged(oTreeViewSelectionEvent) +\t//Put your changes here +\treturn NIL + +[TreeViewSelectionChanging] +method TreeViewSelectionChanging(oTreeViewSelectionEvent) class %FORM:NAME% +\tsuper:TreeViewSelectionChanging(oTreeViewSelectionEvent) +\t//Put your changes here +\treturn NIL + +[MonthCalSelectionChanged] +method MonthCalSelectionChanged(oMonthCalSelectionEvent) class %FORM:NAME% +\tsuper:MonthCalSelectionChanged(oMonthCalSelectionEvent) +\t//Put your changes here +\treturn NIL + +[DateTimeSelectionChanged] +method DateTimeSelectionChanged(oDateTimeSelectionEvent) class %FORM:NAME% +\tsuper:DateTimeSelectionChanged(oDateTimeSelectionEvent) +\t//Put your changes here +\treturn NIL + diff --git a/src/VisualStudio/ProjectSystemCPS.TestBed/VO/Resources/HelpAbt.BMP b/src/VisualStudio/ProjectSystemCPS.TestBed/VO/Resources/HelpAbt.BMP new file mode 100644 index 0000000000000000000000000000000000000000..63825a67f109af39f89a4c03953b75eee17da86f GIT binary patch literal 48654 zcmeHQcUV-%7WciE@BQ(d5F2WuV31xG5U?bcl=l)9 z#n@@mY^VgghQz2~j2#;)i3L;;8v+{Nxw|lX_wK!S3!4}7W&8%dxpU8)8Gd_q=A1KU zjz0dK4|Y=hQ^0Q#{Op3CVEFl4CrkL^0gM1f03(1AzzARjFaj6>i~vReBY+XW2w(&-0vG{|07l@| zt5db-mQ=3TaIs*+#X@@7SXGp9X5*?eS+9OG8%$>p>?!$tVR`o2@3Ph~N_p0rvgMy& z`mx+>gJDJ;MnG2RXPfIS$Qz(rgn92;=Xm;T9pB+WC$^5qf_mx3YwJ3~yE z#-^CiM5jJ!B!`-z5Y3f3C?z%~aK>H!ZwM4!-pQksN{dHWWqX!ZA24b@w=P|nWA!F% z8C;77yo$E#dj5!UY>k=&CN>KK!*)z?Dd1E}-3xss1;dOQX#PNPio~8vFkmrJy7pZ; zHNYsInD`Gt;O4`cUYq1RN~wKr|3wwc4V>HFRbRyjx^W7ILIfslEYYL6smVZwnCy>5 zAo2T8d6ZH~k)KVD*ZpS?^iJ||{ex(M1;0Zh+QH(x?6rDXVS?8O0YT0H@M&-xls6Ddkg2hmnONcxIj~wFAqPbI~9vhepx-L>>%i#4p2yJvb}VV z?{8nd6lv3iXEycV^-!yM$K8J<^92!Y~5e!Ow2V*i@ujrZ>}21SoQ6y{zyvcJ0W7?g|U2agu#qF1Suy(=ocfUEN_{mNbnohAv4ZYAy9t=IVA!^{w4g3}D>06iG4>X4e6KFx zMEz{kVAfujE02YCe*=O*+K2_+!Yuz;xoaCMF>dTQ0wYQ$^C+e8%CBUydX%OoPof<7 zBQ%H@fyI$5;~X041Kj9pEz}nblCu6?zg##Awhvu0;~jhMU2UkRj+G~Ffk~_zo-m^P~`p)v6sX4rJ=jvr+=d>bZT(Y|j5;@qF z;+%RlKX2CM)73?X|bPp>&TFak_GM#^z|Hl znp8#Y&`3`(VM;l%q4eblmW3#Li-x+px~w4NIaw~C2Z}%}*;nKF{4lyqaEq}BY`F|d zsRx@q>*rcBGGLxhK$t9coMN7s-QNXDLWP43gINLqT5$^#eOp3$~5yKji>g8nGxVV?$2tZ6}U+tJuYBwqIpE$=m zC{!LaTQPn%IWfX}p^RW?9ZDCg)gJ~LJ`jVlV%PRKVGs0iLj{iYk8j<~AX7iCnl>(p zrku=`3qf%;-}M^SF>xb>Ks*&ws_~S%?iexZi(wOHNI@{vB_Ny(o8~oNEI^6}<h$tH<}TF-(J3S-t41} zDV1#T^C}oMX}UZpj6|{#7^WB-sYo0xPnW2~Lw)5WYfVSy08aQAVK;5dwC|P$?19HS zw3QR+6-!v&`vr%ucjOZ=v~S_166+i<@RYjjd_Op0V9-ozH=&on+2puLuk-;fpm-#U z?G_G^8_Jr4uC=w5vP?+=6|hVpKADxgJZ}T|yHZIJA_kAg?JJj9iE*RZ5%4XUDBAL# z?>8fZLnL&mK=tt-lT*XI7f6K>HgDBl*Lc(ooJro?m=(ePD<^*dW!02_ub4DO31$vS zq$t{pT#(}Jk~uwaTYpg#tNlKvk$ z-*|f4K!%8{@qS$l(F1k_qS)qAUwG%0%2o?uK(_SGGNXIlrJ4qx^&NmG)7`D(C zjfg8|M4+zvS|VW?9PR~1D_tH?9aN5q(`AD*h7K*AWbC*tEiFnnYr5Y%6^`|(3$vOJ z7wRbrR@o523Nu%AyS80gsE*0n*QS7Zf^+W|FR^KB?1!VDe(A_}Q(UZ5hl;`31!90i zLQ|O~4@w;9R`NbXq;I`as-L4a#r%PiDBHK=gxzCR1uF4wHtCdXXiBPrh<56YXb@!- zVF}v}of0}G$b@uCu!tjUyRe?fXio$`8HWIzumiSW2!MM$I?!Y695Rq$r-YG{X2`ws z{lUl;7Y!HW8XZag=)wKtC0i?bN4i-KwAV?pD*N ze^-{4oZ9!T(%UsrCPb7_PZ=mVUw)`k#SJKd`K`+rjr+4PwLS#kpfk>?XR6vOP7({m zf*GOS<384yu^=ZCrc1#g=C)&`IwA{>c6) zySM2U1mK@86(svf$3{|$2P_(sLL`H-{NVtOeqsx;VGumlk!e&N`+?y$k8|v~Z}l=( zo>{SBT5fZah!a^pZ+gy3j2q2@0DSxCp%HDXhf48?nCJx#ji8TY8@-0OrA#VjcU+NHS3i^L zpCsTo=K5*ATX?K|aHAmzz|BNRxTLk4FnkE0oyr75R{FYbV~dCU2`vy<4i>9sks(EJ#Q*c&-K@m8v4aSFI?NMJ%`|8;1cS<< zVYP1Z_;2qYX`{B!!C! zbdf-HSUO-~`S$uj6Xx^ZpphwJTjcPBDJk8pU%Yss%c}L|3%FCMl8rVO2LztwB}M8| z;+BpekTK;0x;;q$GOFva?=SN8q7M;vC<*RDB6(m`Kq-ajAUeH)^$js$ajrH=q)1(q zaw5{P*R!WjS-VG2o^t{b$Gh9Em}YuVBI^);@y65pN~84U(1^5q`?j|Kx#dNRU5+=` zg#{25$c(-4ID14N0{rLwWz^ZLhbUE5(mWlN5Wk^1M}wm@bxD#55@APEz~P#vP>-HY z334GKqbgv=N`>j3j))s{SvuMj5k)ini&v8Pb7(+PP{*DHb7OT;T5)=VU0y$))Goa@ zk@uhSWrg}CYzm}32jx>;!0(+PS8WhfAcv+XWkPs`$WTQU@1Y@sF3BKQ+LV#jZ=E@0 zy30lPum0<_bLv<_&PA8;rX6C%IZLo8Zrn)wr@*V6>vOf>99{4Rg(^#=e*?r1T;=K# zX>XC<_Qh|rJ|Hk7-qi{bXjNSLGU7epF;dW-M$;v<+duYwvw~{^xSbV&eVJcF3JdBS zQ~QgC9@P$w=S|O8yQ9WikMHv5&j(NG;uD++5S}+RJ*d4~UsngEvHrpJs*AH7xYvaS=5KT>keuME+^l4g@ixr}TO1RLn>SV>EhK7cFbx`W-Y9H0}DLm+}z!ikR zlZOr9ROE4JKvWM%4g^%7E<6>8+z@OYz*sTRBcv(?i>B@gXA&L;=SbTws<6+tbptNf zub#nfnI0|5OAuKiX9!fleRs72$Cfh?HSTd)u)m(9pgIl>{sD-*fe`(L+eC>SCA94!91y^>bQHTCdS-uFTl%1OVmM)`+_fFC!WCl?IJIXdGeSdqTLJA9 zRu#}x4d`E0`6MN(x$-7H=$Dk0O6JZhMx>pP%n*2+z+(`_S*mQ*LuH`~R#_ zf9DotzNO0sSS4GRq#H5{rqM+pJ1z_^^)Tguwr)TUjbxdKi8h?lf{=m-yyI6 zmnSpz@>xD{G!^XEKBZJS0nWGP=S{jDTVK6mEPzx3pE)yhDRGNl1a6%EDbiLu-mm>5 zX!|TwIW$a83OZ$9q+`Ah+DaG|*jajRls#=`KO8}m&rS%ZAMQ{AU4&&r9eX4yWc;A} zXF#ZyPaTU`;fmo1z*f%h(14@NOb(;KkWm_&9>HA|FiaRyfgZtA8eA66&uaT(aF%Kk z_`pXGO2y4{r&)U?kwW_Z2!GuoUvE}Q+{lAK>DNnRA^8cfLj#gS!hLogCVF2jt<12` z>yLziQGrOpK3R&Rw<>t}9L`z`3J=;-paRCo`EvIB#*2Nu>Cz5zlI$VkVUAzvBG7pM zUbxkp03E1yTm_D$zRO;x+t=6@ae*&*N?`|q0tdB>LInjC`tk5Q$q4p?!#=|DXkj*M zmkdUBK`Mh>S5`{g$c4bYnj3I*0{24`UHj@5Xa}RUm?IGi7f-Hsi2RWh47bne2bbL( zHxGs)YA;Z?%jqX87{L>7e7W*C70^Y?tpS#4>GCgFhuaiA6ZS#acTkD4eXFV2^z=cj zqu@sKBGBC0+|+8yZIPDNR#@U9sM47#)}3cr{C)pAb`R~*E&1>kXxfOaI?wF>#*3FP z&HJ}tZm%r@j~W|!=JxcN)``yxd~coyEc^;1fDyn5U<5D%7y*m`MgSv#5x@vw1TX>^ a0gM1f03(1AzzARjFaj6>jKH4_f&T$kqsMaq literal 0 HcmV?d00001 diff --git a/src/VisualStudio/ProjectSystemCPS.TestBed/VO/Resources/XSharp.ICO b/src/VisualStudio/ProjectSystemCPS.TestBed/VO/Resources/XSharp.ICO new file mode 100644 index 0000000000000000000000000000000000000000..dd1707e251c47b5348cb85b0abfbb1e40bfb30d9 GIT binary patch literal 9662 zcmeHNc~n)$88=DyrtL}Flb)vMw6-RSS|h8XN!%0Miq?&~MdNY9sENiUVKu?ss1c3E zeTxYKqT&`6ji`i8MdE@x2r3}&-FM%@;@zgdZ+Q3m-UT#CZTk;+KIY!}X1@8&x6L;f zn>2X}|9pL$Hld${mzp$r9<2|^7c?65RFkIojh`258_(a2u_lewsDnlyX!LAd2E|MM0Qiv)ST7 zD-E(*>3K#)Ma2tP`>98*Z@1fjXBUL2b!3n{t94#wWo6)hV-L{H^LCr9K(yHukXjEZ zHbE!_F5iamS65g6)nT{ac#Mo1$Qa#_j33$$-7g``SfAB6tLl);?quL=c2PW^n3(uX zed~ai4pN;CC#$NiVU;FwI$aEWl2eZPzo<{f5AB2BWRmRCic)3Vv02jiW3yzPCgM4x zC|h=jc3N8zt=948bp$~e0iFc^VK?*7%9|#SmNtfulszeHPm)Ay5V>qNKeLSgiP-8 z<(Oa}HKu(FPg1VtX38|b(&b{X_uusSPojnXMj^JiZVp~QCbvy`L-kMU#fbbsw4bDc zRM}~F5Pn+!eSKYE|B(6|4hPHJl>qE5wdkOiSaiEuiVA4PcDz4Dw%#w-YWHZ%z}%nc z^PfZuJkNphNWMj|sNpAO%YI2c8T8MI_A&6#1f{g%9=8GXcpLkD0shh)_)AHzWhKSx zim+Y^VTbNHMYn6t=5`NIujidn$hVLKsO|kEFq>rne;DFGBR@Y~n5A?`=|}!!?4fO9 zh_*F;pUYTU2mSSr`(mkiK>N z^t`lxH|1_oAs>&icMvx%2rtEq2BOxC|4}^iSo6Eh>Fkbms>mNM+`TM!+B1m9fo_Mk ziEpq^&u^9NHc@k7pX6^im$c-#2q`MCnSq_}A=pPt-?2${AfJ*v#Mp7G)!N!-u?%#% zTr&|bvxuCcIM|8vr>v~(599-Szsqi)>9E=QqwhI0&PTmtdF$L_Mlke{jeqac5~j@ zL;H9i#=C&=M`oOldbgwI!8|%|*@pbjWnlNRLHp!T?aIAt^0@n z)3JND6=kK2e2o)w>-BTF+`nkd-J91Ly+1z+ubJm`I-iFP1d@5*Qm*zn z@Rp+M-`#4{jz>LM8rs!M_k=(0wU^c6s|8-V_ry>7pYv)PoDa=8(9TL}aczDzWA=PsIV{w`cw*~113!)VxL;R;my?=Y%d^?O zf=(9c`PYoKp?Bn>Vf=hTjz4)TOYu+csrn%ndVSbY4hs*^hJ*)dW5e3WF+t6>NdIP9 z*6u_ZIi_CSJ6)Bm>Vd>*^OP|L#>1oQDF<0)mzK1esa-bGADUflR5TL!&v!%Ro;wS~NSub{)Uyylzc|Lz8F{oA9pqif@pqw7{`S97yiRc-AzxgmYm*N;69r}e3> z$Spi8hrZuIg6?avV-G_>!^Zf^=|@u}s^MsCeRO1PJe$`&kW+NW)+`FxiDD4TdOx+% z9a=r)z1BvX*DXLL+PnBee&!%Xw$_KUp+4Y`Av}m7mb*Jyp46|6G#Gwk=pTCtiF-v} zp0`1AijD^LfAWhhY+jEbPIEf>vMA&#qR3z$qo4S(onAJ0QP|7eZUNfdZh`8~*qJ*1 z{S9F?_d6_>me{X_@R!{#R6iNgQ;7_2p}skytxS3+I}Dr}q_#&qcSfyo@y=zvKCNSe z;wzWg@y%;NpP`<9heZ;0%C|k*@jmJ&+A@8j!CMsCl^xr(MoruNnM%2cpT~B4ycuT$ z@6SN@o2hODel2ZXyoCCaQNDbyyN^6{yuUgGG5q?%S7rZQy_ql0<(SMR3eM#^*lGW$ zLG@TdVfR zcYmC@quqBstspx?j&9$=>t2F2wKby#O2=-Ulme6cXy!en-^wuX=$NVHSfFd1aSSpI zx^ey-)u5b;3N9J_w6}Y=i!|ct^*WwoA7ukNcd^brT2FxPKSdo(cy-@#x7wAQOc~>} z3CCy2X8c1Bzs<3FVy9=+MeI_>Np|7n*TCrG&NDvj>+KuarPJx0^3uLC;x`ibPwz_5 zE}l+TE@x(_g2ke7U$WVnHna!eUq-m7wm+S)O^&7eIOOQ~={YfGrc8cCn9dgG%K?cG z>r>hbY{C*Rr`EB-+Ob1X-}rH&+^O4O4sxd6Pc7x8hrG5L`Z(ns`-?B-eB_-;|eLH#G326_guauIj1-17VvJuYZ*{9}7{5yrmeLwWzTkO8I+-nc(#tmh& zTK$nzMBt0HI)OfB`~Jn~Cw@eLn}_vgw|&S<|9n&J86b+2>cfwkGnL?;{t5>>JK*l-=^Ofncd%MBAoMFfgXfkLG$AsYF(J>*rAzg@appmfg9l-G!oKl;GH_$73#_fxxL_7sDcx+ISC><&jha!+(+J?EEI z6erR-OZC_JqX*^K4lTX%O%!}}Q}`&^iu*3f$6hbqb?X2%_7SO?-frrN9yW@Bkb~v1sxpNVX zqqTDJogubkYYk_twroHTo{M!J@-}#_IB(6EA)E)_$ur?L^bdJOG}lsIu3pXhM)IQU zZzT8&zU%XEA8;|pD`q*B7R#St!*Uwq#62OWBu~mII;Z9o=P@F^CUVOtf;{au&e?4< zCTUw|gloxjBWwNA9ZPg?--dqTr7oM#Xde70$Gooo4&HR^0iTdWh2qmp)K7dt6qo9A z%q_-D!59&~#J^LME$DkSC&UIY%V68!d!g7X)sfZs8;69ybx4&})s&N6HpCL<--M0~ zo~yEoc=${wMw!?2e20A^Fy;=%dif%~qrU_c;W@s(XMFD)3pv+-&+8sQ@1O#lkN={1 zH8nNOAuk1c`WAEn{&x=T3i>-Cbm<4$4?gE%pJ%bB1(;Xo8 + + +Visual Objects Application. + + + + + + + diff --git a/src/VisualStudio/ProjectSystemCPS.TestBed/VO/SDI Menus.EmptySDIMenu.rc b/src/VisualStudio/ProjectSystemCPS.TestBed/VO/SDI Menus.EmptySDIMenu.rc new file mode 100644 index 0000000000..73b5c61d08 --- /dev/null +++ b/src/VisualStudio/ProjectSystemCPS.TestBed/VO/SDI Menus.EmptySDIMenu.rc @@ -0,0 +1,40 @@ + +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime version: 4.0.30319.42000 +// Generator : XSharp.MenuEditor 2.1.0.0 +// Timestamp : 28-1-2020 08:22:38 +// +// Changes to this file may cause incorrect behavior and may be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +#define IDM_EmptySDIMenu_File_ID 18500 +#define IDM_EmptySDIMenu_File_Open_ID 18501 +#define IDM_EmptySDIMenu_File_Print_Setup_ID 18503 +#define IDM_EmptySDIMenu_File_Exit_ID 18505 +#define IDM_EmptySDIMenu_Help_ID 18506 +#define IDM_EmptySDIMenu_Help_Index_ID 18507 +#define IDM_EmptySDIMenu_Help_Using_Help_ID 18508 +#define IDM_EmptySDIMenu_Help_About_ID 18510 +EmptySDIMenu MENU +BEGIN + POPUP "&File" + BEGIN + MENUITEM "&Open...\tCtrl+O" , IDM_EmptySDIMenu_File_Open_ID + MENUITEM SEPARATOR + MENUITEM "P&rint Setup..." , IDM_EmptySDIMenu_File_Print_Setup_ID + MENUITEM SEPARATOR + MENUITEM "E&xit\tAlt+F4" , IDM_EmptySDIMenu_File_Exit_ID + END + POPUP "&Help" + BEGIN + MENUITEM "&Index\tF1" , IDM_EmptySDIMenu_Help_Index_ID + MENUITEM "&Using Help" , IDM_EmptySDIMenu_Help_Using_Help_ID + MENUITEM SEPARATOR + MENUITEM "&About..." , IDM_EmptySDIMenu_Help_About_ID + END +END + diff --git a/src/VisualStudio/ProjectSystemCPS.TestBed/VO/SDI Menus.EmptySDIMenu.xsmnu b/src/VisualStudio/ProjectSystemCPS.TestBed/VO/SDI Menus.EmptySDIMenu.xsmnu new file mode 100644 index 0000000000..0b595b3464 --- /dev/null +++ b/src/VisualStudio/ProjectSystemCPS.TestBed/VO/SDI Menus.EmptySDIMenu.xsmnu @@ -0,0 +1,226 @@ + + + + EmptySDIMenu + + + + + Flat Toolbar + Icon + No + 18500 + + + File + &File + + + File + None + Yes + No + + + + + + + -1 + + + + + FileOpen + &Open... + Open a file + File_Open + Ctrl+O + Yes + No + Open + + + Open File + 1 + IDM_FILE_OPEN + + + + + ------------------------ + + + + + None + Yes + No + + + + + + + -1 + + + + + FilePrinterSetup + P&rint Setup... + Setup printer options + File_Printer_Setup + None + Yes + No + + + + + + + -1 + + + + + + + ------------------------- + + + + + None + Yes + No + + + + + + + -1 + + + + + FileExit + E&xit + End of application + File_Exit + Alt+F4 + Yes + No + + + + + + + -1 + + + + + + + Help + &Help + + + + + None + Yes + No + + + + + + + -1 + + + + + HelpIndex + &Index + Index of help + Help_Index + F1 + Yes + No + + + + + + + -1 + + + + + HelpUsingHelp + &Using Help + How to use help + Help_UsingHelp + None + Yes + No + + + + + + + -1 + + + + + + + ------------------------- + + + + + None + Yes + No + + + + + + + -1 + + + + + HelpAbout + &About... + About application + + + None + Yes + No + Help + + + Help About + 2 + + + + + + + + \ No newline at end of file diff --git a/src/VisualStudio/ProjectSystemCPS.TestBed/VO/SDI Menus.EmptySDIMenu_Accelerator.rc b/src/VisualStudio/ProjectSystemCPS.TestBed/VO/SDI Menus.EmptySDIMenu_Accelerator.rc new file mode 100644 index 0000000000..35f20c8812 --- /dev/null +++ b/src/VisualStudio/ProjectSystemCPS.TestBed/VO/SDI Menus.EmptySDIMenu_Accelerator.rc @@ -0,0 +1,27 @@ + +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime version: 4.0.30319.42000 +// Generator : XSharp.MenuEditor 2.1.0.0 +// Timestamp : 28-1-2020 08:22:38 +// +// Changes to this file may cause incorrect behavior and may be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +#define IDM_EmptySDIMenu_File_ID 18500 +#define IDM_EmptySDIMenu_File_Open_ID 18501 +#define IDM_EmptySDIMenu_File_Print_Setup_ID 18503 +#define IDM_EmptySDIMenu_File_Exit_ID 18505 +#define IDM_EmptySDIMenu_Help_ID 18506 +#define IDM_EmptySDIMenu_Help_Index_ID 18507 +#define IDM_EmptySDIMenu_Help_Using_Help_ID 18508 +#define IDM_EmptySDIMenu_Help_About_ID 18510 +EmptySDIMenu_Accelerator ACCELERATORS +BEGIN + 79, IDM_EmptySDIMenu_File_Open_ID, CONTROL ,VIRTKEY + 115, IDM_EmptySDIMenu_File_Exit_ID, ALT ,VIRTKEY + 112, IDM_EmptySDIMenu_Help_Index_ID, VIRTKEY +END diff --git a/src/VisualStudio/ProjectSystemCPS.TestBed/VO/SDI Menus.StandardSDIMenu.rc b/src/VisualStudio/ProjectSystemCPS.TestBed/VO/SDI Menus.StandardSDIMenu.rc new file mode 100644 index 0000000000..dc70afa2d1 --- /dev/null +++ b/src/VisualStudio/ProjectSystemCPS.TestBed/VO/SDI Menus.StandardSDIMenu.rc @@ -0,0 +1,76 @@ + +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime version: 4.0.30319.42000 +// Generator : XSharp.MenuEditor 2.1.0.0 +// Timestamp : 28-1-2020 08:22:41 +// +// Changes to this file may cause incorrect behavior and may be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +#define IDM_StandardSDIMenu_File_ID 29000 +#define IDM_StandardSDIMenu_File_Open_ID 29001 +#define IDM_StandardSDIMenu_File_Print_ID 29003 +#define IDM_StandardSDIMenu_File_Print_Setup_ID 29004 +#define IDM_StandardSDIMenu_File_Exit_ID 29006 +#define IDM_StandardSDIMenu_Edit_ID 29007 +#define IDM_StandardSDIMenu_Edit_Cut_ID 29008 +#define IDM_StandardSDIMenu_Edit_Copy_ID 29009 +#define IDM_StandardSDIMenu_Edit_Paste_ID 29010 +#define IDM_StandardSDIMenu_Edit_Insert_Record_ID 29012 +#define IDM_StandardSDIMenu_Edit_Delete_Record_ID 29013 +#define IDM_StandardSDIMenu_Edit_Go_To_Top_ID 29015 +#define IDM_StandardSDIMenu_Edit_Previous_ID 29016 +#define IDM_StandardSDIMenu_Edit_Next_ID 29017 +#define IDM_StandardSDIMenu_Edit_Go_To_Bottom_ID 29018 +#define IDM_StandardSDIMenu_View_ID 29019 +#define IDM_StandardSDIMenu_View_Form_ID 29020 +#define IDM_StandardSDIMenu_View_Table_ID 29021 +#define IDM_StandardSDIMenu_Help_ID 29022 +#define IDM_StandardSDIMenu_Help_Index_ID 29023 +#define IDM_StandardSDIMenu_Help_Context_Help_ID 29024 +#define IDM_StandardSDIMenu_Help_Using_Help_ID 29025 +#define IDM_StandardSDIMenu_Help_About_ID 29027 +StandardSDIMenu MENU +BEGIN + POPUP "&File" + BEGIN + MENUITEM "&Open...\tCtrl+O" , IDM_StandardSDIMenu_File_Open_ID + MENUITEM SEPARATOR + MENUITEM "&Print" , IDM_StandardSDIMenu_File_Print_ID + MENUITEM "P&rint Setup..." , IDM_StandardSDIMenu_File_Print_Setup_ID + MENUITEM SEPARATOR + MENUITEM "E&xit\tAlt+F4" , IDM_StandardSDIMenu_File_Exit_ID + END + POPUP "&Edit" + BEGIN + MENUITEM "Cu&t\tCtrl+X" , IDM_StandardSDIMenu_Edit_Cut_ID + MENUITEM "&Copy\tCtrl+C" , IDM_StandardSDIMenu_Edit_Copy_ID + MENUITEM "&Paste\tCtrl+V" , IDM_StandardSDIMenu_Edit_Paste_ID + MENUITEM SEPARATOR + MENUITEM "&Insert Record" , IDM_StandardSDIMenu_Edit_Insert_Record_ID + MENUITEM "&Delete Record" , IDM_StandardSDIMenu_Edit_Delete_Record_ID + MENUITEM SEPARATOR + MENUITEM "G&o To Top\tCtrl+Home" , IDM_StandardSDIMenu_Edit_Go_To_Top_ID + MENUITEM "Pre&vious" , IDM_StandardSDIMenu_Edit_Previous_ID + MENUITEM "&Next" , IDM_StandardSDIMenu_Edit_Next_ID + MENUITEM "Go To &Bottom\tCtrl+End" , IDM_StandardSDIMenu_Edit_Go_To_Bottom_ID + END + POPUP "&View" + BEGIN + MENUITEM "&Form\tShift+F2" , IDM_StandardSDIMenu_View_Form_ID + MENUITEM "&Table\tF2" , IDM_StandardSDIMenu_View_Table_ID + END + POPUP "&Help" + BEGIN + MENUITEM "&Index\tF1" , IDM_StandardSDIMenu_Help_Index_ID + MENUITEM "&Context Help\tCtrl+F1" , IDM_StandardSDIMenu_Help_Context_Help_ID + MENUITEM "&Using Help" , IDM_StandardSDIMenu_Help_Using_Help_ID + MENUITEM SEPARATOR + MENUITEM "&About..." , IDM_StandardSDIMenu_Help_About_ID + END +END + diff --git a/src/VisualStudio/ProjectSystemCPS.TestBed/VO/SDI Menus.StandardSDIMenu.xsmnu b/src/VisualStudio/ProjectSystemCPS.TestBed/VO/SDI Menus.StandardSDIMenu.xsmnu new file mode 100644 index 0000000000..5087ad497b --- /dev/null +++ b/src/VisualStudio/ProjectSystemCPS.TestBed/VO/SDI Menus.StandardSDIMenu.xsmnu @@ -0,0 +1,515 @@ + + + + StandardSDIMenu + + + + + Flat Toolbar + Icon + No + 29000 + + + File + &File + + + File + None + Yes + No + + + + + + + -1 + + + + + FileOpen + &Open... + Open a file + File_Open + Ctrl+O + Yes + No + Open + Open + Open File + 1 + IDM_FILE_OPEN + + + + + ------------------------- + + + + + None + Yes + No + + + + + + + -1 + + + + + FilePrint + &Print + Print the window + + + None + Yes + No + + + + + + + -1 + + + + + FilePrinterSetup + P&rint Setup... + Setup printer options + File_Printer_Setup + None + Yes + No + + + + + + + -1 + + + + + + + ------------------------- + + + + + None + Yes + No + + + + + + + -1 + + + + + FileExit + E&xit + End of application + File_Exit + Alt+F4 + Yes + No + + + + + + + -1 + + + + + + + Edit + &Edit + Edit information + Edit + None + Yes + No + + + + + + + -1 + + + + + Cut + Cu&t + Cut to clipboard + Cut + Ctrl+X + Yes + No + Cut + Cut + Cut + 3 + + + + + Copy + &Copy + Copy to clipboard + Copy + Ctrl+C + Yes + No + Copy + Copy + Copy + 4 + + + + + Paste + &Paste + Paste from clipboard + Paste + Ctrl+V + Yes + No + Paste + Paste + Paste + 5 + + + + + + + ------------------------- + + + + + None + Yes + No + + + + + + + -1 + + + + + Append + &Insert Record + Insert a new record + Append + None + Yes + No + + + + + + + -1 + + + + + Delete + &Delete Record + Delete this record + Delete + None + Yes + No + + + + + + + -1 + + + + + + + ------------------------- + + + + + None + Yes + No + + + + + + + -1 + + + + + GoTop + G&o To Top + Go to first record + GoTop + Ctrl+Home + Yes + No + Start Record + Go Top + Go Top + 7 + + + + + SkipPrevious + Pre&vious + Go to previous record + SkipPrevious + None + Yes + No + Previous Record + Previous Record + Previous Record + 8 + + + + + SkipNext + &Next + Go to next record + SkipNext + None + Yes + No + Next Record + Next Record + Next Record + 9 + + + + + GoBottom + Go To &Bottom + Go to last record + GoBottom + Ctrl+End + Yes + No + End Record + Go Bottom + Go Bottom + 10 + + + + + + + View + &View + + + + + None + Yes + No + + + + + + + -1 + + + + + ViewForm + &Form + View a single record as a form + ViewForm + Shift+F2 + Yes + No + View Form + Form View + Form View + 12 + + + + + ViewTable + &Table + View several records in a table + ViewTable + F2 + Yes + No + View Table + Browse View + Browse View + 13 + + + + + + + Help + &Help + + + + + None + Yes + No + + + + + + + -1 + + + + + HelpIndex + &Index + Index of help + Help_Index + F1 + Yes + No + + + + + + + -1 + + + + + HelpContext + &Context Help + Context sensitive help + Help_ContextHelp + Ctrl+F1 + Yes + No + + + + + + + -1 + + + + + HelpUsingHelp + &Using Help + How to use help + Help_UsingHelp + None + Yes + No + + + + + + + -1 + + + + + + + ------------------------- + + + + + None + Yes + No + + + + + + + -1 + + + + + HelpAbout + &About... + About application + + + None + Yes + No + Help + Help + Help + 15 + IDM_HELP_ABOUT + + + + + + \ No newline at end of file diff --git a/src/VisualStudio/ProjectSystemCPS.TestBed/VO/SDI Menus.StandardSDIMenu_Accelerator.rc b/src/VisualStudio/ProjectSystemCPS.TestBed/VO/SDI Menus.StandardSDIMenu_Accelerator.rc new file mode 100644 index 0000000000..aa9c190f62 --- /dev/null +++ b/src/VisualStudio/ProjectSystemCPS.TestBed/VO/SDI Menus.StandardSDIMenu_Accelerator.rc @@ -0,0 +1,50 @@ + +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime version: 4.0.30319.42000 +// Generator : XSharp.MenuEditor 2.1.0.0 +// Timestamp : 28-1-2020 08:22:41 +// +// Changes to this file may cause incorrect behavior and may be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +#define IDM_StandardSDIMenu_File_ID 29000 +#define IDM_StandardSDIMenu_File_Open_ID 29001 +#define IDM_StandardSDIMenu_File_Print_ID 29003 +#define IDM_StandardSDIMenu_File_Print_Setup_ID 29004 +#define IDM_StandardSDIMenu_File_Exit_ID 29006 +#define IDM_StandardSDIMenu_Edit_ID 29007 +#define IDM_StandardSDIMenu_Edit_Cut_ID 29008 +#define IDM_StandardSDIMenu_Edit_Copy_ID 29009 +#define IDM_StandardSDIMenu_Edit_Paste_ID 29010 +#define IDM_StandardSDIMenu_Edit_Insert_Record_ID 29012 +#define IDM_StandardSDIMenu_Edit_Delete_Record_ID 29013 +#define IDM_StandardSDIMenu_Edit_Go_To_Top_ID 29015 +#define IDM_StandardSDIMenu_Edit_Previous_ID 29016 +#define IDM_StandardSDIMenu_Edit_Next_ID 29017 +#define IDM_StandardSDIMenu_Edit_Go_To_Bottom_ID 29018 +#define IDM_StandardSDIMenu_View_ID 29019 +#define IDM_StandardSDIMenu_View_Form_ID 29020 +#define IDM_StandardSDIMenu_View_Table_ID 29021 +#define IDM_StandardSDIMenu_Help_ID 29022 +#define IDM_StandardSDIMenu_Help_Index_ID 29023 +#define IDM_StandardSDIMenu_Help_Context_Help_ID 29024 +#define IDM_StandardSDIMenu_Help_Using_Help_ID 29025 +#define IDM_StandardSDIMenu_Help_About_ID 29027 +StandardSDIMenu_Accelerator ACCELERATORS +BEGIN + 79, IDM_StandardSDIMenu_File_Open_ID, CONTROL ,VIRTKEY + 115, IDM_StandardSDIMenu_File_Exit_ID, ALT ,VIRTKEY + 88, IDM_StandardSDIMenu_Edit_Cut_ID, CONTROL ,VIRTKEY + 67, IDM_StandardSDIMenu_Edit_Copy_ID, CONTROL ,VIRTKEY + 86, IDM_StandardSDIMenu_Edit_Paste_ID, CONTROL ,VIRTKEY + 36, IDM_StandardSDIMenu_Edit_Go_To_Top_ID, CONTROL ,VIRTKEY + 35, IDM_StandardSDIMenu_Edit_Go_To_Bottom_ID, CONTROL ,VIRTKEY + 113, IDM_StandardSDIMenu_View_Form_ID, SHIFT ,VIRTKEY + 113, IDM_StandardSDIMenu_View_Table_ID, VIRTKEY + 112, IDM_StandardSDIMenu_Help_Index_ID, VIRTKEY + 112, IDM_StandardSDIMenu_Help_Context_Help_ID, CONTROL ,VIRTKEY +END diff --git a/src/VisualStudio/ProjectSystemCPS.TestBed/VO/SDI Menus.VS_VERSION_INFO.rc b/src/VisualStudio/ProjectSystemCPS.TestBed/VO/SDI Menus.VS_VERSION_INFO.rc new file mode 100644 index 0000000000..28e0038020 --- /dev/null +++ b/src/VisualStudio/ProjectSystemCPS.TestBed/VO/SDI Menus.VS_VERSION_INFO.rc @@ -0,0 +1,31 @@ +VS_VERSION_INFO VERSIONINFO +FILEVERSION 1, 0, 0, 0 +PRODUCTVERSION 1, 0, 0, 0 +FILEFLAGSMASK 0x3fL +FILEFLAGS 0x00L +FILEOS VOS__WINDOWS32 +FILETYPE VFT_APP +FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "Comments", "This is a VOTestBed!\0" + VALUE "CompanyName", "XSharp BV\0" + VALUE "FileVersion", "1, 0, 0, 0\0" + VALUE "FileDescription", "X# VOTestBed\0" + VALUE "ProductVersion", "1.0.0.0\0" + VALUE "InternalName", "VO MDI App\0" + VALUE "ProductName", "X# VOTestBed\0" + VALUE "LegalCopyright", "Copyright \251 2015-2022\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + + + diff --git a/src/VisualStudio/ProjectSystemCPS.TestBed/VO/SDI Menus.prg b/src/VisualStudio/ProjectSystemCPS.TestBed/VO/SDI Menus.prg new file mode 100644 index 0000000000..f884a28c27 --- /dev/null +++ b/src/VisualStudio/ProjectSystemCPS.TestBed/VO/SDI Menus.prg @@ -0,0 +1,240 @@ +#region DEFINES +DEFINE IDM_StandardSDIMenu_File_ID := 29000 +DEFINE IDM_StandardSDIMenu_File_Open_ID := 29001 +DEFINE IDM_StandardSDIMenu_File_Print_ID := 29003 +DEFINE IDM_StandardSDIMenu_File_Print_Setup_ID := 29004 +DEFINE IDM_StandardSDIMenu_File_Exit_ID := 29006 +DEFINE IDM_StandardSDIMenu_Edit_ID := 29007 +DEFINE IDM_StandardSDIMenu_Edit_Cut_ID := 29008 +DEFINE IDM_StandardSDIMenu_Edit_Copy_ID := 29009 +DEFINE IDM_StandardSDIMenu_Edit_Paste_ID := 29010 +DEFINE IDM_StandardSDIMenu_Edit_Insert_Record_ID := 29012 +DEFINE IDM_StandardSDIMenu_Edit_Delete_Record_ID := 29013 +DEFINE IDM_StandardSDIMenu_Edit_Go_To_Top_ID := 29015 +DEFINE IDM_StandardSDIMenu_Edit_Previous_ID := 29016 +DEFINE IDM_StandardSDIMenu_Edit_Next_ID := 29017 +DEFINE IDM_StandardSDIMenu_Edit_Go_To_Bottom_ID := 29018 +DEFINE IDM_StandardSDIMenu_View_ID := 29019 +DEFINE IDM_StandardSDIMenu_View_Form_ID := 29020 +DEFINE IDM_StandardSDIMenu_View_Table_ID := 29021 +DEFINE IDM_StandardSDIMenu_Help_ID := 29022 +DEFINE IDM_StandardSDIMenu_Help_Index_ID := 29023 +DEFINE IDM_StandardSDIMenu_Help_Context_Help_ID := 29024 +DEFINE IDM_StandardSDIMenu_Help_Using_Help_ID := 29025 +DEFINE IDM_StandardSDIMenu_Help_About_ID := 29027 +DEFINE IDM_EmptySDIMenu_File_ID := 18500 +DEFINE IDM_EmptySDIMenu_File_Open_ID := 18501 +DEFINE IDM_EmptySDIMenu_File_Print_Setup_ID := 18503 +DEFINE IDM_EmptySDIMenu_File_Exit_ID := 18505 +DEFINE IDM_EmptySDIMenu_Help_ID := 18506 +DEFINE IDM_EmptySDIMenu_Help_Index_ID := 18507 +DEFINE IDM_EmptySDIMenu_Help_Using_Help_ID := 18508 +DEFINE IDM_EmptySDIMenu_Help_About_ID := 18510 +#endregion + +PARTIAL CLASS EmptySDIMenu INHERIT Menu + +CONSTRUCTOR( oOwner ) + + LOCAL oTB AS Toolbar + + SELF:PreInit() + + SUPER( ResourceID { "EmptySDIMenu" , _GetInst( ) } ) + + SELF:RegisterItem(IDM_EmptySDIMenu_File_ID, ; + HyperLabel{ #File , "&File" , , "File" } , SELF:Handle() , 0) + + SELF:RegisterItem(IDM_EmptySDIMenu_File_Open_ID, ; + HyperLabel{ #FileOpen , "&Open... Ctrl+O" , "Open a file" , "File_Open" }) + + SELF:RegisterItem(IDM_EmptySDIMenu_File_Print_Setup_ID, ; + HyperLabel{ #FilePrinterSetup , "P&rint Setup..." , "Setup printer options" , "File_Printer_Setup" }) + + SELF:RegisterItem(IDM_EmptySDIMenu_File_Exit_ID, ; + HyperLabel{ #FileExit , "E&xit Alt+F4" , "End of application" , "File_Exit" }) + + SELF:RegisterItem(IDM_EmptySDIMenu_Help_ID, ; + HyperLabel{ #Help , "&Help" , , } , SELF:Handle() , 1) + + SELF:RegisterItem(IDM_EmptySDIMenu_Help_Index_ID, ; + HyperLabel{ #HelpIndex , "&Index F1" , "Index of help" , "Help_Index" }) + + SELF:RegisterItem(IDM_EmptySDIMenu_Help_Using_Help_ID, ; + HyperLabel{ #HelpUsingHelp , "&Using Help" , "How to use help" , "Help_UsingHelp" }) + + SELF:RegisterItem(IDM_EmptySDIMenu_Help_About_ID, ; + HyperLabel{ #HelpAbout , "&About..." , "About application" , }) + + oTB := Toolbar{} + + oTB:ButtonStyle := TB_ICONONLY + oTB:Flat := TRUE + oTB:EnableBands(FALSE) + + oTB:AppendItem(IDT_OPEN , IDM_EmptySDIMenu_File_Open_ID) + oTB:AddTipText(IDT_OPEN , IDM_EmptySDIMenu_File_Open_ID , "Open File") + + oTB:AppendItem(IDT_HELP , IDM_EmptySDIMenu_Help_About_ID) + oTB:AddTipText(IDT_HELP , IDM_EmptySDIMenu_Help_About_ID , "Help About") + + + SELF:ToolBar := oTB + SELF:Accelerator := EmptySDIMenu_Accelerator{ } + + SELF:PostInit() + + RETURN + +END CLASS +PARTIAL CLASS EmptySDIMenu_Accelerator INHERIT Accelerator + +CONSTRUCTOR() + SUPER( ResourceID { "EmptySDIMenu_Accelerator" , _GetInst( ) } ) +RETURN + + +END CLASS +PARTIAL CLASS StandardSDIMenu INHERIT Menu + +CONSTRUCTOR( oOwner ) + + LOCAL oTB AS Toolbar + + SELF:PreInit() + + SUPER( ResourceID { "StandardSDIMenu" , _GetInst( ) } ) + + SELF:RegisterItem(IDM_StandardSDIMenu_File_ID, ; + HyperLabel{ #File , "&File" , , "File" } , SELF:Handle() , 0) + + SELF:RegisterItem(IDM_StandardSDIMenu_File_Open_ID, ; + HyperLabel{ #FileOpen , "&Open... Ctrl+O" , "Open a file" , "File_Open" }) + + SELF:RegisterItem(IDM_StandardSDIMenu_File_Print_ID, ; + HyperLabel{ #FilePrint , "&Print" , "Print the window" , }) + + SELF:RegisterItem(IDM_StandardSDIMenu_File_Print_Setup_ID, ; + HyperLabel{ #FilePrinterSetup , "P&rint Setup..." , "Setup printer options" , "File_Printer_Setup" }) + + SELF:RegisterItem(IDM_StandardSDIMenu_File_Exit_ID, ; + HyperLabel{ #FileExit , "E&xit Alt+F4" , "End of application" , "File_Exit" }) + + SELF:RegisterItem(IDM_StandardSDIMenu_Edit_ID, ; + HyperLabel{ #Edit , "&Edit" , "Edit information" , "Edit" } , SELF:Handle() , 1) + + SELF:RegisterItem(IDM_StandardSDIMenu_Edit_Cut_ID, ; + HyperLabel{ #Cut , "Cu&t Ctrl+X" , "Cut to clipboard" , "Cut" }) + + SELF:RegisterItem(IDM_StandardSDIMenu_Edit_Copy_ID, ; + HyperLabel{ #Copy , "&Copy Ctrl+C" , "Copy to clipboard" , "Copy" }) + + SELF:RegisterItem(IDM_StandardSDIMenu_Edit_Paste_ID, ; + HyperLabel{ #Paste , "&Paste Ctrl+V" , "Paste from clipboard" , "Paste" }) + + SELF:RegisterItem(IDM_StandardSDIMenu_Edit_Insert_Record_ID, ; + HyperLabel{ #Append , "&Insert Record" , "Insert a new record" , "Append" }) + + SELF:RegisterItem(IDM_StandardSDIMenu_Edit_Delete_Record_ID, ; + HyperLabel{ #Delete , "&Delete Record" , "Delete this record" , "Delete" }) + + SELF:RegisterItem(IDM_StandardSDIMenu_Edit_Go_To_Top_ID, ; + HyperLabel{ #GoTop , "G&o To Top Ctrl+Home" , "Go to first record" , "GoTop" }) + + SELF:RegisterItem(IDM_StandardSDIMenu_Edit_Previous_ID, ; + HyperLabel{ #SkipPrevious , "Pre&vious" , "Go to previous record" , "SkipPrevious" }) + + SELF:RegisterItem(IDM_StandardSDIMenu_Edit_Next_ID, ; + HyperLabel{ #SkipNext , "&Next" , "Go to next record" , "SkipNext" }) + + SELF:RegisterItem(IDM_StandardSDIMenu_Edit_Go_To_Bottom_ID, ; + HyperLabel{ #GoBottom , "Go To &Bottom Ctrl+End" , "Go to last record" , "GoBottom" }) + + SELF:RegisterItem(IDM_StandardSDIMenu_View_ID, ; + HyperLabel{ #View , "&View" , , } , SELF:Handle() , 2) + + SELF:RegisterItem(IDM_StandardSDIMenu_View_Form_ID, ; + HyperLabel{ #ViewForm , "&Form Shift+F2" , "View a single record as a form" , "ViewForm" }) + + SELF:RegisterItem(IDM_StandardSDIMenu_View_Table_ID, ; + HyperLabel{ #ViewTable , "&Table F2" , "View several records in a table" , "ViewTable" }) + + SELF:RegisterItem(IDM_StandardSDIMenu_Help_ID, ; + HyperLabel{ #Help , "&Help" , , } , SELF:Handle() , 3) + + SELF:RegisterItem(IDM_StandardSDIMenu_Help_Index_ID, ; + HyperLabel{ #HelpIndex , "&Index F1" , "Index of help" , "Help_Index" }) + + SELF:RegisterItem(IDM_StandardSDIMenu_Help_Context_Help_ID, ; + HyperLabel{ #HelpContext , "&Context Help Ctrl+F1" , "Context sensitive help" , "Help_ContextHelp" }) + + SELF:RegisterItem(IDM_StandardSDIMenu_Help_Using_Help_ID, ; + HyperLabel{ #HelpUsingHelp , "&Using Help" , "How to use help" , "Help_UsingHelp" }) + + SELF:RegisterItem(IDM_StandardSDIMenu_Help_About_ID, ; + HyperLabel{ #HelpAbout , "&About..." , "About application" , }) + + oTB := Toolbar{} + + oTB:ButtonStyle := TB_ICONONLY + oTB:Flat := TRUE + oTB:EnableBands(FALSE) + + oTB:AppendItem(IDT_OPEN , IDM_StandardSDIMenu_File_Open_ID) + oTB:AddTipText(IDT_OPEN , IDM_StandardSDIMenu_File_Open_ID , "Open File") + + oTB:AppendItem(IDT_SEPARATOR) + + oTB:AppendItem(IDT_CUT , IDM_StandardSDIMenu_Edit_Cut_ID) + oTB:AddTipText(IDT_CUT , IDM_StandardSDIMenu_Edit_Cut_ID , "Cut") + + oTB:AppendItem(IDT_COPY , IDM_StandardSDIMenu_Edit_Copy_ID) + oTB:AddTipText(IDT_COPY , IDM_StandardSDIMenu_Edit_Copy_ID , "Copy") + + oTB:AppendItem(IDT_PASTE , IDM_StandardSDIMenu_Edit_Paste_ID) + oTB:AddTipText(IDT_PASTE , IDM_StandardSDIMenu_Edit_Paste_ID , "Paste") + + oTB:AppendItem(IDT_SEPARATOR) + + oTB:AppendItem(IDT_STARTREC , IDM_StandardSDIMenu_Edit_Go_To_Top_ID) + oTB:AddTipText(IDT_STARTREC , IDM_StandardSDIMenu_Edit_Go_To_Top_ID , "Go Top") + + oTB:AppendItem(IDT_PREVREC , IDM_StandardSDIMenu_Edit_Previous_ID) + oTB:AddTipText(IDT_PREVREC , IDM_StandardSDIMenu_Edit_Previous_ID , "Previous Record") + + oTB:AppendItem(IDT_NEXTREC , IDM_StandardSDIMenu_Edit_Next_ID) + oTB:AddTipText(IDT_NEXTREC , IDM_StandardSDIMenu_Edit_Next_ID , "Next Record") + + oTB:AppendItem(IDT_ENDREC , IDM_StandardSDIMenu_Edit_Go_To_Bottom_ID) + oTB:AddTipText(IDT_ENDREC , IDM_StandardSDIMenu_Edit_Go_To_Bottom_ID , "Go Bottom") + + oTB:AppendItem(IDT_SEPARATOR) + + oTB:AppendItem(IDT_VFORM , IDM_StandardSDIMenu_View_Form_ID) + oTB:AddTipText(IDT_VFORM , IDM_StandardSDIMenu_View_Form_ID , "Form View") + + oTB:AppendItem(IDT_VGBROWSE , IDM_StandardSDIMenu_View_Table_ID) + oTB:AddTipText(IDT_VGBROWSE , IDM_StandardSDIMenu_View_Table_ID , "Browse View") + + oTB:AppendItem(IDT_SEPARATOR) + + oTB:AppendItem(IDT_HELP , IDM_StandardSDIMenu_Help_About_ID) + oTB:AddTipText(IDT_HELP , IDM_StandardSDIMenu_Help_About_ID , "Help") + + + SELF:ToolBar := oTB + SELF:Accelerator := StandardSDIMenu_Accelerator{ } + + SELF:PostInit() + + RETURN + +END CLASS +PARTIAL CLASS StandardSDIMenu_Accelerator INHERIT Accelerator + +CONSTRUCTOR() + SUPER( ResourceID { "StandardSDIMenu_Accelerator" , _GetInst( ) } ) +RETURN + + +END CLASS diff --git a/src/VisualStudio/ProjectSystemCPS.TestBed/VO/Standard SDI.IDI_STANDARDICON.rc b/src/VisualStudio/ProjectSystemCPS.TestBed/VO/Standard SDI.IDI_STANDARDICON.rc new file mode 100644 index 0000000000..aa3dfe446e --- /dev/null +++ b/src/VisualStudio/ProjectSystemCPS.TestBed/VO/Standard SDI.IDI_STANDARDICON.rc @@ -0,0 +1 @@ + 101 ICON "Resources\\XSharp.ICO" diff --git a/src/VisualStudio/ProjectSystemCPS.TestBed/VO/Standard SDI.Manifest.rc b/src/VisualStudio/ProjectSystemCPS.TestBed/VO/Standard SDI.Manifest.rc new file mode 100644 index 0000000000..c95c0e84da --- /dev/null +++ b/src/VisualStudio/ProjectSystemCPS.TestBed/VO/Standard SDI.Manifest.rc @@ -0,0 +1,4 @@ + 1 24 resources\cctl6.man + + + diff --git a/src/VisualStudio/ProjectSystemCPS.TestBed/VO/Standard SDI.prg b/src/VisualStudio/ProjectSystemCPS.TestBed/VO/Standard SDI.prg new file mode 100644 index 0000000000..e9912c77b6 --- /dev/null +++ b/src/VisualStudio/ProjectSystemCPS.TestBed/VO/Standard SDI.prg @@ -0,0 +1,118 @@ +#region DEFINES +define IDI_STANDARDICON := 101 +define IDI_VOICON := 102 +#endregion + +class StandardSDIWindow inherit DataWindow + protect oPrinter as PrintingDevice + protect oStdMenu as Menu + + +METHOD DoOpenFile(cFileName, lReadOnly) + LOCAL oTB AS TextBox + + IF (Len(cFileName) > 3) .AND. (Upper(Right(cFileName, 4)) == ".DBF") + IF (SELF:Menu != oStdMenu) + SELF:Menu := oStdMenu + SELF:ToolBar:PressItem(IDM_StandardSDIMenu_View_Form_ID, #MenuItemID) + ENDIF + + SELF:Use(DBServer{cFileName, , lReadOnly}) + SELF:Caption := "Browse Database: " + cFileName + ELSE + oTB := TextBox{SELF, "File Open Error", "Cannot open " + cFileName + " - Not a DBF file"} + oTB:Type := BUTTONOKAY + oTB:Beep:=TRUE + oTB:Show() + ENDIF +RETURN SELF + +METHOD Drop(oDragEvent) + IF File(oDragEvent:FileName(1)) + SELF:DoOpenFile(oDragEvent:FileName(1)) + ENDIF +RETURN SELF + +METHOD FileClose() + SELF:Use() +RETURN SELF + +METHOD FileExit() + SELF:EndWindow() +RETURN SELF + +METHOD FileOpen() + LOCAL oOD AS OpenDialog + LOCAL oTB AS TextBox + LOCAL retval AS SHORT + + IF(oAttachedServer == NULL_OBJECT) + (oOD := OpenDialog{SELF, "*.dbf"}):Show() + + IF !Empty(oOD:FileName) + SELF:DoOpenFile(oOD:FileName, oOD:ReadOnly) + ENDIF + ELSE + oTB := TextBox{SELF, "Warning", "A DBF file is already open!" + _CHR(10)+ "Do you want close it?"} + oTB:Type := BUTTONOKAYCANCEL + retval := oTB:Show() + IF retval == BOXREPLYCANCEL + RETURN SELF + ENDIF + IF retVal==BOXREPLYOKAY + (oOD := OpenDialog{SELF, "*.dbf"}):Show() + IF !Empty(oOD:FileName) + SELF:DoOpenFile(oOD:FileName, oOD:ReadOnly) + ENDIF + ENDIF + ENDIF +RETURN SELF + +METHOD FilePrint() + SELF:Print(oPrinter) +RETURN SELF + +METHOD FilePrinterSetup() + + oPrinter:Setup() + +RETURN SELF + +CONSTRUCTOR(oOwnerApp) + + SetDeleted(TRUE) + + SUPER(oOwnerApp) + + SELF:EnableDragDropClient() + + SELF:Icon := Icon{ResourceID{IDI_STANDARDICON, _GetInst()}} + SELF:Menu := EmptySDIMenu{SELF} + oStdMenu := StandardSDIMenu{SELF} + SELF:Caption := "VO SDI Application" + SELF:EnableStatusBar(TRUE) + SELF:StatusBar:DisplayTime(TRUE) + SELF:QuitOnClose := TRUE + + SELF:Size := Dimension{850,650} + + oPrinter := PrintingDevice{} + + RETURN SELF + + +method ViewForm() + self:ToolBar:UnPressItem(IDM_StandardSDIMenu_View_Table_ID) + self:ToolBar:PressItem(IDM_StandardSDIMenu_View_Form_ID) + + return super:ViewForm() + +method ViewTable() + self:ToolBar:UnPressItem(IDM_StandardSDIMenu_View_Form_ID) + self:ToolBar:PressItem(IDM_StandardSDIMenu_View_Table_ID) + + return super:ViewTable() + + + +END CLASS diff --git a/src/VisualStudio/ProjectSystemCPS.TestBed/VO/Start.prg b/src/VisualStudio/ProjectSystemCPS.TestBed/VO/Start.prg new file mode 100644 index 0000000000..340a15d66c --- /dev/null +++ b/src/VisualStudio/ProjectSystemCPS.TestBed/VO/Start.prg @@ -0,0 +1,24 @@ +[STAThread]; +FUNCTION Start() AS INT + LOCAL oXApp AS XApp + TRY + oXApp := XApp{} + oXApp:Start() + CATCH oException AS Exception + ErrorDialog(oException) + END TRY +RETURN 0 + +CLASS XApp INHERIT App +METHOD Start() + LOCAL oMainWindow AS StandardSDIWindow + + oMainWindow := StandardSDIWindow{SELF} + oMainWindow:Show(SHOWCENTERED) + + SELF:Exec() + +RETURN SELF + + +END CLASS diff --git a/src/VisualStudio/ProjectSystemCPS.TestBed/VO/VOTestBed.xsproj b/src/VisualStudio/ProjectSystemCPS.TestBed/VO/VOTestBed.xsproj new file mode 100644 index 0000000000..5e87d2fc5b --- /dev/null +++ b/src/VisualStudio/ProjectSystemCPS.TestBed/VO/VOTestBed.xsproj @@ -0,0 +1,52 @@ + + + + WinExe + net48 + x86 + VOTestBed + VO + true + true + true + true + true + true + true + true + true + true + true + true + true + false + false + true + false + true + true + false + $(NoWarn);9032;9043;618;162;165;649;9025 + + + + $(registry:HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\XSharpBV\XSharp@XSharpPath) + $(MSBuildProgramFiles32)\XSharp + $([MSBuild]::EnsureTrailingSlash('$(XSharpInstallDir)'))Assemblies\ + + + + + + + + + + + From 8ca7ca3c1b1e13b2712eebe7c01b81b651640850 Mon Sep 17 00:00:00 2001 From: Hansjoerg Petriffer Date: Tue, 29 Sep 2026 10:11:30 +0200 Subject: [PATCH 50/60] [VsIntegration] CPS project system: walk source files that change on disk Under CPS nobody re-read a source file that changed on disk outside the VS editor. The VO designers write the generated .prg directly to disk when it is not open in an editor (menu editor: SDI Menus.prg with the new menu IDs, window editor: Help About.prg with the new control), and the code model kept the old types until the next project walk (found in the VS test with VO/VOTestBed). MPFproj covers this in XSharpProjectNode.OnFileChanged (WalkFile(file, notify: true)). New SourceFileWatcher (one per project, created by the adapter): a FileSystemWatcher on the project folder including subfolders. Changes to source files of the project (adapter: in the file set and XFile.IsSource) are collected and walked after 500 ms, one walk per file for a burst of writes; notify: true refreshes the Task List. After a buffer overflow the whole project is walked again. Files outside the project folder (links) are not watched. The watcher also sees saves from the VS editor, so the editor save walk of ProjectFileSaveWatcher (IVsRunningDocTableEvents.OnAfterSave, 1803b79e2a) is removed to avoid a second walk per save. What remains of that file, setting the comment tokens for pure CPS solutions, is now CommentTokens.EnsureSet. Verified outside VS: 5 quick writes -> one callback 500 ms after the last, temp file + rename (editor save) -> callback, non-source file -> none, none after Dispose. Tested in VS: menu editor and window editor saves with the .prg closed -> "changed on disk, walking it again" 0.5 s later, new menu item and control in the code model and in completion; TODO saved in the editor -> Task List entry. Co-Authored-By: Claude Opus 5.5 --- .../LanguageService/CommentTokens.cs | 60 ++++++++ .../LanguageService/ProjectFileSaveWatcher.cs | 144 ------------------ .../LanguageService/SourceFileWatcher.cs | 136 +++++++++++++++++ .../LanguageService/XSharpProjectAdapter.cs | 39 ++++- 4 files changed, 232 insertions(+), 147 deletions(-) create mode 100644 src/VisualStudio/ProjectSystemCPS/LanguageService/CommentTokens.cs delete mode 100644 src/VisualStudio/ProjectSystemCPS/LanguageService/ProjectFileSaveWatcher.cs create mode 100644 src/VisualStudio/ProjectSystemCPS/LanguageService/SourceFileWatcher.cs diff --git a/src/VisualStudio/ProjectSystemCPS/LanguageService/CommentTokens.cs b/src/VisualStudio/ProjectSystemCPS/LanguageService/CommentTokens.cs new file mode 100644 index 0000000000..767eb14fc2 --- /dev/null +++ b/src/VisualStudio/ProjectSystemCPS/LanguageService/CommentTokens.cs @@ -0,0 +1,60 @@ +// +// Copyright (c) XSharp B.V. All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +// + +using System.Collections.Generic; +using Microsoft.VisualStudio; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; +using XSharp.Settings; +using XSharpModel; + +namespace XSharp.ProjectSystem.LanguageService +{ + /// + /// The comment tokens (TODO, HACK etc.) of the code model's parser. + /// + /// + /// Saved files are walked again by (it replaced the editor save watcher that lived + /// in this file), which refreshes the comment tasks in the Task List. + /// + internal static class CommentTokens + { + /// + /// Passes the comment tokens (Tools > Options > Environment > Task List) to the code model when nobody did yet. + /// + /// + /// The X# project package sets them when an MPFproj project opens and after the options dialog closes + /// (XSharpProjectPackage.SetCommentTokens). In a solution with only CPS projects the parser would otherwise + /// find no comment tasks at all. + /// + public static void EnsureSet() + { + ThreadHelper.ThrowIfNotOnUIThread(); + if (XSolution.CommentTokens.Count > 0 || + !(ServiceProvider.GlobalProvider.GetService(typeof(SVsTaskList)) is IVsCommentTaskInfo taskInfo) || + ErrorHandler.Failed(taskInfo.EnumTokens(out IVsEnumCommentTaskTokens enumTokens)) || enumTokens == null) + { + return; + } + var tokens = new List(); + var buffer = new IVsCommentTaskToken[1]; + while (enumTokens.Next(1, buffer, out uint fetched) == VSConstants.S_OK && fetched == 1) + { + var priority = new VSTASKPRIORITY[1]; + if (ErrorHandler.Succeeded(buffer[0].Text(out string text)) && !string.IsNullOrEmpty(text) && + ErrorHandler.Succeeded(buffer[0].Priority(priority))) + { + tokens.Add(new XCommentToken(text, (int)priority[0])); + } + } + if (tokens.Count > 0) + { + XSolution.SetCommentTokens(tokens); + XSettings.Information($"CommentTokens: {tokens.Count} comment tokens set"); + } + } + } +} diff --git a/src/VisualStudio/ProjectSystemCPS/LanguageService/ProjectFileSaveWatcher.cs b/src/VisualStudio/ProjectSystemCPS/LanguageService/ProjectFileSaveWatcher.cs deleted file mode 100644 index 2c0fcfcead..0000000000 --- a/src/VisualStudio/ProjectSystemCPS/LanguageService/ProjectFileSaveWatcher.cs +++ /dev/null @@ -1,144 +0,0 @@ -// -// Copyright (c) XSharp B.V. All Rights Reserved. -// Licensed under the Apache License, Version 2.0. -// See License.txt in the project root for license information. -// - -using System; -using System.Collections.Generic; -using System.Runtime.InteropServices; -using System.Threading.Tasks; -using Microsoft.VisualStudio; -using Microsoft.VisualStudio.Shell; -using Microsoft.VisualStudio.Shell.Interop; -using Microsoft.VisualStudio.Threading; -using XSharp.Settings; -using XSharpModel; - -namespace XSharp.ProjectSystem.LanguageService -{ - /// - /// Re-walks a source file of an X# CPS project after it was saved, so the code model (and with it the comment - /// tasks in the Task List) reflects the saved file. - /// - /// - /// MPFproj does this in XSharpProjectNode.OnFileChanged: WalkFile(file, notify: true), whose FileWalkComplete event - /// refreshes the Task List. Under CPS nothing re-walked a saved file: a new TODO only showed up after the next - /// project walk (e.g. after a design-time build), verified in VS. The walk runs on a background thread. - /// - internal sealed class ProjectFileSaveWatcher : IVsRunningDocTableEvents - { - private static ProjectFileSaveWatcher _instance; - - private readonly IVsRunningDocumentTable _rdt; - private readonly JoinableTaskFactory _joinableTaskFactory; - - private ProjectFileSaveWatcher(IVsRunningDocumentTable rdt, JoinableTaskFactory joinableTaskFactory) - { - _rdt = rdt; - _joinableTaskFactory = joinableTaskFactory; - } - - /// - /// Starts watching, once per session. Called when the first X# CPS project loads. - /// - public static void EnsureAdvised(JoinableTaskFactory joinableTaskFactory) - { - ThreadHelper.ThrowIfNotOnUIThread(); - if (_instance != null || - !(ServiceProvider.GlobalProvider.GetService(typeof(SVsRunningDocumentTable)) is IVsRunningDocumentTable rdt)) - { - return; - } - var instance = new ProjectFileSaveWatcher(rdt, joinableTaskFactory); - if (ErrorHandler.Succeeded(rdt.AdviseRunningDocTableEvents(instance, out uint _))) - { - // Process lifetime, like NewFormDesignerRedirect - _instance = instance; - } - } - - /// - /// Passes the comment tokens (Tools > Options > Environment > Task List) to the code model when nobody did yet. - /// - /// - /// The X# project package sets them when an MPFproj project opens and after the options dialog closes - /// (XSharpProjectPackage.SetCommentTokens). In a solution with only CPS projects the parser would otherwise - /// find no comment tasks at all. - /// - public static void EnsureCommentTokens() - { - ThreadHelper.ThrowIfNotOnUIThread(); - if (XSolution.CommentTokens.Count > 0 || - !(ServiceProvider.GlobalProvider.GetService(typeof(SVsTaskList)) is IVsCommentTaskInfo taskInfo) || - ErrorHandler.Failed(taskInfo.EnumTokens(out IVsEnumCommentTaskTokens enumTokens)) || enumTokens == null) - { - return; - } - var tokens = new List(); - var buffer = new IVsCommentTaskToken[1]; - while (enumTokens.Next(1, buffer, out uint fetched) == VSConstants.S_OK && fetched == 1) - { - var priority = new VSTASKPRIORITY[1]; - if (ErrorHandler.Succeeded(buffer[0].Text(out string text)) && !string.IsNullOrEmpty(text) && - ErrorHandler.Succeeded(buffer[0].Priority(priority))) - { - tokens.Add(new XCommentToken(text, (int)priority[0])); - } - } - if (tokens.Count > 0) - { - XSolution.SetCommentTokens(tokens); - XSettings.Information($"ProjectFileSaveWatcher: {tokens.Count} comment tokens set"); - } - } - - public int OnAfterSave(uint docCookie) - { - ThreadHelper.ThrowIfNotOnUIThread(); - try - { - if (ErrorHandler.Failed(_rdt.GetDocumentInfo(docCookie, out uint _, out uint _, out uint _, out string moniker, - out IVsHierarchy _, out uint _, out IntPtr docData))) - { - return VSConstants.S_OK; - } - if (docData != IntPtr.Zero) - { - Marshal.Release(docData); - } - var file = string.IsNullOrEmpty(moniker) ? null : XSolution.FindFile(moniker); - var project = file?.Project; - if (project == null || !(project.ProjectNode is XSharpProjectAdapter) || !file.IsSource) - { - return VSConstants.S_OK; - } - _joinableTaskFactory.RunAsync(async () => - { - await TaskScheduler.Default; - try - { - project.WalkFile(file, true); - } - catch (Exception e) - { - XSettings.Exception(e); - } - }).FileAndForget("XSharp/ProjectSystemCPS/ProjectFileSaveWatcher"); - } - catch (Exception e) - { - XSettings.Exception(e); - } - return VSConstants.S_OK; - } - - #region IVsRunningDocTableEvents members that are not used - public int OnAfterFirstDocumentLock(uint docCookie, uint dwRDTLockType, uint dwReadLocksRemaining, uint dwEditLocksRemaining) => VSConstants.S_OK; - public int OnBeforeLastDocumentUnlock(uint docCookie, uint dwRDTLockType, uint dwReadLocksRemaining, uint dwEditLocksRemaining) => VSConstants.S_OK; - public int OnAfterAttributeChange(uint docCookie, uint grfAttribs) => VSConstants.S_OK; - public int OnBeforeDocumentWindowShow(uint docCookie, int fFirstShow, IVsWindowFrame pFrame) => VSConstants.S_OK; - public int OnAfterDocumentWindowHide(uint docCookie, IVsWindowFrame pFrame) => VSConstants.S_OK; - #endregion - } -} diff --git a/src/VisualStudio/ProjectSystemCPS/LanguageService/SourceFileWatcher.cs b/src/VisualStudio/ProjectSystemCPS/LanguageService/SourceFileWatcher.cs new file mode 100644 index 0000000000..c51a5ddb30 --- /dev/null +++ b/src/VisualStudio/ProjectSystemCPS/LanguageService/SourceFileWatcher.cs @@ -0,0 +1,136 @@ +// +// Copyright (c) XSharp B.V. All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +// + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using XSharp.Settings; + +namespace XSharp.ProjectSystem.LanguageService +{ + /// + /// Reports source files of an X# CPS project that changed on disk, so the adapter can walk them again. + /// + /// + /// MPFproj does this in XSharpProjectNode.OnFileChanged (file change notifications for its items, then + /// WalkFile(file, notify: true)). Under CPS nobody re-read a file that changed on disk: the VO designers write the + /// generated .prg directly to disk when it is not open in an editor, and the code model kept the old types until + /// the next project walk (verified in VS). The same applies to saves from the VS editor, external editors and + /// source control operations, which this watcher covers as well. + /// A FileSystemWatcher on the project folder (including subfolders); the adapter decides which paths are source + /// files of the project. Changes are collected and reported after , so a burst of writes + /// (a designer save writes several files, editors write via temporary files) gives one walk per file. + /// Files outside the project folder (links) are not watched. + /// + internal sealed class SourceFileWatcher : IDisposable + { + private static readonly TimeSpan Delay = TimeSpan.FromMilliseconds(500); + + private readonly Func isSourceFile; + private readonly Action fileChanged; + private readonly Action changesLost; + private readonly FileSystemWatcher watcher; + private readonly Timer timer; + private readonly object gate = new object(); + private readonly HashSet pending = new HashSet(StringComparer.OrdinalIgnoreCase); + private bool disposed; + + /// The project folder. + /// True for the full path of a source file of the project (called on the watcher's thread). + /// Called with the full path of each changed source file, on a background thread. + /// Called when changes may have been missed (the watcher's buffer overflowed). + public SourceFileWatcher(string folder, Func isSourceFile, Action fileChanged, Action changesLost) + { + this.isSourceFile = isSourceFile; + this.fileChanged = fileChanged; + this.changesLost = changesLost; + timer = new Timer(OnTimer, null, Timeout.Infinite, Timeout.Infinite); + watcher = new FileSystemWatcher(folder) + { + IncludeSubdirectories = true, + NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite | NotifyFilters.Size, + InternalBufferSize = 64 * 1024, + }; + watcher.Changed += (sender, e) => Queue(e.FullPath); + watcher.Created += (sender, e) => Queue(e.FullPath); + watcher.Renamed += (sender, e) => Queue(e.FullPath); + watcher.Error += OnError; + watcher.EnableRaisingEvents = true; + } + + private void Queue(string path) + { + try + { + if (string.IsNullOrEmpty(path) || !isSourceFile(path)) + return; + lock (gate) + { + if (disposed) + return; + pending.Add(path); + timer.Change(Delay, Timeout.InfiniteTimeSpan); + } + } + catch (Exception e) + { + XSettings.Exception(e); + } + } + + private void OnTimer(object state) + { + string[] files; + lock (gate) + { + if (disposed) + return; + files = pending.ToArray(); + pending.Clear(); + } + foreach (var file in files) + { + try + { + fileChanged(file); + } + catch (Exception e) + { + XSettings.Exception(e); + } + } + } + + private void OnError(object sender, ErrorEventArgs e) + { + XSettings.Information("SourceFileWatcher: " + e.GetException()?.Message + ", walking the project again"); + try + { + changesLost(); + } + catch (Exception ex) + { + XSettings.Exception(ex); + } + } + + public void Dispose() + { + lock (gate) + { + if (disposed) + return; + disposed = true; + pending.Clear(); + } + watcher.EnableRaisingEvents = false; + watcher.Dispose(); + timer.Dispose(); + } + } +} diff --git a/src/VisualStudio/ProjectSystemCPS/LanguageService/XSharpProjectAdapter.cs b/src/VisualStudio/ProjectSystemCPS/LanguageService/XSharpProjectAdapter.cs index cbf623736d..c77c6c0939 100644 --- a/src/VisualStudio/ProjectSystemCPS/LanguageService/XSharpProjectAdapter.cs +++ b/src/VisualStudio/ProjectSystemCPS/LanguageService/XSharpProjectAdapter.cs @@ -74,6 +74,7 @@ internal sealed class XSharpProjectAdapter : IProjectDynamicLoadComponent, IXSha private List links; private IntellisenseErrorList errorList; private CommentTaskList taskList; + private SourceFileWatcher sourceWatcher; private XParseOptions parseOptions = XParseOptions.Default; private bool parseOptionsFromCommandLine; private string rootNamespace = ""; @@ -111,7 +112,7 @@ public async Task LoadAsync() // X# database, verified in VS). await threading.JoinableTaskFactory.SwitchToMainThreadAsync(); // Before the first walk, so the parser finds the comment tasks (TODO etc.) - ProjectFileSaveWatcher.EnsureCommentTokens(); + CommentTokens.EnsureSet(); lock (modelGate) { if (model != null) @@ -127,8 +128,9 @@ public async Task LoadAsync() XSettings.Information("XSharpProjectAdapter: created code model for " + project.FullPath); // Forms added with "Add New Item" open in the shadow designer instead of the code editor ShadowDesigner.NewFormDesignerRedirect.EnsureAdvised(threading.JoinableTaskFactory); - // Saved files are walked again (comment tasks), like XSharpProjectNode.OnFileChanged - ProjectFileSaveWatcher.EnsureAdvised(threading.JoinableTaskFactory); + // Source files that change on disk (editor saves, VO designers, external tools) are walked again, like + // XSharpProjectNode.OnFileChanged + sourceWatcher = new SourceFileWatcher(ProjectFolder, IsSourceFileOfProject, WalkChangedFile, WalkProject); errorList = new IntellisenseErrorList(threading.JoinableTaskFactory, DisplayName, errors.GetAll); errors.Changed = errorList.Update; taskList = new CommentTaskList(threading.JoinableTaskFactory, DisplayName); @@ -176,6 +178,8 @@ public Task UnloadAsync() files.Clear(); } errors.Changed = null; + sourceWatcher?.Dispose(); + sourceWatcher = null; errorList?.Dispose(); errorList = null; taskList?.Dispose(); @@ -415,6 +419,35 @@ private void RefreshCommentTasks() } } + /// + /// For (its thread): is a source file of this project. + /// + private bool IsSourceFileOfProject(string path) + { + XProject current; + lock (gate) + { + current = model; + if (current == null || !files.Contains(path)) + return false; + } + return current.FindXFile(path)?.IsSource == true; + } + + /// + /// For (background thread): walk a changed source file again. notify: true + /// raises FileWalkComplete, which refreshes the Task List. + /// + private void WalkChangedFile(string path) + { + var current = model; + var file = current?.FindXFile(path); + if (file == null || !file.IsSource) + return; + XSettings.Information("XSharpProjectAdapter: " + path + " changed on disk, walking it again"); + current.WalkFile(file, true); + } + private void WalkProject() { var current = model; From 438f7d3c3589d423f2d7ccfaa51ec3a02a2bbee4 Mon Sep 17 00:00:00 2001 From: Hansjoerg Petriffer Date: Tue, 29 Sep 2026 10:35:55 +0200 Subject: [PATCH 51/60] [VsIntegration] CPS test bed: DBServer and FieldSpec for the VO designers VO\VOTestBed gets a DBServer (Customer.prg + Customer.Customer.xsdbs, class Customer on Customer.dbf) and FieldSpecs (Fieldspecs.prg + Fieldspecs.FieldSpecs.xsfs), created from the X# item templates VODBServer and VOFieldSpec (ProjectPackage/Templates/ProjectItems/VO) with $safeitemrootname$ replaced; Fieldspecs.prg is the template's empty stub, the FieldSpec editor fills it. The X# SDK globs add the VO binaries as VOBinary with DependentUpon. Tested in VS: the FieldSpec editor regenerates Fieldspecs.prg, the DBServer editor regenerates Customer.prg and creates Customer.FieldSpecs.xsfs (added to the code model through AddFileNode and the glob); SourceFileWatcher walks the .prg files 0.5 s after the save; the generated code compiles; the project file stays unchanged. The committed files are the template state. Co-Authored-By: Claude Opus 5.5 --- .../VO/Customer.Customer.xsdbs | 21 ++ .../ProjectSystemCPS.TestBed/VO/Customer.prg | 211 ++++++++++++++++++ .../VO/Fieldspecs.FieldSpecs.xsfs | 5 + .../VO/Fieldspecs.prg | 1 + 4 files changed, 238 insertions(+) create mode 100644 src/VisualStudio/ProjectSystemCPS.TestBed/VO/Customer.Customer.xsdbs create mode 100644 src/VisualStudio/ProjectSystemCPS.TestBed/VO/Customer.prg create mode 100644 src/VisualStudio/ProjectSystemCPS.TestBed/VO/Fieldspecs.FieldSpecs.xsfs create mode 100644 src/VisualStudio/ProjectSystemCPS.TestBed/VO/Fieldspecs.prg diff --git a/src/VisualStudio/ProjectSystemCPS.TestBed/VO/Customer.Customer.xsdbs b/src/VisualStudio/ProjectSystemCPS.TestBed/VO/Customer.Customer.xsdbs new file mode 100644 index 0000000000..14edf1d256 --- /dev/null +++ b/src/VisualStudio/ProjectSystemCPS.TestBed/VO/Customer.Customer.xsdbs @@ -0,0 +1,21 @@ + + + + Customer + Customer.dbf + Customer + DBSERVER + Customer + Customer + + + + + DBFNTX + 0 + No + + + + + \ No newline at end of file diff --git a/src/VisualStudio/ProjectSystemCPS.TestBed/VO/Customer.prg b/src/VisualStudio/ProjectSystemCPS.TestBed/VO/Customer.prg new file mode 100644 index 0000000000..6b5ff7fdc3 --- /dev/null +++ b/src/VisualStudio/ProjectSystemCPS.TestBed/VO/Customer.prg @@ -0,0 +1,211 @@ +#ifndef __XSHARP_RT__ +#include "VOSystemClasses.vh" +#include "VOSystemLibrary.vh" +#endif + +CLASS Customer INHERIT DBSERVER + INSTANCE cDBFPath := "" AS STRING + INSTANCE cName := "Customer" AS STRING + INSTANCE xDriver := "DBFNTX" AS USUAL + INSTANCE lReadOnlyMode:= FALSE AS LOGIC + INSTANCE lSharedMode := NIL AS USUAL + INSTANCE nOrder := 0 AS INT + + +CONSTRUCTOR(cDBF, lShare, lRO, xRdd) + LOCAL oFS AS FILESPEC + LOCAL i AS DWORD + LOCAL nFields AS DWORD + LOCAL aFieldDesc AS ARRAY + LOCAL aIndex AS ARRAY + LOCAL nIndexCount AS DWORD + LOCAL oFSIndex AS FILESPEC + LOCAL nPos AS DWORD + LOCAL lTemp AS LOGIC + LOCAL oFSTemp AS FILESPEC + + + IF IsLogic(lShare) + SELF:lSharedMode := lShare + ELSE + IF !IsLogic(SELF:lSharedMode) + SELF:lSharedMode := !SetExclusive() + ENDIF + ENDIF + + IF IsLogic(lRO) + SELF:lReadOnlyMode := lRO + ENDIF + + IF IsString(xRdd) .OR. IsArray(xRdd) + SELF:xDriver := xRdd + ENDIF + + SELF:PreInit() + + IF IsString(cDBF) + // UH 01/18/2000 + oFSTemp := FileSpec{SELF:cDBFPath + SELF:cName} + oFS := FileSpec{cDBF} + + IF SLen(oFS:Drive) = 0 + oFS:Drive := CurDrive() + ENDIF + IF SLen(oFS:Path) = 0 + oFS:Path := "\" + CurDir() + ENDIF + + IF SLen(oFS:FileName) = 0 + oFS:Filename := SELF:cName + ENDIF + + IF oFS:FullPath == oFSTemp:Fullpath + lTemp := .T. + ELSE + IF Left(cDBF, 2) =="\\" // Unc path, for example \\Server\Share\FileName.DBF + SELF:cDBFPath := oFS:Path + ELSE + SELF:cDBFPath := oFS:Drive + oFS:Path + ENDIF + SELF:cName := oFS:FileName + oFS:Extension + oFS := FileSpec{SELF:cDBFPath + SELF:cName} + ENDIF + ELSE + oFS := FileSpec{SELF:cName} + oFS:Path := SELF:cDBFPath + ENDIF + + + SUPER(oFS, SELF:lSharedMode, SELF:lReadOnlyMode , SELF:xDriver ) + + oHyperLabel := HyperLabel{#Customer, "Customer", "Customer", "Customer"} + + IF oHLStatus = NIL + nFields := ALen(aFieldDesc := SELF:FieldDesc) + FOR i:=1 UPTO nFields + nPos := SELF:FieldPos( aFieldDesc[i][DBC_NAME] ) + + SELF:SetDataField( nPos,; + DataField{aFieldDesc[i][DBC_SYMBOL],aFieldDesc[i][DBC_FIELDSPEC]} ) + + IF String2Symbol(aFieldDesc[i][DBC_NAME]) != aFieldDesc[i][DBC_SYMBOL] + SELF:FieldInfo(DBS_ALIAS, nPos, Symbol2String(aFieldDesc[i][DBC_SYMBOL]) ) + ENDIF + NEXT + + nIndexCount := ALen(aIndex:=SELF:IndexList) + + FOR i:=1 UPTO nIndexCount + oFSIndex := FileSpec{ aIndex[i][DBC_INDEXNAME] } + oFSIndex:Path := SELF:cDBFPath + + IF lTemp .AND. !Empty( aIndex[i][DBC_INDEXPATH] ) + oFSIndex:Path := aIndex[i][DBC_INDEXPATH] + ENDIF + + IF oFSIndex:Find() + lTemp := SELF:SetIndex( oFSIndex ) + ENDIF + NEXT + + // UH 01/18/2000 + // SELF:nOrder > 0 + IF lTemp .AND. SELF:nOrder > 0 + SELF:SetOrder(SELF:nOrder) + ENDIF + + SELF:GoTop() + ENDIF + + SELF:PostInit() + + RETURN + + +OVERRIDE ACCESS FieldDesc + // + // Describes all fields selected by DBServer-Editor + // + LOCAL aRet AS ARRAY + LOCAL nFields AS DWORD + + nFields := 0 + + IF nFields > 0 + aRet := ArrayCreate(nFields) + + // + // The following code creates an array of field + // descriptors with these items for each + // selected field: + // + // { , , } + // + // Use following predefined constants to access + // each subarray: + // + // DBC_SYMBOL + // DBC_NAME + // DBC_FIELDSPEC + // + + ELSE + aRet := {} + ENDIF + + + RETURN aRet + + +OVERRIDE ACCESS IndexList + // + // Describes all index files created or selected + // by DBServer-Editor + // + LOCAL aRet AS ARRAY + LOCAL nIndexCount AS DWORD + + nIndexCount := 0 + + IF nIndexCount > 0 + aRet := ArrayCreate(nIndexCount) + + // + // The following code creates an array of index + // file descriptors with these items for each + // selected index file: + // + // { , , } + // + // Use following predefined constants to access + // each subarray: + // + // DBC_INDEXNAME + // DBC_INDEXPATH + // DBC_ORDERS + // + // Array contains an array of + // order descriptors with these items for each + // order: + // + // { , , , , } + // + // Use following predefined constants to access + // aOrder subarrays: + // + // DBC_TAGNAME + // DBC_DUPLICATE + // DBC_ASCENDING + // DBC_KEYEXP + // DBC_FOREXP + // + + + ELSE + aRet := {} + ENDIF + + RETURN aRet + + +END CLASS diff --git a/src/VisualStudio/ProjectSystemCPS.TestBed/VO/Fieldspecs.FieldSpecs.xsfs b/src/VisualStudio/ProjectSystemCPS.TestBed/VO/Fieldspecs.FieldSpecs.xsfs new file mode 100644 index 0000000000..4f672202fc --- /dev/null +++ b/src/VisualStudio/ProjectSystemCPS.TestBed/VO/Fieldspecs.FieldSpecs.xsfs @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/VisualStudio/ProjectSystemCPS.TestBed/VO/Fieldspecs.prg b/src/VisualStudio/ProjectSystemCPS.TestBed/VO/Fieldspecs.prg new file mode 100644 index 0000000000..5f282702bb --- /dev/null +++ b/src/VisualStudio/ProjectSystemCPS.TestBed/VO/Fieldspecs.prg @@ -0,0 +1 @@ + \ No newline at end of file From 5ec34de0f26282d8590f44a2a3f1349412505ff8 Mon Sep 17 00:00:00 2001 From: Hansjoerg Petriffer Date: Tue, 29 Sep 2026 12:11:59 +0200 Subject: [PATCH 52/60] [CodeDomProvider] Write static field references on parsed types with '.' GenerateFieldReferenceExpression used the static selector only when the target was exactly a CodeTypeReferenceExpression. The X# CodeDOM parser creates the subclass XCodeTypeReferenceExpression, so code that was parsed and generated again got a colon: "Add .designer file" wrote "System.Windows.Forms.AutoScaleMode:Font", which the compiler rejects (XS9114) and the shadow designer cannot convert. GeneratePropertyReference- Expression already tested with "is"; now both do. Checked outside VS with the dev build (DEVPATH, the GAC copy of 3.0.2 still writes the colon): an XCodeTypeReferenceExpression target gives "AutoScaleMode.Font", SELF:button1 keeps the colon. Co-Authored-By: Claude Opus 5.5 --- src/VisualStudio/CodeDomProvider/XSharpCodeGenerator.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/VisualStudio/CodeDomProvider/XSharpCodeGenerator.cs b/src/VisualStudio/CodeDomProvider/XSharpCodeGenerator.cs index 0cac38dc6a..508b460ec4 100644 --- a/src/VisualStudio/CodeDomProvider/XSharpCodeGenerator.cs +++ b/src/VisualStudio/CodeDomProvider/XSharpCodeGenerator.cs @@ -809,7 +809,8 @@ protected override void GenerateFieldReferenceExpression(CodeFieldReferenceExpre { this.GenerateExpression(e.TargetObject); // If we have a Type, we must use a dot as a selector - if (e.TargetObject.GetType() == typeof(CodeTypeReferenceExpression)) + // (also for XCodeTypeReferenceExpression, which the parser creates, like in GeneratePropertyReferenceExpression) + if (e.TargetObject is CodeTypeReferenceExpression) { base.Output.Write(this.staticSelector); } From c54a1b9b56ed1a10cea318a1f0570a8c635ea882 Mon Sep 17 00:00:00 2001 From: Hansjoerg Petriffer Date: Tue, 29 Sep 2026 12:12:23 +0200 Subject: [PATCH 53/60] [VsIntegration] CPS project system: form icon for generated form files The tree properties provider found the base class of a form with "CLASS name INHERIT base" on one line. The X# CodeDOM generator writes the declaration with a line continuation ("CLASS Form1 ;" and "INHERIT ..." on the next line), so a form lost its icon as soon as the generator wrote its main file: "Add .designer file", or the shadow designer adding an event handler. The regex now allows the continuation. Co-Authored-By: Claude Opus 5.5 --- .../Imaging/XSharpProjectTreePropertiesProvider.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/VisualStudio/ProjectSystemCPS/Imaging/XSharpProjectTreePropertiesProvider.cs b/src/VisualStudio/ProjectSystemCPS/Imaging/XSharpProjectTreePropertiesProvider.cs index 0d9c42269c..c643c496d9 100644 --- a/src/VisualStudio/ProjectSystemCPS/Imaging/XSharpProjectTreePropertiesProvider.cs +++ b/src/VisualStudio/ProjectSystemCPS/Imaging/XSharpProjectTreePropertiesProvider.cs @@ -77,8 +77,10 @@ internal sealed class XSharpProjectTreePropertiesProvider : IProjectTreeProperti private static readonly ConcurrentDictionary Names)> designerFilesByFolder = new ConcurrentDictionary)>(StringComparer.OrdinalIgnoreCase); + // The X# CodeDOM generator (form designers, "Add .designer file") writes the declaration over two lines, with a + // line continuation: "CLASS Form1 ;" and "INHERIT System.Windows.Forms.Form" private static readonly Regex BaseClassRegex = - new Regex(@"\bCLASS\s+\S+\s+INHERIT\s+([A-Za-z_][\w.]*)", RegexOptions.IgnoreCase | RegexOptions.Compiled); + new Regex(@"\bCLASS\s+[^\s;]+[\s;]+INHERIT\s+([A-Za-z_][\w.]*)", RegexOptions.IgnoreCase | RegexOptions.Compiled); private readonly UnconfiguredProject project; From 52d9031bb196a31a0b293c9ce26b8b2dcb65ce15 Mon Sep 17 00:00:00 2001 From: Hansjoerg Petriffer Date: Tue, 29 Sep 2026 12:12:38 +0200 Subject: [PATCH 54/60] [VsIntegration] CPS project system: update the form icon when a designer file is added CPS calls the tree properties providers only for the nodes that changed in a tree update. "Add .designer file" adds a .Designer.prg below an existing .prg: that adds a child node but leaves the node of the .prg unchanged, so its form icon only appeared after the project was loaded again. When the list of properties providers changes, CPS calculates the properties of all nodes again (PhysicalProjectTreeProvider.OnProjectTreePropertiesProviders- Changed, providers compared by instance). New XSharpProjectTreePropertiesProviderSource (IProjectTreePropertiesProvider- DataSource, the pattern of the managed project system's TreeItemOrder- PropertyProviderSource) supplies the provider, which is no longer a MEF export itself. It follows the source items and publishes a new provider only when a .Designer.prg was added or removed. Its values carry their own version, not the versions of the source items subscription: the tree input synchronizes its sources by their data source versions, and with the source item versions the tree was never published and VS hung while restoring the open documents (seen in the experimental instance, stacks read with cdb). The first provider is published right away. Tested in VS: the icon changes right after "Add .designer file" and after deleting the designer file; the solution loads normally. Co-Authored-By: Claude Opus 5.5 --- .../XSharpProjectTreePropertiesProvider.cs | 13 +-- ...harpProjectTreePropertiesProviderSource.cs | 103 ++++++++++++++++++ 2 files changed, 109 insertions(+), 7 deletions(-) create mode 100644 src/VisualStudio/ProjectSystemCPS/Imaging/XSharpProjectTreePropertiesProviderSource.cs diff --git a/src/VisualStudio/ProjectSystemCPS/Imaging/XSharpProjectTreePropertiesProvider.cs b/src/VisualStudio/ProjectSystemCPS/Imaging/XSharpProjectTreePropertiesProvider.cs index c643c496d9..12e157136f 100644 --- a/src/VisualStudio/ProjectSystemCPS/Imaging/XSharpProjectTreePropertiesProvider.cs +++ b/src/VisualStudio/ProjectSystemCPS/Imaging/XSharpProjectTreePropertiesProvider.cs @@ -28,10 +28,9 @@ namespace XSharp.ProjectSystem.Imaging /// A .prg is a form/user control when its SubType says so, or -- SDK-style projects rarely have a SubType -- /// when it has a matching .Designer.prg and its class inherits from Form/UserControl (same inference as the /// MPFproj SDK project node). + /// Not a MEF export: supplies it, so that the icons of + /// all nodes are calculated again when a .Designer.prg is added or removed. /// - [Export(typeof(IProjectTreePropertiesProvider))] - [AppliesTo(XSharpCapabilities.XSharpCps)] - [Order(1000)] internal sealed class XSharpProjectTreePropertiesProvider : IProjectTreePropertiesProvider { private static readonly ProjectImageMoniker WindowsForm = Known(KnownImageIds.WindowsForm); @@ -57,9 +56,10 @@ internal sealed class XSharpProjectTreePropertiesProvider : IProjectTreeProperti { ".vnrep", Known(KnownImageIds.Report) }, }; - // This provider runs synchronously on the CPS tree thread for every node, on every tree update, so it keeps - // file system access low: the designer files are looked up in a cached list per folder, and a form's file is - // only read until its CLASS declaration. Both caches are cleared when they grow beyond MaxCacheEntries. + // This provider runs synchronously on the CPS tree thread for the nodes of each tree update (all nodes when + // the provider instance changes), so it keeps file system access low: the designer files are looked up in a + // cached list per folder, and a form's file is only read until its CLASS declaration. Both caches are + // cleared when they grow beyond MaxCacheEntries. private const int MaxCacheEntries = 1000; // A folder listing is reused for this long, then again as long as the folder's timestamp is unchanged // (on NTFS it changes when a file in it is created, deleted or renamed). Short: it only has to cover the burst @@ -84,7 +84,6 @@ internal sealed class XSharpProjectTreePropertiesProvider : IProjectTreeProperti private readonly UnconfiguredProject project; - [ImportingConstructor] public XSharpProjectTreePropertiesProvider(UnconfiguredProject project) { this.project = project; diff --git a/src/VisualStudio/ProjectSystemCPS/Imaging/XSharpProjectTreePropertiesProviderSource.cs b/src/VisualStudio/ProjectSystemCPS/Imaging/XSharpProjectTreePropertiesProviderSource.cs new file mode 100644 index 0000000000..fcf230322f --- /dev/null +++ b/src/VisualStudio/ProjectSystemCPS/Imaging/XSharpProjectTreePropertiesProviderSource.cs @@ -0,0 +1,103 @@ +// +// Copyright (c) XSharp B.V. All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +// + +using System; +using System.Collections.Immutable; +using System.ComponentModel.Composition; +using System.Threading.Tasks.Dataflow; +using Microsoft.VisualStudio.ProjectSystem; +using XSharp.Settings; + +namespace XSharp.ProjectSystem.Imaging +{ + /// + /// Supplies to the project tree, and a new instance of it when + /// the set of .Designer.prg files of the project changes. + /// + /// + /// CPS calls the property providers only for the nodes that changed in a tree update. "Add .designer file" adds a + /// .Designer.prg below an existing .prg: that adds a child node but leaves the node of the .prg unchanged, so its + /// form icon only appeared after the project was loaded again. When the list of property providers changes, CPS + /// calculates the properties of all nodes again (PhysicalProjectTreeProvider.OnProjectTreePropertiesProvidersChanged), + /// comparing the providers by instance. So this data source publishes a new provider only when a .Designer.prg was + /// added or removed. + /// The values carry their own version, not the versions of the source items subscription: the tree input + /// synchronizes its sources by their data source versions (SyncLinkTo), and with the versions of the source items + /// it waited for ever -- the tree was never published and VS hung while restoring the open documents (verified in + /// VS). The first provider is published right away, the tree waits for one from every source. + /// + [Export(typeof(IProjectTreePropertiesProviderDataSource))] + [AppliesTo(XSharpCapabilities.XSharpCps)] + [Order(1000)] + internal sealed class XSharpProjectTreePropertiesProviderSource : ChainedProjectValueDataSourceBase, IProjectTreePropertiesProviderDataSource + { + private static readonly NamedIdentity VersionKey = new NamedIdentity("XSharpProjectTreePropertiesProvider"); + + private readonly UnconfiguredProject project; + private readonly IActiveConfiguredProjectSubscriptionService subscriptions; + // Only used by the action block, which handles one update at a time (and before it is linked) + private long version; + private ImmutableHashSet designerFiles; + + [ImportingConstructor] + public XSharpProjectTreePropertiesProviderSource(UnconfiguredProject project, IActiveConfiguredProjectSubscriptionService subscriptions) + : base(project, synchronousDisposal: false, registerDataSource: false) + { + this.project = project; + this.subscriptions = subscriptions; + } + + protected override IDisposable LinkExternalInput(ITargetBlock> targetBlock) + { + targetBlock.Post(CreateValue()); + var action = DataflowBlockSlim.CreateActionBlock>(update => + { + if (DesignerFilesChanged(update.Value)) + targetBlock.Post(CreateValue()); + }); + // Like the adapter: the source item rules are dynamic, their names come from SourceItemRuleNamesSource + return subscriptions.SourceItemsRuleSource.SourceBlock.LinkTo(action, subscriptions.SourceItemRuleNamesSource.SourceBlock, + new DataflowLinkOptions { PropagateCompletion = true }); + } + + private IProjectVersionedValue CreateValue() + { + version++; + return new ProjectVersionedValue(new XSharpProjectTreePropertiesProvider(project), + ImmutableDictionary.Empty.Add(VersionKey, version)); + } + + /// + /// True when a .Designer.prg was added or removed since the last update. The first update only records them: + /// the first provider was applied to all nodes anyway. + /// + private bool DesignerFilesChanged(IProjectSubscriptionUpdate update) + { + try + { + var builder = ImmutableHashSet.CreateBuilder(StringComparer.OrdinalIgnoreCase); + foreach (var rule in update.CurrentState.Values) + { + foreach (var item in rule.Items.Keys) + { + if (item.EndsWith(".designer.prg", StringComparison.OrdinalIgnoreCase)) + builder.Add(item); + } + } + var current = builder.ToImmutable(); + var previous = designerFiles; + designerFiles = current; + return previous != null && !current.SetEquals(previous); + } + catch (Exception e) + { + // Keep the provider: a failure must never break the tree + XSettings.Exception(e); + return false; + } + } + } +} From 2ba79abf798277936f4c2cb7d83aced4842c671e Mon Sep 17 00:00:00 2001 From: Hansjoerg Petriffer Date: Tue, 29 Sep 2026 12:12:58 +0200 Subject: [PATCH 55/60] [VsIntegration] Add .designer file for X# projects loaded by CPS The command was hidden for CPS projects: it generated the designer file from the MPFproj file node (XSharpFileNode) and cast the project node to XSharpProjectNode. - Visible for .prg files of X# projects only (before, the MPFproj check also kept it away from C# forms). SDK-style projects rarely have a SubType, so for CPS projects the base classes of the first class in the file are followed through the code model, the same rule as XSharpFileNode.DetermineSubType (TypeNameToSubtype is now internal). Also hidden when a .designer.prg exists next to the file without being nested below it; the command would have overwritten it. - VSXSharpCodeDomProvider has a second constructor with the code model project and the file path; without a file node it writes into an open editor buffer by path. The CPS project system needs no AddFileNode: the SDK globs include the new file and XSharp.SDK.Props nests it. - The form is parsed first; the files are only created when the form class with InitializeComponent was found. Right after the solution was opened the references are not loaded yet and the parser cannot recognize it (the command left an empty designer file behind and failed silently). Now a message says to try again later. Exceptions are shown instead of only going to the activity log. Tested in VS with a single-file form in the WinForms test project: the designer file is created and nested, the form keeps its icon, the designer opens it (after correcting "AutoScaleMode:Font", written by the GAC copy of the CodeDOM generator without 5ec34de0f2), and the project builds. Co-Authored-By: Claude Opus 5.5 --- .../VSXsharpCodeDomProvider.cs | 43 +++++-- .../Commands/CommandAddDesignerFile.cs | 117 ++++++++++++++---- .../ProjectPackage/Nodes/XSharpFileNode.cs | 2 +- 3 files changed, 126 insertions(+), 36 deletions(-) diff --git a/src/VisualStudio/ProjectPackage/CodeDomProvider/VSXsharpCodeDomProvider.cs b/src/VisualStudio/ProjectPackage/CodeDomProvider/VSXsharpCodeDomProvider.cs index 00f32658f4..c5c80746fb 100644 --- a/src/VisualStudio/ProjectPackage/CodeDomProvider/VSXsharpCodeDomProvider.cs +++ b/src/VisualStudio/ProjectPackage/CodeDomProvider/VSXsharpCodeDomProvider.cs @@ -14,6 +14,7 @@ using Microsoft; using Microsoft.VisualStudio.Project; using XSharp.Settings; +using XSharpModel; namespace XSharp.Project { @@ -30,6 +31,8 @@ public XDocDataTextReader(DocData docData, CodeTypeDeclaration classname) :base( public class VSXSharpCodeDomProvider : XSharp.CodeDom.XSharpCodeDomProvider { private XSharpFileNode _fileNode; + // Full path of the file when there is no MPFproj file node (see the second constructor) + private string _filePath; // The parameterless constructor is called by the WPF designer ? @@ -40,22 +43,34 @@ public VSXSharpCodeDomProvider(XSharpFileNode fileNode) _projectNode = projectNode.ProjectModel; } + /// + /// For a file of a project without MPFproj file nodes (an X# project loaded by the CPS project system): + /// the code model project of the file and its full path. + /// + public VSXSharpCodeDomProvider(XProject project, string filePath) + { + _projectNode = project; + _filePath = filePath; + } + #region helper functions private bool IsFormSubType { - get { return _fileNode.HasDesigner; } + get { return _fileNode != null && _fileNode.HasDesigner; } } private string GetFilePath() { + if (_fileNode == null) + return _filePath; return Path.Combine(Path.GetDirectoryName(_fileNode.GetMkDocument()), _fileNode.FileName); } private string GetDesignerFilePath() { - return Path.Combine(Path.GetDirectoryName(_fileNode.GetMkDocument()), - Path.GetFileNameWithoutExtension(_fileNode.FileName) + + return Path.Combine(Path.GetDirectoryName(GetFilePath()), + Path.GetFileNameWithoutExtension(GetFilePath()) + ".Designer.prg"); } @@ -139,17 +154,23 @@ private void SaveSource(string filename, string source, Encoding encoding, bool { source = XSettings.SynchronizeKeywordCase(source, filename); - XSharpFileNode node = _fileNode.FindChild(filename) as XSharpFileNode; + // assign the source to the open buffer when possible + string url = null; + if (_fileNode != null) + { + XSharpFileNode node = _fileNode.FindChild(filename) as XSharpFileNode; + url = node?.Url; + } + else + { + url = filename; + } bool done = false; - if (node != null) + if (url != null && XDocuments.IsOpen(url)) { - // assign the source to the open buffer when possible - if (XDocuments.IsOpen(node.Url)) + if (XDocuments.SetText(url, source)) { - if (XDocuments.SetText(node.Url, source)) - { - done = true; - } + done = true; } } if (!done && SaveToDisk) diff --git a/src/VisualStudio/ProjectPackage/Commands/CommandAddDesignerFile.cs b/src/VisualStudio/ProjectPackage/Commands/CommandAddDesignerFile.cs index 14766c0684..bbec1cab45 100644 --- a/src/VisualStudio/ProjectPackage/Commands/CommandAddDesignerFile.cs +++ b/src/VisualStudio/ProjectPackage/Commands/CommandAddDesignerFile.cs @@ -31,9 +31,14 @@ private async Task CheckAvailabilityAsync() bool visible = false; foreach (var item in items) { - if (item is PhysicalFile file) + if (item is PhysicalFile file && IsXSharpSourceFile(file)) { var subtype = await file.GetAttributeAsync(ProjectFileConstants.SubType); + if (string.IsNullOrEmpty(subtype) && !await Commands.ProjectIsMpfProjectAsync()) + { + // SDK-style projects (CPS) rarely have a SubType + subtype = InferSubType(file.FullPath); + } if (subtype == ProjectFileAttributeValue.Form || subtype == ProjectFileAttributeValue.UserControl) { visible = true; @@ -46,15 +51,15 @@ private async Task CheckAvailabilityAsync() currentFile = null; } } + if (visible && File.Exists(XSharpCodeDomHelper.BuildDesignerFileName(file.FullPath))) + { + // a designer file that is not nested below the form + visible = false; + currentFile = null; + } } } } - // The designer file is generated from the MPFproj file node (XSharpFileNode) - if (visible && !await Commands.ProjectIsMpfProjectAsync()) - { - visible = false; - currentFile = null; - } Command.Visible = visible; if (items.Count() != 1) { @@ -63,36 +68,103 @@ private async Task CheckAvailabilityAsync() } } + private static bool IsXSharpSourceFile(PhysicalFile file) + { + return string.Equals(Path.GetExtension(file.FullPath), ".prg", StringComparison.OrdinalIgnoreCase) && + !file.FullPath.EndsWith(".designer.prg", StringComparison.OrdinalIgnoreCase) && + file.ContainingProject.IsXSharp(); + } + + /// + /// Form or UserControl when the first class of the file inherits from one of them, directly or through + /// its base classes. The same rule as XSharpFileNode.DetermineSubType, from the code model. + /// + private static string InferSubType(string path) + { + var xFile = XSolution.FindFile(path); + if (xFile?.Project == null) + return null; + var first = XDatabase.GetTypesInFile(xFile).FirstOrDefault(c => c.TypeName != XLiterals.GlobalName); + if (first == null) + return null; + var usings = xFile.Usings.Concat(xFile.StaticUsings).ToList(); + var baseType = first.BaseTypeName; + // a limit against cycles in broken code + for (int i = 0; i < 20 && !string.IsNullOrEmpty(baseType); i++) + { + var subType = XSharpFileNode.TypeNameToSubtype(baseType); + if (!string.IsNullOrEmpty(subType)) + return subType; + baseType = xFile.Project.FindType(baseType, usings)?.BaseTypeName; + } + return null; + } + protected override async Task ExecuteAsync(OleMenuCmdEventArgs e) + { + try + { + await AddDesignerFileAsync(); + } + catch (Exception ex) + { + // The command framework would only write it to the activity log + XSettings.Exception(ex); + await VS.MessageBox.ShowErrorAsync(CommandTitle, ex.Message); + } + finally + { + await VS.StatusBar.ShowMessageAsync(""); + } + } + + private const string CommandTitle = "Add .designer file"; + + private async Task AddDesignerFileAsync() { if (currentFile != null) { var newfile = System.IO.Path.ChangeExtension(currentFile.FullPath, ".designer.prg"); - await VS.StatusBar.ShowMessageAsync("Creating designer file:" + newfile); - System.IO.File.WriteAllText(newfile, ""); var project = currentFile.ContainingProject; - XSharpProjectNode projectNode = null; - if (project != null) + var xproject = project != null ? XSolution.FindProjectByFileName(project.FullPath) : null; + if (xproject != null) { - var xproject = XSolution.FindProjectByFileName(project.FullPath); - if (xproject != null) + XSettings.Information($"Reading file data for {currentFile.FullPath} from database"); + VSXSharpCodeDomProvider provider; + if (xproject.ProjectNode is XSharpProjectNode projectNode && + projectNode.FindChild(currentFile.FullPath) is XSharpFileNode fileNode) { - xproject.ProjectNode.AddFileNode(newfile); - projectNode = (XSharpProjectNode)xproject.ProjectNode; + provider = new VSXSharpCodeDomProvider(fileNode); + } + else + { + provider = new VSXSharpCodeDomProvider(xproject, currentFile.FullPath); } - - XSettings.Information($"Reading file data for {currentFile.FullPath} from database"); - XSharpFileNode fileNode; - fileNode = (XSharpFileNode) projectNode.FindChild(currentFile.FullPath); - var provider = new VSXSharpCodeDomProvider(fileNode); provider.FileName = currentFile.FullPath; var source = File.ReadAllText(currentFile.FullPath); var reader = new StringReader(source); var ccuForm = provider.Parse(reader) as XCodeCompileUnit; + // The parser recognizes InitializeComponent (returning System.Void) and the types in it only with + // the references of the project. Right after the solution was opened they may not be loaded yet; + // then the generated code would be wrong (':' instead of '.' for static members). + var formClass = ccuForm == null ? null : XSharpCodeDomHelper.FindDesignerClass(ccuForm) as XCodeTypeDeclaration; + if (formClass == null) + { + await VS.MessageBox.ShowErrorAsync(CommandTitle, + $"Could not find the form class with its InitializeComponent method in {Path.GetFileName(currentFile.FullPath)}.\n\n" + + "When the solution was just opened, try again after the project has finished loading."); + return; + } ccuForm.Source = source; ccuForm.FileName = currentFile.FullPath; ccuForm.MustWrite = true; - var formClass = (XCodeTypeDeclaration) XSharpCodeDomHelper.FindDesignerClass(ccuForm); + + await VS.StatusBar.ShowMessageAsync("Creating designer file:" + newfile); + System.IO.File.WriteAllText(newfile, ""); + // MPFproj adds the designer file below the form. The CPS project system does nothing here: the + // SDK globs include the new file, and XSharp.SDK.Props makes it depend on the form. + xproject.ProjectNode.AddFileNode(newfile); + var designerClass = new XCodeTypeDeclaration(formClass.Name); var ccuDesigner = new XCodeCompileUnit(); ccuDesigner.FileName = newfile; @@ -167,9 +239,6 @@ protected override async Task ExecuteAsync(OleMenuCmdEventArgs e) } File.WriteAllText(currentFile.FullPath, newSource); } - - - await VS.StatusBar.ShowMessageAsync(""); } } } diff --git a/src/VisualStudio/ProjectPackage/Nodes/XSharpFileNode.cs b/src/VisualStudio/ProjectPackage/Nodes/XSharpFileNode.cs index b911b846e0..119ea16853 100644 --- a/src/VisualStudio/ProjectPackage/Nodes/XSharpFileNode.cs +++ b/src/VisualStudio/ProjectPackage/Nodes/XSharpFileNode.cs @@ -206,7 +206,7 @@ protected override int ExcludeFromProject() }); } - private static string TypeNameToSubtype(string typeName) + internal static string TypeNameToSubtype(string typeName) { switch (typeName.ToLower()) { From 2a18bc968bbce249fdd6d7f350eb498e7105351b Mon Sep 17 00:00:00 2001 From: Hansjoerg Petriffer Date: Tue, 29 Sep 2026 12:13:10 +0200 Subject: [PATCH 56/60] [VsIntegration] Add .designer file: do not write the form file in lower case The generated form source was lowered for the "#endregion without #region" check and only restored when that check applied. For a form without a #region the whole form file was written back in lower case. Only the check is case insensitive now. The bug is also on dev. Co-Authored-By: Claude Opus 5.5 --- .../ProjectPackage/Commands/CommandAddDesignerFile.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/VisualStudio/ProjectPackage/Commands/CommandAddDesignerFile.cs b/src/VisualStudio/ProjectPackage/Commands/CommandAddDesignerFile.cs index bbec1cab45..44d96095d0 100644 --- a/src/VisualStudio/ProjectPackage/Commands/CommandAddDesignerFile.cs +++ b/src/VisualStudio/ProjectPackage/Commands/CommandAddDesignerFile.cs @@ -224,10 +224,11 @@ await VS.MessageBox.ShowErrorAsync(CommandTitle, var writer = new StringWriter(); var options = new CodeGeneratorOptions(); provider.GenerateCodeFromCompileUnit(mergedccu, writer, options); - var newSource = writer.ToString().ToLower(); - if (newSource.Contains("#endregion") && !newSource.Contains("#region")) + var newSource = writer.ToString(); + // only the check is case insensitive: the form file is written as generated + var lowerSource = newSource.ToLower(); + if (lowerSource.Contains("#endregion") && !lowerSource.Contains("#region")) { - newSource = writer.ToString(); var lines = Regex.Split(newSource, "\r\n|\r|\n"); var sb = new StringBuilder(); foreach (var line in lines) From 1f768cb9651b8640da7b6c523027937868b15700 Mon Sep 17 00:00:00 2001 From: Hansjoerg Petriffer Date: Tue, 29 Sep 2026 12:30:38 +0200 Subject: [PATCH 57/60] [VsIntegration] CPS shadow designer: do not wait for the references on the blocked UI thread CPS waits synchronously on the UI thread for the task of a command handler (ProjectNode.ExecCommand), without pumping messages. When a form was opened while the package references were still missing, EnsureReferencesAsync waited for the project load inside that wait: the NuGet restore and the design-time build after it could not finish, the wait gave up after 3 s (the IntelliSense stage was already complete, the first design-time build had run without the packages), the build prompt came, and after OK VS hung for good (seen in the experimental instance, stacks read with cdb). - Double click / Enter and View Designer report the command as handled right away when package references are missing, and open the designer in the background. When it cannot be opened, the form opens in the code editor, the default action CPS would have taken. With the references present nothing changes. - After the load stage the references are awaited for up to 20 s instead of 3 s, with a status bar message. Tested in VS with a WebView2 PackageReference restored from a local feed: double click right after the solution opened -> no prompt, the designer opened 1.4 s later, after the design-time build with the packages; with a failing restore -> VS stays responsive, prompt after the wait, the build runs, the designer opens. Co-Authored-By: Claude Opus 5.5 --- .../ShadowDesigner/ShadowDesignerBridge.cs | 27 ++++++++++++++--- .../XSharpShadowDesignerCommands.cs | 29 ++++++++++++++++++- 2 files changed, 51 insertions(+), 5 deletions(-) diff --git a/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/ShadowDesignerBridge.cs b/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/ShadowDesignerBridge.cs index 2aebcd92c4..85aa6d37d1 100644 --- a/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/ShadowDesignerBridge.cs +++ b/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/ShadowDesignerBridge.cs @@ -347,7 +347,7 @@ public static bool TryResolveCompanionPaths(string mainPrgPath, XProject xProjec /// when a build is genuinely needed -- a project with no NuGet references at all /// (nothing to resolve) or one that's already built correctly both return false here. ///
- private static bool IsMissingAnyPackageReference(XProject xProject) + internal static bool IsMissingAnyPackageReference(XProject xProject) { var packageReferences = CompanionProjectWriter.ReadPackageReferences(xProject.FileName); if (packageReferences.Count == 0) return false; @@ -356,8 +356,10 @@ private static bool IsMissingAnyPackageReference(XProject xProject) // How long to wait for the project load (NuGet restore + design-time build) before offering a build private static readonly TimeSpan ProjectLoadTimeout = TimeSpan.FromSeconds(60); - // The adapter processes a design-time build result asynchronously, shortly after the load stage completed - private static readonly TimeSpan ReferenceUpdateTimeout = TimeSpan.FromSeconds(3); + // How long to wait for the references after the load stage. The stage can already be complete when the NuGet + // restore has not finished yet (the first design-time build ran without the packages; seen in VS), and the + // design-time build after the restore follows a few seconds later. + private static readonly TimeSpan ReferenceUpdateTimeout = TimeSpan.FromSeconds(20); /// /// Makes sure that the assembly references of the project's packages are known to the code model before the @@ -370,7 +372,8 @@ private static bool IsMissingAnyPackageReference(XProject xProject) /// restore. When they are missing (shortly after the solution was opened, or while restoring) this waits /// asynchronously for the IntelliSense stage of the project load. Only when they are still missing, a build is /// offered (the build also restores); it runs asynchronously (IVsSolutionBuildManager), without the nested - /// message pump of EnvDTE's BuildProject(WaitForBuildToFinish: true) inside the CPS command handler. + /// message pump of EnvDTE's BuildProject(WaitForBuildToFinish: true). Callers must not be awaited + /// synchronously on the UI thread (as CPS does with command handlers): see XSharpShadowDesignerCommands. /// Returns false with an error when the user declined the build or it could not be started. /// public static async Task<(bool Ok, string Error)> EnsureReferencesAsync(XProject xProject) @@ -412,6 +415,22 @@ private static bool IsMissingAnyPackageReference(XProject xProject) } private static async Task WaitForReferencesAsync(XProject xProject) + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + var statusBar = await AsyncServiceProvider.GlobalProvider.GetServiceAsync(typeof(SVsStatusbar)) as IVsStatusbar; + statusBar?.SetText("X# WinForms Designer: waiting for the package references of " + Path.GetFileNameWithoutExtension(xProject.FileName) + "..."); + try + { + await WaitForReferencesCoreAsync(xProject); + } + finally + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + statusBar?.SetText(""); + } + } + + private static async Task WaitForReferencesCoreAsync(XProject xProject) { await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); if (await AsyncServiceProvider.GlobalProvider.GetServiceAsync(typeof(SVsOperationProgressStatusService)) is IVsOperationProgressStatusService progress) diff --git a/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/XSharpShadowDesignerCommands.cs b/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/XSharpShadowDesignerCommands.cs index 616e148eab..1cad338070 100644 --- a/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/XSharpShadowDesignerCommands.cs +++ b/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/XSharpShadowDesignerCommands.cs @@ -43,10 +43,37 @@ public static bool TryGetFormFile(IImmutableSet nodes, out string // time is swallowed instead of starting a second wait/build. private static bool opening; + /// + /// Entry of the Solution Explorer commands (double click / Enter, View Designer). + /// + /// + /// CPS waits synchronously on the UI thread for the task of a command handler (ProjectNode.ExecCommand), without + /// pumping messages. While package references are missing, EnsureReferencesAsync waits for the project load and + /// may build: neither can finish while the UI thread is blocked, and VS hung after the build prompt (verified + /// in VS). Then the command is reported as handled right away and the open continues in the background; a form + /// that could not be opened is shown in the code editor, the default action CPS would have taken. + /// public static async Task TryOpenAsync(IProjectThreadingService threading, string path) { await threading.SwitchToUIThread(); - return await TryOpenAsync(path); + var xProject = XSolution.FindFile(path)?.Project; + if (xProject == null || !ShadowDesignerBridge.IsMissingAnyPackageReference(xProject)) + return await TryOpenAsync(path); + // Deliberately fire-and-forget, like CompanionSaveWatcher: VSSDK007 wants a package JoinableTaskFactory; + // the project's one (IProjectThreadingService) would make an unload wait for the build prompt. +#pragma warning disable VSSDK007 + ThreadHelper.JoinableTaskFactory.RunAsync(async () => + { + if (!await TryOpenAsync(path)) + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + VsShellUtilities.OpenDocument(ServiceProvider.GlobalProvider, path, VSConstants.LOGVIEWID.Code_guid, + out _, out _, out var frame); + frame?.Show(); + } + }).FileAndForget("XSharp/ProjectSystemCPS/ShadowDesignerOpen"); +#pragma warning restore VSSDK007 + return true; } /// From bd1ba9333b67131775fc89af5ee7170c3d86f172 Mon Sep 17 00:00:00 2001 From: Hansjoerg Petriffer Date: Tue, 29 Sep 2026 12:40:17 +0200 Subject: [PATCH 58/60] [VsIntegration] CPS shadow designer: wait only briefly for the references after a build After the build offered by EnsureReferencesAsync, the references were awaited for the same 20 s as during the project load. A build restores, and the design-time build with the references follows right after it (0.2 s in VS), so 3 s are enough. Also for a failed build: it can have restored and failed on compile errors, then the references still come; when the restore failed, the Designer used to open only after the full 20 s. Tested in VS with a restore that fails (local feed renamed): prompt, OK, the build fails, the Designer opens 3.8 s later (before: 21 s). Co-Authored-By: Claude Opus 5.5 --- .../ShadowDesigner/ShadowDesignerBridge.cs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/ShadowDesignerBridge.cs b/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/ShadowDesignerBridge.cs index 85aa6d37d1..6c1403f033 100644 --- a/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/ShadowDesignerBridge.cs +++ b/src/VisualStudio/ProjectSystemCPS/ShadowDesigner/ShadowDesignerBridge.cs @@ -360,6 +360,8 @@ internal static bool IsMissingAnyPackageReference(XProject xProject) // restore has not finished yet (the first design-time build ran without the packages; seen in VS), and the // design-time build after the restore follows a few seconds later. private static readonly TimeSpan ReferenceUpdateTimeout = TimeSpan.FromSeconds(20); + // After a build (which restores) the design-time build with the references follows right away + private static readonly TimeSpan ReferenceUpdateAfterBuildTimeout = TimeSpan.FromSeconds(3); /// /// Makes sure that the assembly references of the project's packages are known to the code model before the @@ -409,19 +411,22 @@ internal static bool IsMissingAnyPackageReference(XProject xProject) { return (false, buildError); } - await WaitForReferencesAsync(xProject); + // The build has restored: the design-time build with the references follows right after it (0.2 s in + // VS). A short wait, also for a failed build, which can have restored as well (compile errors): the + // references can still come, but when the restore failed 20 s would only delay the Designer. + await WaitForReferencesAsync(xProject, ReferenceUpdateAfterBuildTimeout); // Like before: continue even when references are still missing (e.g. a failed build) return (true, null); } - private static async Task WaitForReferencesAsync(XProject xProject) + private static async Task WaitForReferencesAsync(XProject xProject, TimeSpan? timeout = null) { await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); var statusBar = await AsyncServiceProvider.GlobalProvider.GetServiceAsync(typeof(SVsStatusbar)) as IVsStatusbar; statusBar?.SetText("X# WinForms Designer: waiting for the package references of " + Path.GetFileNameWithoutExtension(xProject.FileName) + "..."); try { - await WaitForReferencesCoreAsync(xProject); + await WaitForReferencesCoreAsync(xProject, timeout ?? ReferenceUpdateTimeout); } finally { @@ -430,7 +435,7 @@ private static async Task WaitForReferencesAsync(XProject xProject) } } - private static async Task WaitForReferencesCoreAsync(XProject xProject) + private static async Task WaitForReferencesCoreAsync(XProject xProject, TimeSpan timeout) { await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); if (await AsyncServiceProvider.GlobalProvider.GetServiceAsync(typeof(SVsOperationProgressStatusService)) is IVsOperationProgressStatusService progress) @@ -442,7 +447,7 @@ private static async Task WaitForReferencesCoreAsync(XProject xProject) await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); } } - var deadline = DateTime.UtcNow + ReferenceUpdateTimeout; + var deadline = DateTime.UtcNow + timeout; while (IsMissingAnyPackageReference(xProject) && DateTime.UtcNow < deadline) { await Task.Delay(250); From 8e4f2fecb3137873c3577c22465b529e9f81c1b0 Mon Sep 17 00:00:00 2001 From: Hansjoerg Petriffer Date: Tue, 29 Sep 2026 13:14:27 +0200 Subject: [PATCH 59/60] [VsIntegration] CPS project system: conditional compilation symbols keep DEBUG and TRACE The "Conditional compilation symbols" field of the managed Build page edits DefineConstants with a MultiStringSelector, whose value is a list of name/value pairs. For C# the managed DefineConstantsValueProvider (persistence ProjectFileWithInterception, AppliesTo CSharp | FSharp) converts it. For X# nothing did: adding MYSYM wrote MYSYM=False into the project file, the implicit DEBUG and TRACE were lost and MYSYM was not defined either (verified in VS). That value provider cannot be used for X#: its interfaces are in Microsoft.VisualStudio.ProjectSystem.Managed.dll, which has no usable package and a version per VS release (the part would be rejected on older VS). So: - BuildPropertyPage.XSharp.xaml defines DefineConstants like the managed page, with the persistence "XSharpDefineConstants". - XSharpDefineConstantsPropertiesProvider (public CPS API only) wraps the project file provider and converts DefineConstants: the list shows the symbols of the project file after "$(DefineConstants);" (a value without the prefix is shown as a whole), a change is written as "$(DefineConstants);SYMBOL;...", an empty list deletes the property. Exported named and unnamed: the property pages import the unnamed contract and select by the "Name" metadata. - KeyValuePairListEncoding: the editors' list format, internal in the managed project system. Tested in VS: MYSYM added for Debug -> "$(DefineConstants);MYSYM", the program reports DEBUG, TRACE and MYSYM; the page shows MYSYM again after reopening; removing it deletes the property. Co-Authored-By: Claude Opus 5.5 --- .../Rules/BuildPropertyPage.XSharp.xaml | 24 +++ .../PropertyPages/KeyValuePairListEncoding.cs | 72 +++++++ ...XSharpDefineConstantsPropertiesProvider.cs | 180 ++++++++++++++++++ 3 files changed, 276 insertions(+) create mode 100644 src/VisualStudio/ProjectSystemCPS/PropertyPages/KeyValuePairListEncoding.cs create mode 100644 src/VisualStudio/ProjectSystemCPS/PropertyPages/XSharpDefineConstantsPropertiesProvider.cs diff --git a/src/Compiler/src/Compiler/XSharpBuildTask/Rules/BuildPropertyPage.XSharp.xaml b/src/Compiler/src/Compiler/XSharpBuildTask/Rules/BuildPropertyPage.XSharp.xaml index e63966cd22..5c9f479083 100644 --- a/src/Compiler/src/Compiler/XSharpBuildTask/Rules/BuildPropertyPage.XSharp.xaml +++ b/src/Compiler/src/Compiler/XSharpBuildTask/Rules/BuildPropertyPage.XSharp.xaml @@ -3,6 +3,30 @@ OverrideMode="Extend" xmlns="http://schemas.microsoft.com/build/2009/properties"> + + + + + + + + + + + + + + + + + + /// The value format of the property editors' name/value lists (MultiStringSelector, NameValueList): + /// "name1=value1,name2=value2", with '/' escaping '/', ',' and '='. + /// + /// + /// The same format as KeyValuePairListEncoding of the managed project system, which is internal to it. + /// + internal static class KeyValuePairListEncoding + { + public static IEnumerable<(string Name, string Value)> Parse(string input) + { + if (string.IsNullOrWhiteSpace(input)) + yield break; + foreach (var entry in ReadEntries(input)) + { + var (encodedName, encodedValue) = SplitEntry(entry); + var name = Decode(encodedName); + if (!string.IsNullOrEmpty(name)) + yield return (name, Decode(encodedValue)); + } + } + + public static string Format(IEnumerable<(string Name, string Value)> pairs) => + string.Join(",", pairs.Select(pair => Encode(pair.Name) + "=" + Encode(pair.Value))); + + private static string Encode(string value) => value.Replace("/", "//").Replace(",", "/,").Replace("=", "/="); + + private static string Decode(string value) => value.Replace("/=", "=").Replace("/,", ",").Replace("//", "/"); + + private static IEnumerable ReadEntries(string text) + { + bool escaped = false; + int start = 0; + for (int i = 0; i < text.Length; i++) + { + if (text[i] == ',' && !escaped) + { + yield return text.Substring(start, i - start); + start = i + 1; + escaped = false; + } + else + { + escaped = text[i] == '/' && !escaped; + } + } + yield return text.Substring(start); + } + + private static (string Name, string Value) SplitEntry(string entry) + { + bool escaped = false; + for (int i = 0; i < entry.Length; i++) + { + if (entry[i] == '=' && !escaped) + return (entry.Substring(0, i), entry.Substring(i + 1)); + escaped = entry[i] == '/' && !escaped; + } + return (string.Empty, string.Empty); + } + } +} diff --git a/src/VisualStudio/ProjectSystemCPS/PropertyPages/XSharpDefineConstantsPropertiesProvider.cs b/src/VisualStudio/ProjectSystemCPS/PropertyPages/XSharpDefineConstantsPropertiesProvider.cs new file mode 100644 index 0000000000..df15ddced3 --- /dev/null +++ b/src/VisualStudio/ProjectSystemCPS/PropertyPages/XSharpDefineConstantsPropertiesProvider.cs @@ -0,0 +1,180 @@ +// +// Copyright (c) XSharp B.V. All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +// +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Build.Execution; +using Microsoft.Build.Framework; +using Microsoft.Build.Framework.XamlTypes; +using Microsoft.VisualStudio.ProjectSystem; +using Microsoft.VisualStudio.ProjectSystem.Properties; +using Microsoft.VisualStudio.Threading; + +namespace XSharp.ProjectSystem.PropertyPages +{ + /// + /// Persistence "XSharpDefineConstants" of the "Conditional compilation symbols" field (DefineConstants in + /// BuildPropertyPage.XSharp.xaml): the project file, with the value converted like C# does. + /// + /// + /// The managed Build page edits DefineConstants with a MultiStringSelector, whose value is a list of name/value + /// pairs ("MYSYM=False"). For C# the managed DefineConstantsValueProvider (persistence + /// "ProjectFileWithInterception", AppliesTo CSharp | FSharp) converts it: the list shows only the symbols of the + /// project file after "$(DefineConstants);", and a change is written as "$(DefineConstants);MYSYM", so the + /// implicit symbols (DEBUG, TRACE) are kept. For X# nothing converted it: VS wrote "MYSYM=False" into the project + /// file and DEBUG and TRACE were lost (verified in VS). That value provider cannot be used for X#: its interfaces + /// are in Microsoft.VisualStudio.ProjectSystem.Managed.dll, which has no package and a version per VS release. + /// So this provider does the same with the public CPS API: it wraps the project file provider and converts only + /// DefineConstants. + /// + // The property pages import the unnamed contracts and select the provider by the "Name" metadata + // (PropertyPagesDataModelProvider); the named exports are for imports by persistence name. + [Export(PersistenceName, typeof(IProjectPropertiesProvider))] + [Export(typeof(IProjectPropertiesProvider))] + [Export(PersistenceName, typeof(IProjectInstancePropertiesProvider))] + [Export(typeof(IProjectInstancePropertiesProvider))] + [ExportMetadata("Name", PersistenceName)] + [AppliesTo(XSharpCapabilities.XSharpCps)] + internal sealed class XSharpDefineConstantsPropertiesProvider : IProjectPropertiesProvider, IProjectInstancePropertiesProvider + { + public const string PersistenceName = "XSharpDefineConstants"; + private const string ProjectFileContract = "Microsoft.VisualStudio.ProjectSystem.ProjectFile"; + + private readonly IProjectPropertiesProvider provider; + private readonly IProjectInstancePropertiesProvider instanceProvider; + private readonly UnconfiguredProject project; + + [ImportingConstructor] + public XSharpDefineConstantsPropertiesProvider( + [Import(ProjectFileContract)] IProjectPropertiesProvider provider, + [Import(ProjectFileContract)] IProjectInstancePropertiesProvider instanceProvider, + UnconfiguredProject project) + { + this.provider = provider; + this.instanceProvider = instanceProvider; + this.project = project; + } + + public string DefaultProjectPath => project.FullPath; + + public event AsyncEventHandler ProjectPropertyChanged + { + add => provider.ProjectPropertyChanged += value; + remove => provider.ProjectPropertyChanged -= value; + } + + public event AsyncEventHandler ProjectPropertyChangedOnWriter + { + add => provider.ProjectPropertyChangedOnWriter += value; + remove => provider.ProjectPropertyChangedOnWriter -= value; + } + + public event AsyncEventHandler ProjectPropertyChanging + { + add => provider.ProjectPropertyChanging += value; + remove => provider.ProjectPropertyChanging -= value; + } + + // The property pages read and write through GetProperties; the rest is passed on unchanged + public IProjectProperties GetProperties(string file, string itemType, string item) => + new DefineConstantsProperties(provider.GetProperties(file, itemType, item)); + + public IProjectProperties GetCommonProperties() => provider.GetCommonProperties(); + public IProjectProperties GetItemProperties(string itemType, string item) => provider.GetItemProperties(itemType, item); + public IProjectProperties GetItemTypeProperties(string itemType) => provider.GetItemTypeProperties(itemType); + public IProjectProperties GetCommonProperties(ProjectInstance projectInstance) => instanceProvider.GetCommonProperties(projectInstance); + public IProjectProperties GetItemTypeProperties(ProjectInstance projectInstance, string itemType) => instanceProvider.GetItemTypeProperties(projectInstance, itemType); + public IProjectProperties GetItemProperties(ProjectInstance projectInstance, string itemType, string itemName) => instanceProvider.GetItemProperties(projectInstance, itemType, itemName); + public IProjectProperties GetItemProperties(ITaskItem taskItem) => instanceProvider.GetItemProperties(taskItem); + + /// + /// The project file properties; DefineConstants is converted between the MultiStringSelector list and + /// "$(DefineConstants);SYMBOL;..." (DefineConstantsValueProvider of the managed project system). + /// + private sealed class DefineConstantsProperties : IProjectProperties, IRuleAwareProjectProperties + { + private const string DefineConstants = nameof(DefineConstants); + private const string RecursivePrefix = "$(DefineConstants)"; + + private readonly IProjectProperties properties; + + public DefineConstantsProperties(IProjectProperties properties) + { + this.properties = properties; + } + + private static bool IsDefineConstants(string propertyName) => + string.Equals(propertyName, DefineConstants, StringComparison.OrdinalIgnoreCase); + + public IProjectPropertiesContext Context => properties.Context; + public string FileFullPath => properties.FileFullPath; + public PropertyKind PropertyKind => properties.PropertyKind; + + public Task DeleteDirectPropertiesAsync() => properties.DeleteDirectPropertiesAsync(); + public Task DeletePropertyAsync(string propertyName, IReadOnlyDictionary dimensionalConditions = null) => + properties.DeletePropertyAsync(propertyName, dimensionalConditions); + public Task> GetDirectPropertyNamesAsync() => properties.GetDirectPropertyNamesAsync(); + public Task> GetPropertyNamesAsync() => properties.GetPropertyNamesAsync(); + public Task GetEvaluatedPropertyValueAsync(string propertyName) => properties.GetEvaluatedPropertyValueAsync(propertyName); + public Task IsValueInheritedAsync(string propertyName) => properties.IsValueInheritedAsync(propertyName); + + public async Task GetUnevaluatedPropertyValueAsync(string propertyName) + { + var value = await properties.GetUnevaluatedPropertyValueAsync(propertyName); + if (!IsDefineConstants(propertyName)) + return value; + // Only the symbols of the project file itself; the imported ones (DEBUG, TRACE) are shown as the + // evaluated preview + if (value == null || await properties.IsValueInheritedAsync(propertyName)) + return string.Empty; + return KeyValuePairListEncoding.Format(ParseOwnSymbols(value).Select(symbol => (symbol, bool.FalseString))); + } + + public async Task SetPropertyValueAsync(string propertyName, string unevaluatedPropertyValue, IReadOnlyDictionary dimensionalConditions = null) + { + if (!IsDefineConstants(propertyName)) + { + await properties.SetPropertyValueAsync(propertyName, unevaluatedPropertyValue, dimensionalConditions); + return; + } + // Exactly the symbols of the list, after the inherited ones. (The managed provider also leaves out + // symbols of an outer "$(DefineConstants);..." definition; a symbol defined twice does no harm.) + var symbols = KeyValuePairListEncoding.Parse(unevaluatedPropertyValue) + .Select(pair => pair.Name.Trim()) + .Where(symbol => symbol.Length > 0) + .Distinct(StringComparer.Ordinal) + .ToList(); + if (symbols.Count == 0) + { + await properties.DeletePropertyAsync(propertyName, dimensionalConditions); + return; + } + await properties.SetPropertyValueAsync(propertyName, RecursivePrefix + ";" + string.Join(";", symbols), dimensionalConditions); + } + + /// + /// The symbols after "$(DefineConstants);". A value without that prefix (written by hand or by MPFproj) + /// is taken as a whole, so its symbols are shown and kept. + /// + private static IEnumerable ParseOwnSymbols(string value) + { + value = value?.Trim() ?? string.Empty; + if (value.StartsWith(RecursivePrefix, StringComparison.OrdinalIgnoreCase)) + value = value.Substring(RecursivePrefix.Length); + return value.Split(new[] { ';', ',' }, StringSplitOptions.RemoveEmptyEntries) + .Select(symbol => symbol.Trim()) + .Where(symbol => symbol.Length > 0); + } + + public void SetRuleContext(Rule rule) + { + (properties as IRuleAwareProjectProperties)?.SetRuleContext(rule); + } + } + } +} From f8b0fd86dfcabeecb4a68aa144a6c5bb696cb9af Mon Sep 17 00:00:00 2001 From: Hansjoerg Petriffer Date: Tue, 29 Sep 2026 15:48:08 +0200 Subject: [PATCH 60/60] [VsIntegration] CPS project system: documentation README.md in src/VisualStudio/ProjectSystemCPS: what the CPS project system for SDK-style X# projects offers, the supported Visual Studio versions, the installation (VSIX plus matching X# MSBuild support files and rules), how it works (project selection, capabilities, MSBuild integration, feeding the code model, errors and Task List, property pages, icons, VO designers, shadow WinForms designer, commands, multi-targeting), the source layout, building, testing and debugging, troubleshooting and the known limitations. Co-Authored-By: Claude Opus 5.5 --- src/VisualStudio/ProjectSystemCPS/README.md | 519 ++++++++++++++++++++ 1 file changed, 519 insertions(+) create mode 100644 src/VisualStudio/ProjectSystemCPS/README.md diff --git a/src/VisualStudio/ProjectSystemCPS/README.md b/src/VisualStudio/ProjectSystemCPS/README.md new file mode 100644 index 0000000000..25a9e349e6 --- /dev/null +++ b/src/VisualStudio/ProjectSystemCPS/README.md @@ -0,0 +1,519 @@ +# X# project system for SDK-style projects (CPS) + +`XSharp.ProjectSystemCPS` loads **SDK-style X# projects** (``) in Visual Studio with the +**Common Project System (CPS)**, the project system that Visual Studio also uses for SDK-style C#, VB and F# +projects. Legacy (non-SDK) X# projects keep using the classic X# project system, a fork of MPFproj +(`ProjectBase` and `ProjectPackage`). Both run side by side; a project selector decides per project. + +The X# language service and code model (`XSharpCodeModelXs`) are unchanged. This assembly only replaces the part +that feeds them: instead of walking the MPFproj hierarchy and reading a response file, it takes the project items, +references and compiler options from CPS and its design-time builds. + +Contents: + +1. [What you get](#1-what-you-get) +2. [Supported environments](#2-supported-environments) +3. [Installation](#3-installation) +4. [How it works](#4-how-it-works) +5. [Source layout](#5-source-layout) +6. [Building](#6-building) +7. [Testing and debugging](#7-testing-and-debugging) +8. [Troubleshooting](#8-troubleshooting) +9. [Known limitations and open issues](#9-known-limitations-and-open-issues) +10. [References](#10-references) + +--- + +## 1. What you get + +For SDK-style X# projects, Visual Studio offers what it offers for SDK-style C# projects, plus the X# specific +parts: + +- **Solution Explorer:** the Dependencies node (packages, assemblies, projects, COM, frameworks), file globbing, + "Edit Project File", nesting of dependent files (`.designer.prg`, `.rc`, VO binaries), X# icons for the project, + source files, forms, user controls and VO binaries. +- **NuGet:** `PackageReference`, restore, "Manage NuGet Packages". +- **Properties:** the new project properties editor with the managed pages (Application, Build, Build Events, + Package, Debug, …) and the X# pages Language and Dialect. +- **Debugging:** launch profiles (`Properties/launchSettings.json`) and the framework dropdown of multi-target + projects. +- **Publish and Pack** (`.nupkg`), as for C# projects. +- **IntelliSense without a prior build:** completion, navigation and the other features of the X# language service + work as soon as the design-time build has run. A real build is not needed. +- **Error squiggles** for build errors, and **Task List** entries for comment tokens (`// TODO` …). +- **Designers:** the VO designers (window, menu, DBServer, FieldSpec) and the WinForms designer through the + *shadow designer*; "Add .designer file" for forms written in a single file. + +--- + +## 2. Supported environments + +| Environment | SDK-style X# projects | Legacy X# projects | +|---|---|---| +| Visual Studio 2022 (17.x) | CPS (this assembly) | MPFproj | +| Visual Studio 2026 (18.x) | CPS (tested with 18.10) | MPFproj | +| Visual Studio 2019 (16.x) | not supported ("cannot be loaded inside this version of Visual Studio", as before) | MPFproj | + +`XSharp.ProjectSystemCPS` only uses the public CPS SDK (`Microsoft.VisualStudio.ProjectSystem.Sdk` 17.9). It does +not reference the private assemblies of the managed project system (`Microsoft.VisualStudio.ProjectSystem.Managed*.dll`), +because they have no package and change their version with every Visual Studio release. + +--- + +## 3. Installation + +### 3.1 What has to be installed + +The CPS project system needs two parts, which **must come from the same build**: + +1. **The VSIX** (`XSharp.Project2022.vsix`, built by `ProjectPackage2022`). It contains `XSharp.ProjectSystemCPS.dll`, + its pkgdef (package, project type, project selector) and `XSharp.ProjectSystemCPS.imagemanifest` (icons). +2. **The X# MSBuild support files** in the X# `MsBuild` folder (`XSharpBuildTask`, installed by the X# setup): + + | File | Purpose | + |---|---| + | `XSharp.CurrentVersion.targets`, `XSharp.CrossTargeting.targets` | import `XSharp.DesignTime.targets` when the project is loaded by CPS | + | `XSharp.DesignTime.targets` | the CPS design-time integration (managed design-time targets, capabilities, rules) | + | `XSharp.SDK.Props` | item globs of SDK-style projects; excludes the shadow designer companion folders | + | `Rules\ProjectItemsSchema.XSharp.xaml` | file extensions → item types (Compile, None, NativeResource, VOBinary) | + | `Rules\XSharpCompilerCommandLineArgs.xaml`, `Rules\XSharpProjectProperties.xaml` | design-time data for the code model | + | `Rules\NativeResource*.xaml`, `Rules\VOBinary*.xaml` | properties of `.rc` files and VO binaries | + | `Rules\ApplicationPropertyPage.XSharp.xaml`, `BuildPropertyPage.XSharp.xaml`, `LanguagePage.XSharp.xaml`, `DialectPage.XSharp.xaml`, `ReferencesPage.XSharp.xaml` | the property pages | + + `XSharp.Build.csproj` copies the rules to its output folder (`CopyToOutputDirectory`); **the X# installer must ship + the `Rules` folder** together with the targets. + +### 3.2 Finding the MSBuild support files + +The project selector checks the support files before it sends a project to CPS +(`Selection/XSharpMsBuildSupport.cs`). It looks for the X# `MsBuild` folder like `XSharp.BeforeCommon.Props` does: + +1. the environment variable `XSharpMsBuildDir`, +2. the registry value `XSharpPath` (`HKLM\SOFTWARE\WOW6432Node\XSharpBV\XSharp` or `HKLM\SOFTWARE\XSharpBV\XSharp`) + plus `MsBuild`, +3. `%ProgramFiles(x86)%\XSharp\MsBuild`. + +CPS is used when `XSharp.CurrentVersion.targets` there contains the `XSharpCpsProjectSystem` import and +`Rules\XSharpCompilerCommandLineArgs.xaml` exists. + +### 3.3 Mismatched installations + +- **VSIX newer than the X# installation** (support files without the CPS import): SDK-style projects are not loaded. + The selector sends them to MPFproj, which shows "The project file … is an SDK style project. SDK style X# projects + are loaded by the X# project system for SDK style projects, which needs the X# MSBuild support files of a matching + X# installation. Please run the X# setup program again." Legacy projects are not affected. +- **X# installation newer than the VSIX:** the targets only import the design-time integration when the global + property `XSharpCpsProjectSystem` is `true`, which only this project system sets. Command-line builds and MPFproj + projects are unchanged. + +### 3.4 Assemblies in the GAC + +The X# setup installs `XSharp.CodeDomProvider`, `XSharp.VSParser` and `XSharp.Evaluator` in the GAC. Visual Studio +always loads the GAC copy when its identity matches, also for development builds of the VSIX. A change to one of these +assemblies (for example the CodeDOM generator used by "Add .designer file" and the designers) only becomes effective +in Visual Studio with a matching X# installation. + +--- + +## 4. How it works + +### 4.1 Project selection + +Solutions reference X# projects with the MPFproj project type GUID `{AA6C8D78-22FF-423A-9C7C-5F2393824E04}`. The CPS +project type has its own GUID `{AB494DCE-A11B-4583-851E-D4A86775B3F0}`. + +`Selection/XSharpProjectSelector` is an `IVsProjectSelector` (the mechanism the managed project system uses for +`FSharpProjectSelector`), registered for **both** project types (`Selection/ProvideProjectSelectorAttribute`, +`Projects\{type}\ProjectSelector`). For every X# project that is opened it decides: + +- **CPS** when the project file is SDK-style (``, `` or ``, + `Selection/SdkProjectFile`) **and** the MSBuild support files support CPS (3.2); +- **MPFproj** otherwise, also when the file cannot be read. + +The package `XSharpProjectSystemPackage` registers the selector object with `IVsRegisterProjectSelector` when it +initializes; Visual Studio loads the package on demand. + +When Visual Studio saves a solution it writes the GUID of the project system that actually loaded the project, so the +entry of an SDK-style project changes once from `{AA6C8D78…}` to `{AB494DCE…}` (C# SDK projects do the same: +`{FAE04EC0…}` → `{9A19103F…}`; Visual Studio has no way to prevent it). Because the selector is registered for the CPS +GUID as well, such an entry still goes to MPFproj when the project is legacy or the support files are too old. + +The X# project templates still use the MPFproj project type (template language `XSharp`); SDK-style templates reach +CPS through the selector. The CPS project type has its own template language `XSharpCps`, so no template is offered +twice. + +### 4.2 Project type and capabilities + +`XSharpProjectSystemPackage` registers the CPS project type (`[ProjectTypeRegistration]`) with the initial +capabilities of `XSharpCapabilities.ProjectTypeCapabilities`: + +``` +XSharpCps; AppDesigner; HandlesOwnReload; OpenProjectFile; PreserveFormatting; +ProjectConfigurationsDeclaredDimensions; ProjectPropertiesEditor; .NET; UseProjectEvaluationCache +``` + +This is the set of the managed C# project type without `LanguageService` (X# has its own language service, not the +Roslyn workspace), `CSharp`, `SharedImports` and `EditAndContinue`. `XSharpCps` is the gate for all MEF parts of this +assembly (`[AppliesTo("XSharpCps")]`). + +`XSharp.DesignTime.targets` adds the MSBuild capabilities of `Microsoft.Managed.DesignTime.targets` +(`DependenciesTree`, `PackageReferences`, `AssemblyReferences`, `ProjectReferences`, `COMReferences`, `LaunchProfiles`, +`Publish`, `UseFileGlobs`, …) plus `XSharp; Managed; SingleTargetBuildForStartupProjects; ProjectPropertiesEditor`, and +removes `LanguageService` and `ReferencesFolder`. + +### 4.3 MSBuild integration + +- `Evaluation/XSharpCpsGlobalPropertiesProvider` sets the **global property `XSharpCpsProjectSystem=true`** for projects + of the CPS project type. +- `XSharp.CurrentVersion.targets` and `XSharp.CrossTargeting.targets` import `XSharp.DesignTime.targets` only when that + property is `true` and the files exist (`$(XSharpDesignTimeTargetsPath)`, `$(XSharpManagedDesignTimeTargetsPath)`, + both overridable). MPFproj projects and command-line builds are therefore not affected. +- `XSharp.DesignTime.targets` imports the managed design-time targets from the Visual Studio installation (like F#), + sets the X# capabilities and rules, and points `AddItemTemplatesGuid`, `GeneratorsTypeGuid` and `CmdUIContextGuid` + to the MPFproj project factory, where the X# package already registers the item templates and single-file + generators. **Add New Item** therefore offers the X# templates. +- `XSharpCpsProjectSystem` is appended to `_GlobalPropertiesToRemoveFromProjectReferences`, so referenced projects + (C#, legacy X#) are built with their own global properties and are not built twice. +- `Rules/ProjectItemsSchema.XSharp.xaml` maps the file extensions: `.prg/.prgx/.xs` → `Compile`, `.vh/.xh/.ch` → `None`, + `.rc` → `NativeResource`, `.xsfrm/.vnfrm/.xsdbs/.vndbs/.xsmnu/.vnmnu/.xsfs/.vnfs/.xssql/.vnsqs/.xsrep/.vnrep` → `VOBinary`. + The globs themselves come from the .NET SDK and `XSharp.SDK.Props` (with `DependentUpon` for designer files, `.rc` + files and VO binaries). + +### 4.4 Feeding the code model + +`LanguageService/XSharpProjectAdapter` is the core. It is an `IProjectDynamicLoadComponent` (one per project, +`UnconfiguredProject` scope) and implements `IXSharpProject`, the interface through which the code model talks to a +project system. When a project loads it: + +1. makes sure the X# solution model (`XSolution`) is open. If the X# package has not opened it yet (a pure CPS + solution, a project opened without a `.sln`), the adapter loads the X# package and opens the model for the solution + file, also when the solution has not been saved yet (`.vs\{name}\X#Model.xsdb`); +2. creates the `XProject` **on the UI thread**, like MPFproj (parallel creation raced on the X# database); +3. subscribes to the **active configuration** through `IActiveConfiguredProjectSubscriptionService`: + + | Subscription | Data | Code model | + |---|---|---| + | `SourceItemsRuleSource` (rule names from `SourceItemRuleNamesSource`) | Compile, None, Content, EmbeddedResource, Page, ApplicationDefinition, Resource, VOBinary, NativeResource items | `AddFile` / `RemoveFile` (a rename is remove + add) | + | `ProjectRuleSource` (`XSharpProjectProperties`, `EvaluatedProjectReference`) | RootNamespace, IntermediateOutputPath, TargetPath, NS, project references | properties; provisional parse options (dialect only) until the first design-time build; `AddProjectReference` / `RemoveProjectReference` | + | `ProjectBuildRuleSource` (`XSharpCompilerCommandLineArgs`) | the compiler command line of the design-time build (`CompileDesignTime`) | `XParseOptions.FromVsValues`, `RefreshReferences`, `ResolveReferences` | + + `XSharpCommandLine` converts the Xsc switches into the format of `FromVsValues` (the same result as the old + response-file path). A failed design-time build (empty result) keeps the previous state. +4. starts the model walker (`ModelWalker.AddProject`) after file changes and design-time builds. + +Two locks keep this consistent: `modelGate` serializes all changes of the code model, `gate` only protects the file set +(so `HasFileNode`, called by the designers on the UI thread, never waits for database I/O). + +`LanguageService/SourceFileWatcher` (a `FileSystemWatcher` on the project folder) walks source files of the project +that change on disk 500 ms after the last change: saves in the editor, files written by the designers, external tools, +source control. After a buffer overflow the whole project is walked again. Linked files outside the project folder are +not watched. + +`AddFileNode` / `DeleteFileNode` (called by the VO designers after they wrote or before they delete a generated file) +register the file with the code model immediately; the project file does not change, because the SDK globs include the +files. + +### 4.5 Errors and the Task List + +- `LanguageService/BuildErrorLoggerProvider` (`IBuildLoggerProviderAsync`) attaches an MSBuild logger to real builds + (Build, Rebuild, Clean; not design-time builds) and passes the errors and warnings to the adapter + (`SetBuildErrors`, per configuration). `IntellisenseErrorStore` merges them with the code model errors; the X# + error tagger draws the **squiggles** from them. Build errors are not added to the Error List again, Visual Studio + already shows them. +- `LanguageService/IntellisenseErrorList` shows code model errors in the Error List. The code model currently reports + none (as with MPFproj), so the list stays empty. +- `LanguageService/CommentTaskList` shows the comment tasks of the project in the Task List after each walk. + `CommentTokens` passes the Task List tokens (Tools > Options > Environment > Task List) to the code model when no + MPFproj project did. + +### 4.6 Property pages + +The project properties are the new properties editor of Visual Studio (capability `ProjectPropertiesEditor`) with XAML +rules. `PropertyPages/XSharpProjectDesignerPageProvider` is needed so that Visual Studio offers the project designer at +all (it only has providers for C#/VB/F#). + +| Page | Rule | Content | +|---|---|---| +| Application | `ApplicationPropertyPage.XSharp.xaml` (extends the managed page) | manifest, `VulcanCompatibleResources`, `UseNativeVersion`, `NoWin32Manifest`; the C# Win32 resource choice is hidden (X# compiles the `.rc` files instead) | +| Build | `BuildPropertyPage.XSharp.xaml` (extends the managed page) | other flags (`CommandLineOption`), shared compiler, warning level, `SuppressRCWarnings`, conditional compilation symbols; C#-only options hidden | +| Language | `LanguagePage.XSharp.xaml` | dialect, named arguments, unsafe, case sensitivity, overflow checks, include paths, standard defines, … | +| Dialect | `DialectPage.XSharp.xaml` | VO1–VO17, LB, XPP1, FOX2, FOX3 (depending on the dialect) | +| Global Usings | `ReferencesPage.XSharp.xaml` | `ImplicitUsings` | +| Build Events, Debug, Package, … | managed pages | | + +**Conditional compilation symbols:** the managed Build page edits `DefineConstants` with a list editor whose value is a +list of name/value pairs. For C# a value provider of the managed project system converts it; for X# nothing did, so the +editor wrote `MYSYM=False` into the project file and lost the implicit `DEBUG;TRACE`. +`BuildPropertyPage.XSharp.xaml` therefore defines `DefineConstants` with its own persistence `XSharpDefineConstants`, +implemented by `PropertyPages/XSharpDefineConstantsPropertiesProvider`: it wraps the project file provider, shows only +the project's own symbols (after `$(DefineConstants);`), writes `$(DefineConstants);MYSYM` and deletes the property +when the list is empty. `PropertyPages/KeyValuePairListEncoding` is the list format of the editors. + +`` items (global usings with alias or static) are edited in the project file; the C# "Manage Implicit Global +Usings" editor needs a value provider that only exists for C#. + +Changing a property triggers a design-time build; the adapter picks up the new parse options from it. + +### 4.7 Solution Explorer icons + +`Imaging/XSharpProjectTreePropertiesProvider` sets the X# icons (`XSharp.ProjectSystemCPS.imagemanifest`): the project, +`.prg/.prgx/.xs` files, VO binaries, and forms and user controls. A `.prg` is a form or user control when its `SubType` +says so or, since SDK-style projects rarely have one, when a matching `.Designer.prg` exists and its class inherits +from `Form`/`UserControl` (also over a line continuation, the format of the CodeDOM generator). + +CPS only recalculates the icons of nodes that changed in a tree update. Adding a `.Designer.prg` below an existing +`.prg` does not change the `.prg` node, so `Imaging/XSharpProjectTreePropertiesProviderSource` +(`IProjectTreePropertiesProviderDataSource`) supplies the provider and publishes a new instance when a `.Designer.prg` +is added or removed; CPS then recalculates all nodes. Its values carry their own version key: with the versions of the +source items subscription the tree waited for ever. + +The image library resolves the manifest's pack URIs at startup, so the package has `[ProvideBindingPath]` and +`ProjectPackage2022` a `ProvideCodeBase` for `XSharp.ProjectSystemCPS`. + +### 4.8 VO designers + +The VO window, menu, DBServer and FieldSpec editors are registered by file extension in the X# package and do not +depend on the project system. They get the project through `XSolution.FindFile(file).Project.ProjectNode`, which is the +adapter for files of CPS projects. They write the generated `.prg`/`.rc` files to disk and call +`HasFileNode`/`AddFileNode`/`DeleteFileNode`; `SourceFileWatcher` makes the code model read the regenerated files. + +### 4.9 WinForms designer (shadow designer) + +The Visual Studio WinForms designer only works with C# and VB projects that the managed project system loaded (for +.NET it runs out of process and needs the project's references, target framework and Roslyn workspace). The shadow +designer therefore opens X# forms through a generated **companion C# project**: + +1. **Companion:** `ShadowDesignerBridge` converts the form (`Form1.prg` plus `Form1.Designer.prg`) with the X# CodeDOM + parser and the C# code provider into `Form1.Designer.cs` and a stub `Form1.cs` (same base class and imports), in a + companion project `.ShadowDesigner\.ShadowDesigner.csproj` next to the project folder + (`CompanionProjectWriter`). The companion gets the target framework of the loaded project and its resolved + references. `XSharp.SDK.Props` excludes `**/*.ShadowDesigner/**` from X# projects. +2. **Solution:** `SolutionWiring` adds the companion to the solution folder "Shadow Designer (generated)" and opens + `Form1.Designer.cs` in the designer. +3. **Saving:** `CompanionSaveWatcher` and `DesignerChangesSync` write the designer's changes back as X# + (`Form1.Designer.prg`) with the X# CodeDOM generator; `EventHandlerSync` adds event handlers to `Form1.prg`. + The context menu commands "Sync Designer Changes From Shadow Designer" and "Sync Event Handlers From Shadow Designer" + do the same on demand. +4. **Cleanup:** `ShadowDesignerCleanup` removes the companion projects and their folders when the solution closes + (with retries while the designer host still locks a folder, and cancellation when the companion is used again). + +Entry points in CPS projects: double click / Enter (`XSharpShadowDesignerDefaultActionCommand`) and View Designer +(`XSharpShadowDesignerViewFormCommand`) on a `.prg` that has a `.Designer.prg`. Forms created with **Add New Item** +open in the designer too: the template wizard opens them in the code editor, so `NewFormDesignerRedirect` switches a +form created within the last 30 s to the designer. + +**References:** the designer needs the assemblies of the project's packages. When they are not known yet (the solution +has just been opened, the NuGet restore is still running), `ShadowDesignerBridge.EnsureReferencesAsync` waits for the +IntelliSense stage of the solution load and then up to 20 s for the design-time build, with a status bar message. If +they are still missing it offers a build ("Build now?"; Tools > Options > X# Custom Editors > Other Editors, +"Auto-build for Shadow Designer", builds without asking) and waits up to 3 s after it. CPS runs command handlers while +it waits synchronously on the UI thread, so in that case the command returns at once and the designer opens in the +background; if it cannot be opened, the form opens in the code editor. + +### 4.10 Commands of the X# package + +Most commands of `ProjectPackage/Commands` are global (XPorters, UDC tester, repository window, …) and work unchanged. +Project-specific ones: + +- **Add .designer file** (context menu of a form `.prg`): splits a form written in a single file into `Form.prg` and + `Form.designer.prg`. For CPS projects the form is recognized through the code model (base classes of the first + class), the CodeDOM provider works on the path, and the new file needs no project item (globs). The form is parsed + first; when the project is still loading and the form class cannot be recognized, a message asks to try again. +- **Convert to Windows Form**, **Sync …** commands: work for CPS projects. +- **Edit project file**: only for MPFproj projects; CPS has its own "Edit Project File". + +### 4.11 Multi-targeting + +Projects with `` build and debug all target frameworks (framework dropdown, launch profiles). The +code model follows the **active configuration**, which CPS sets to the first entry of ``; the +framework dropdown only selects what F5 starts. IntelliSense therefore uses the references and defines of the first +target framework. This matches the default editor context of C#, which in addition offers a context dropdown in the +editor (one Roslyn project per target framework); the X# code model has one context per project. + +--- + +## 5. Source layout + +``` +src/VisualStudio/ProjectSystemCPS/ + XSharpProjectSystemPackage.cs package, CPS project type, project selector registration + XSharpCapabilities.cs capability names and the initial capabilities + Evaluation/ + XSharpCpsGlobalPropertiesProvider.cs global property XSharpCpsProjectSystem=true + Selection/ + XSharpProjectSelector.cs IVsProjectSelector: SDK-style → CPS, else MPFproj + ProvideProjectSelectorAttribute.cs pkgdef registration of the selector + SdkProjectFile.cs "is this an SDK-style project file" + XSharpMsBuildSupport.cs do the installed MSBuild support files support CPS + LanguageService/ + XSharpProjectAdapter.cs IXSharpProject: feeds XProject from CPS + XSharpCommandLine.cs Xsc switches → XParseOptions.FromVsValues + SourceFileWatcher.cs walks source files changed on disk + BuildErrorLoggerProvider.cs build errors for the squiggles + IntellisenseErrorStore.cs code model and build errors per file + IntellisenseErrorList.cs Error List provider + CommentTaskList.cs, CommentTokens.cs Task List + PropertyPages/ + XSharpProjectDesignerPageProvider.cs enables the project designer + XSharpDefineConstantsPropertiesProvider.cs persistence "XSharpDefineConstants" + KeyValuePairListEncoding.cs list format of the property editors + Imaging/ + XSharpImages.cs image monikers + XSharpProjectTreePropertiesProvider.cs icons + XSharpProjectTreePropertiesProviderSource.cs recalculates the icons when designer files change + ShadowDesigner/ WinForms designer through a companion C# project (4.9) + XSharp.ProjectSystemCPS.imagemanifest, Resources/*.png + +src/Compiler/src/Compiler/XSharpBuildTask/ + XSharp.DesignTime.targets CPS design-time integration + XSharp.CurrentVersion.targets, XSharp.CrossTargeting.targets gated import + XSharp.SDK.Props SDK item globs + Rules/*.xaml items schema, design-time data, property pages + +src/VisualStudio/ProjectSystemCPS.TestBed/ test projects and StartExp.cmd (7.1) +``` + +Other parts involved: + +- `src/VisualStudio/XSharpCodeModelXs` — the code model (`XProject`, `XSolution`, `IXSharpProject` in `Interfaces.prg`), + unchanged. +- `src/VisualStudio/ProjectPackage` — the X# package (language service registration, editors, commands, MPFproj + project type). `ProjectPackage2022` references `ProjectSystemCPS` and ships it in its VSIX. + `XSharpProjectFactory.CreateProject` rejects SDK-style projects that reach MPFproj. +- `src/VisualStudio/ProjectBase` — the MPFproj fork, for legacy projects only. +- `src/VisualStudio/CodeDomProvider` — the X# CodeDOM provider (designers, "Add .designer file"). + +Rules for changes: + +- `ProjectSystemCPS` references `XSharpCodeModelXs` (and `CodeDomProvider`), **never** `ProjectBase` or `ProjectPackage`; + the dependency goes the other way. +- Only the public CPS SDK; no reference to `Microsoft.VisualStudio.ProjectSystem.Managed*.dll`. +- Keep the public API of `XSharpCodeModelXs` unchanged. + +--- + +## 6. Building + +`ProjectSystemCPS` is part of `src/VSIntegration2022.sln` and is built with it (`src/buildvs2022.cmd Debug|Release`). +It is an SDK-style `net48` project; `ProjectPackage2022` puts the assembly, its pkgdef and the image manifest into its +VSIX. + +To build the VSIX alone (from `src/VisualStudio`, with the Visual Studio 2022 MSBuild): + +``` +msbuild ProjectPackage/ProjectPackage2022.csproj -p:Configuration=Debug -p:Platform=AnyCPU +``` + +- Pass `Platform=AnyCPU` (without a space); other values leave the `DEV17` define unset and give misleading errors. +- A Debug build **deploys** the VSIX into the experimental instance of Visual Studio 2022; the instance must be + closed. `-p:DeployTargetInstanceId=` deploys into another Visual Studio installation instead, for + example Visual Studio 2026 (the instance id from `vswhere -all`). +- The Debug build of `ProjectPackage2022` overwrites the Visual Studio 2019 manifest + `ProjectPackage/source.extension.vsixmanifest`; restore it (`git checkout`) before committing. +- The MSBuild support files (3.1) are built by `XSharp.Build.csproj` (`src/buildcompiler.cmd`). + +--- + +## 7. Testing and debugging + +### 7.1 Test bed + +`src/VisualStudio/ProjectSystemCPS.TestBed`: + +| Project / solution | Purpose | +|---|---| +| `CpsTestBed.xsproj`, `CpsTestBed.sln` | SDK-style console project (Core dialect, net48); the solution references it with the CPS GUID | +| `Legacy/LegacyTestBed.xsproj` | legacy project, must stay on MPFproj | +| `WinForms/WinFormsTestBed.xsproj` | SDK-style WinForms project (net8.0-windows) for the shadow designer | +| `VO/VOTestBed.xsproj` | SDK-style copy of the VO SDI Application template (net48, x86) for the VO designers | +| `SelectorTestBed.sln` | all four projects with the MPFproj GUID: tests the selector | + +`StartExp.cmd [solution]` starts the Visual Studio 2022 experimental instance for tests with the MSBuild support files +of the repository **without changing the installed X#**: it copies the installed X# `MsBuild` folder to +`Artifacts\CpsMsBuild`, copies the changed repository files over it (`XSharp.CurrentVersion.targets`, +`XSharp.CrossTargeting.targets`, `XSharp.DesignTime.targets`, `XSharp.SDK.Props`, `Rules`), deletes the image library +cache of the experimental instance and starts `devenv /rootSuffix Exp` with `XSharpMsBuildDir` pointing to the copy. +Default solution: `CpsTestBed.sln`. For Visual Studio 2026, start its `devenv.exe /rootSuffix Exp ` the same +way with `XSharpMsBuildDir` set to `Artifacts\CpsMsBuild`. + +After a test, reset the test bed: Visual Studio rewrites the project type GUIDs in `SelectorTestBed.sln` (4.1), and the +designers rewrite the files of `WinForms` and `VO`. + +For a "never built" state, delete `bin`, `Artifacts\obj\` and the `.vs` folder of the test bed (the X# +database `X#Model.xsdb`). + +### 7.2 Logs + +- **X# log:** `%TEMP%\XSharp.Intellisense\Project_*.log`, one file per Visual Studio session. Useful entries: + `XSharpProjectSelector: -> CPS|MPFproj`, `XSharpMsBuildSupport: …`, + `XSharpProjectAdapter: … design-time build, N options, M references, dialect …`, `… files added, … files removed`, + `… changed on disk, walking it again`, `ShadowDesignerBridge: …`, `ShadowDesignerCleanup: …`. +- **ActivityLog:** `%APPDATA%\Microsoft\VisualStudio\\ActivityLog.xml` (UTF-16). Package load failures + (`SetSite failed for package [XSharpProjectSystemPackage]`) are logged even without `/log`. Pass the solution before + `/log` (`devenv /log`), otherwise Visual Studio writes the log into the solution file. +- **MEF errors:** `%LOCALAPPDATA%\Microsoft\VisualStudio\\ComponentModelCache\Microsoft.VisualStudio.Default.err`. +- **Binlogs of Visual Studio builds:** start Visual Studio with `MSBUILDDEBUGENGINE=1` and `MSBUILDDEBUGPATH=`; + every build and design-time build writes a binlog there. + +### 7.3 Useful techniques + +- **Hangs:** attach non-invasively and dump the managed stacks: + `cdb -pv -p -c ".loadby sos clr; !EEStack -EE; qd"`. A UI thread in CPS `ProjectNode.HrInvoke` with no thread + working on the project tree means that the tree waits for a data source. +- **Assembly resolution:** Visual Studio resolves extension and platform assemblies through the per-instance + `%LOCALAPPDATA%\Microsoft\VisualStudio\\devenv.exe.config`, generated from the pkgdef `RuntimeConfiguration` + entries. The merged configuration is in `privateregistry.bin` of the instance; it can be read without admin rights + with `RegLoadAppKey` while Visual Studio is closed. +- **NuGet scenarios:** a `PackageReference` with `RestoreSources` (a local folder feed) and `RestorePackagesPath` in the + project makes the restore controllable (delete the packages folder and `project.assets.json`: restore at load; rename + the feed: the restore fails). Keep the paths short: NuGet inside Visual Studio does not extract files beyond 260 + characters. +- **GAC'd assemblies** (3.4) can be tested outside Visual Studio with a small console program that has + `` in its `.config` and `DEVPATH` pointing to the build output. + +--- + +## 8. Troubleshooting + +| Symptom | Cause and remedy | +|---|---| +| "The project file … is an SDK style project … Please run the X# setup program again." | The selector sent the project to MPFproj. Either the MSBuild support files are too old (install the matching X#; the X# log shows `XSharpMsBuildSupport: … does not support CPS`), or the CPS package did not load (ActivityLog: `SetSite failed for package [XSharpProjectSystemPackage]`). | +| `SetSite failed … Could not load file or assembly 'Microsoft.VisualStudio.ProjectSystem.VS, Version=17.0.0.0'` in an experimental instance | The configuration of that instance lacks the CPS `codeBase` entries (seen with an instance created before a Visual Studio update). Reset it: `CreateExpInstance.exe /Reset /VSInstance=_ /RootSuffix=Exp` (`VSSDK\VisualStudioIntegration\Tools\Bin`), then deploy again. This deletes the settings and extensions of that experimental instance. | +| No X# icons | The image library cache does not contain the X# manifest. Delete `ImageLibrary\ImageLibrary.cache` of the instance (`StartExp.cmd` does this) and restart. | +| No IntelliSense, completion empty | Check the X# log for the design-time build line of the project. If it is missing, check the Output window (Build) and the binlog of the design-time build; if the adapter reports 0 references, the design-time build failed. | +| "Could not find the form class …" from "Add .designer file" | The project is still loading and the references are not known yet; try again when completion works in the file. | +| The shadow designer shows "… is not contained within a project that supports code" | The companion project did not pick up the file; close the designer and open it again. Check the X# log (`ShadowDesignerBridge`). | +| The `.sln` changes the project type GUID of SDK-style projects | Expected, once (4.1). | +| "One or more projects in the solution were not loaded correctly" after Visual Studio was killed | The Output window (Solution) reports that the document restore is skipped once. Harmless. | + +--- + +## 9. Known limitations and open issues + +- **Multi-targeting:** IntelliSense uses the first target framework (4.11). A possible improvement is to follow the + framework selected in the dropdown. +- **"Include files" folder:** the virtual folder of MPFproj that lists `#include` files does not exist for CPS projects. +- **Default items turned off:** in SDK-style projects with `EnableDefaultItems=false` (or + `EnableDefaultCompileItems=false`), `.prg` files generated by the VO designers do not become part of the project, + because the adapter relies on the globs. The X# globs of `XSharp.SDK.Props` (VO binaries, `.rc`, `*.designer.prg`, + `*.resx`, `*.xaml.prg`) ignore these properties. +- **Shadow designer saves:** writing back rebuilds `Form.Designer.prg` from the fields and `InitializeComponent` only; + other members (`Dispose`), regions and field initializers of that file are lost (the same logic existed before for + the MPFproj SDK projects). +- **Shadow designer companion** is visible in Solution Explorer and written into the `.sln` while the solution is open. +- **Code model errors** are not reported by the code model, so the Error List shows only build errors (as with MPFproj). +- **Global usings editor:** `` items are edited in the project file (4.6). +- **Installation:** the VSIX and the MSBuild support files must match (3.3). +- **Visual Studio 2019** cannot load SDK-style X# projects. + +--- + +## 10. References + +- CPS: [microsoft/VSProjectSystem](https://github.com/microsoft/VSProjectSystem) (documentation of the Common Project + System and its extensibility points). +- The managed project system (C#, VB, F#): [dotnet/project-system](https://github.com/dotnet/project-system), the + reference for capabilities, rules, design-time builds, project selectors (`FSharpProjectSelector`) and the language + service integration. +- The code model contract: `src/VisualStudio/XSharpCodeModelXs/Interfaces.prg` (`IXSharpProject`) and the smallest + implementation, `OrphanedFilesProject.prg`.