Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 19 additions & 80 deletions .cursorrules
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
# Cursor project orientation for ProteoWizard (pwiz)
#
# This file provides Cursor-specific behavior guidance.
# For detailed coding conventions, style guidelines, and testing patterns,
# see STYLEGUIDE.md - the comprehensive style guide used by all AI tools.

[project]
name = "ProteoWizard (pwiz)"
Expand Down Expand Up @@ -51,6 +55,13 @@ docs = [
- Use fenced code blocks only for relevant snippets or commands.
- Default to read-only discovery before edits; then apply focused edits.

[build_and_testing]
- LLM agents CANNOT build or run this project directly.
- Build and test operations must be performed by the developer using Visual Studio.
- When code changes are made, request that the developer build and test the changes.
- Do not attempt to run quickbuild.bat, bs.bat, or any build commands.
- Do not attempt to run tests or execute the application.

[file_headers]
standard_header = """
/*
Expand Down Expand Up @@ -82,95 +93,23 @@ ai_attribution = {
}

[testing]
unit_tests = {
projects = ["Test", "TestData"]
base_classes = ["AbstractUnitTest", "AbstractUnitTestEx"]
characteristics = "Fast execution, no UI overhead (no SkylineWindow)"
test_project = "General unit tests, may access file system but no UI"
testdata_project = "Unit tests with mass spectrometry data files, must access file system"
}
functional_tests = {
projects = ["TestFunctional", "TestPerf", "TestTutorial"]
base_classes = ["AbstractFunctionalTest", "AbstractFunctionalTestEx"]
overhead = "Significant - must show/destroy SkylineWindow and drive UI"
performance = "Much slower than unit tests (in-memory, no file system/network)"
consolidation = "Prefer multiple validations in single test class with one [TestMethod]"
helpers = "Use private helper methods for different validation concerns"
avoid = "Separate test classes for related functional validations that could share SkylineWindow"
}
test_perf = {
purpose = "Performance tests with large datasets (>100MB)"
requirements = ">100GB free disk space, Downloads folder storage"
frequency = "Run less often than standard functional tests"
}
test_tutorial = {
purpose = "Automated tutorial implementation and screenshot capture"
source = "Skyline/Documentation/Tutorials/*"
screenshots = "s-01.png, s-02.png, cover.png in language subfolders (en, ja, zh-CHS)"
examples = ["TestIrtTutorial for iRT tutorial"]
}
# See STYLEGUIDE.md for comprehensive testing guidelines
reference = "STYLEGUIDE.md"

[style_skyline_csharp]
control_flow = {
single_line_if = false
allow_no_braces_single_statement = true
}
member_order = [
"static_fields",
"static_public_methods",
"private_instance_fields",
"constructors",
"public_instance_api",
"private_helpers_after_usage"
]
naming = {
descriptive_names = true
avoid_abbreviations = true
private_instance_field_prefix = "_"
private_static_field_prefix = "_"
interface_prefix = "I"
type_parameter_prefix = "T"
enum_member_style = "snake_case"
}
formatting = {
preserve_existing_indentation = true
avoid_unrelated_reformatting = true
tabs_disallowed = true
}
# See STYLEGUIDE.md for comprehensive C# coding conventions
reference = "STYLEGUIDE.md"
docs = [
"STYLEGUIDE.md"
]

[localization]
resource_system = {
primary_location = "pwiz_tools/Skyline/Properties/Resources.resx"
auxiliary_locations = [
"pwiz_tools/Skyline/Menus/MenusResources.resx",
"pwiz_tools/Skyline/FileUI/FileUIResources.resx",
"pwiz_tools/Skyline/Controls/Graphs/GraphResources.resx"
]
organization_tool = "pwiz_tools/Skyline/Executables/AssortResources/ResourceAssorter.cs"
static_analysis_test = "pwiz_tools/Skyline/Test/CodeInspectionTest.cs"
}
guidelines = [
"For folder-specific strings (e.g., menu items), add directly to <folder-name>Resources.resx (e.g., MenusResources.resx)",
"For general UI strings, add to Properties/Resources.resx and let ResourceAssorter organize them",
"ResourceAssorter moves strings from Properties/Resources.resx to appropriate <folder-name>Resources.resx files based on usage context",
"Generate resource keys by replacing non-alphanumerics with '_' and trimming",
"Prefer context prefixes like SkylineWindow_ or EditMenu_ where appropriate",
"CRITICAL: When adding .resx entries, also add corresponding public static string properties to .Designer.cs in ALPHABETICAL ORDER",
"Compiler will fail with CS0117 errors if Designer.cs is not updated",
"ReSharper static analysis and CodeInspectionTest.cs verify proper resource placement",
"AssortResources.exe can clean up misplaced resources if needed"
]
# See STYLEGUIDE.md for detailed localization guidelines
reference = "STYLEGUIDE.md"

[ui_guidelines]
menu_items = [
"All menuMain items and submenus must have mnemonics (e.g., '&Keyboard Shortcuts')",
"Use title-case for menu text and action buttons (e.g., 'Keyboard Shortcuts')",
"Only menuMain items can have mnemonics and keyboard shortcuts",
"Context menus should not have mnemonics or keyboard shortcuts"
]
# See STYLEGUIDE.md for detailed UI guidelines
reference = "STYLEGUIDE.md"

[executables]
note = "Projects under pwiz_tools/Skyline/Executables are separate solutions (stand-alone tools and utilities). Not built by Skyline.sln; prefer Skyline conventions unless local overrides are required."
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,15 @@ Full setup and troubleshooting guide: [How to Build Skyline](https://skyline.ms/

Skyline C# coding conventions: see `STYLEGUIDE.md`.

### Threading Guidelines

The project avoids `async`/`await` and .NET Task support in favor of deterministic threading:

- **Use `CommonActionUtil.RunAsync()`** (in Shared projects) or `ActionUtil.RunAsync()` (in Skyline) instead of `Task.Run()` or `async`/`await`
- **Avoid .NET thread pool** - Use allocated threads for more deterministic behavior and easier debugging
- **Prefer synchronous operations** on background threads when possible
- **Thread marshaling** - Use `Invoke()` for UI thread operations from background threads

Executables note: Projects under `pwiz_tools/Skyline/Executables` are separate solutions (most build stand‑alone EXEs or developer tools, some ship with Skyline). They are not built by `Skyline.sln`, but should generally follow the same coding conventions unless a local project override is required. See the Tool Store: https://skyline.ms/tools.url

EditorConfig: Repository-wide `.editorconfig` enforces core C# naming/formatting so separate solutions (including `pwiz_tools/Skyline/Executables`) inherit consistent style in Visual Studio.
Expand Down
3 changes: 3 additions & 0 deletions STYLEGUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

This guide captures Skyline-specific C# conventions to keep AI- and human-authored code consistent with `pwiz_tools/Skyline`.

**Universal AI Guidelines**: This file serves as the comprehensive style guide for all AI tools (Cursor, Claude Code, GitHub Copilot, ChatGPT, etc.). The `.cursorrules` file references this document to avoid duplication.

## Control flow
- If statements must not be single-line. If braces are omitted, keep the condition and body on separate lines.

Expand Down Expand Up @@ -73,6 +75,7 @@ EditorConfig
- If a specific Executables project needs different rules, add a minimal project-level `.editorconfig` or project `.DotSettings` override local to that solution only.

## Resource strings (localization)
- **CRITICAL: ALL user-facing text must be in .resx files - NO string literals in .cs files**
- Add new UI strings for menus/dialogs/pages to `pwiz_tools/Skyline/Menus/MenusResources.resx`.
- Strings will be translated to Chinese/Japanese via our translation process; use clear, concise English.
- Generate resource keys from the English text in a ReSharper-like way:
Expand Down
6 changes: 6 additions & 0 deletions pwiz_tools/Shared/Common/Common.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
<OldToolsVersion>3.5</OldToolsVersion>
<UpgradeBackupLocation />
<TargetFrameworkProfile />
<RuntimeIdentifiers>win;win-x64;win-x86</RuntimeIdentifiers>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
<DebugSymbols>true</DebugSymbols>
Expand Down Expand Up @@ -586,6 +587,11 @@
<None Include="Resources\maximize.bmp" />
<None Include="Resources\MatchCase.bmp" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Web.WebView2">
<Version>1.0.2420.47</Version>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MSGraph\MSGraph.csproj">
<Project>{26CFD1FF-F4F7-4F66-B5B4-E686BDB9B34E}</Project>
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/*
* Original author: Nicholas Shulman <nicksh .at. u.washington.edu>,
* MacCoss Lab, Department of Genome Sciences, UW
* AI assistance: Cursor (Claude Sonnet 4) <cursor .at. anysphere.co>
*
* Copyright 2016 University of Washington - Seattle, WA
*
Expand All @@ -17,31 +18,156 @@
* limitations under the License.
*/
using System;
using System.Windows.Forms;
using Microsoft.Web.WebView2.Core;
using pwiz.Common.GUI;
using pwiz.Common.SystemUtil;

namespace pwiz.Common.DataBinding.Controls.Editor
{
public partial class DocumentationViewer : CommonFormEx
{
private string _documentationHtml = string.Empty;
private string _webView2Html = string.Empty;

/// <summary>
/// Static property to set the WebView2 environment directory for test scenarios.
/// Tests should set this before creating a DocumentationViewer and reset to null when done.
/// </summary>
public static string TestWebView2EnvironmentDirectory { get; set; }

public DocumentationViewer(bool showInTaskBar)
{
InitializeComponent();

// WINDOWS 10 UPDATE HACK: Because Windows 10 update version 1803 causes unparented non-ShowInTaskbar windows to leak GDI and User handles
// WINDOWS 10 UPDATE HACK: Because Windows 10 update version 1803 causes un-parented non-ShowInTaskbar windows to leak GDI and User handles
ShowInTaskbar = showInTaskBar;
}

public String DocumentationHtml
public bool IsWebView2Initialized { get; private set; }

public string DocumentationHtml
{
get => _documentationHtml;
set
{
_documentationHtml = value;

if (IsWebView2Initialized)
NavigateToHtml();
}
}

protected override void OnHandleCreated(EventArgs e)
{
InitializeWebView2();

base.OnHandleCreated(e);
}

private void InitializeWebView2()
{
try
{
// Initialize WebView2 environment on UI thread to avoid COM threading issues
var environment = InitWebView2Environment();
var task = webView2.EnsureCoreWebView2Async(environment);

// Initialize WebView2 on a background thread
CommonActionUtil.RunAsync(() =>
{
try
{
// Initialize WebView2 with the environment
task.Wait();
IsWebView2Initialized = true;

// Navigate to the HTML if it was set before initialization
if (!string.IsNullOrEmpty(_documentationHtml))
{
RunUIAsync(NavigateToHtml);
}
}
catch (Exception ex)
{
RunUIAsync(() => CommonAlertDlg.ShowException(this, ex));
}
});
}
catch (Exception ex)
{
CommonAlertDlg.ShowException(this, ex);
}
}

private CoreWebView2Environment InitWebView2Environment()
{
if (string.IsNullOrEmpty(TestWebView2EnvironmentDirectory))
return null;

// Create environment with custom user data folder on UI thread
return CoreWebView2Environment.CreateAsync(null, TestWebView2EnvironmentDirectory).Result;
}

private void RunUIAsync(Action act)
{
CommonActionUtil.SafeBeginInvoke(this, act);
}

private void NavigateToHtml()
{
get { return webBrowser1.DocumentText; }
set { webBrowser1.DocumentText = value; }
if (IsWebView2Initialized && !string.IsNullOrEmpty(_documentationHtml))
{
webView2.NavigateToString(_documentationHtml);
}
}

protected override void OnFormClosed(FormClosedEventArgs e)
#region Test helpers

/// <summary>
/// Gets the HTML content currently displayed in the WebView2 control.
/// This is useful for testing to verify that the content was actually rendered.
/// </summary>
public string GetWebView2HtmlContent(int minLen = 1)
{
base.OnFormClosed(e);
Dispose();
// Wait for the HTML content of the control to stabilize as matching the documentation HTML
// Normalize line endings for comparison since WebView2 may normalize \r\n to \n
if (_webView2Html.Length >= minLen)
return _webView2Html;

if (!IsWebView2Initialized || webView2?.CoreWebView2 == null)
return string.Empty;

try
{
// Execute JavaScript to get the document's HTML content
var task = webView2.CoreWebView2.ExecuteScriptAsync(@"document.documentElement.outerHTML");
CommonActionUtil.RunAsync(() =>
{
try
{
var encodedHtml = task.Result;
// Decode Unicode escape sequences (e.g., \u003C -> <)
var decodedHtml = System.Text.RegularExpressions.Regex.Unescape(encodedHtml);
// Remove leading and trailing quotation marks from JavaScript string
var rawHtml = decodedHtml.Trim('"');
// Normalize line endings to match WebView2's normalization
_webView2Html = rawHtml;
}
catch (Exception ex)
{
// Log cleanup errors for debugging but don't throw
System.Diagnostics.Debug.WriteLine($@"Failed to get WebView2 outerHtml: {ex}");
}
});
}
catch (Exception ex)
{
// Log cleanup errors for debugging but don't throw
System.Diagnostics.Debug.WriteLine($@"Failed to execute script on WebView2: {ex}");
}
return string.Empty;
}

#endregion
}
}
Loading