diff --git a/docs/plans/SCREENSHOT-FIX-PLAN.md b/docs/plans/SCREENSHOT-FIX-PLAN.md
deleted file mode 100644
index cc50c899..00000000
--- a/docs/plans/SCREENSHOT-FIX-PLAN.md
+++ /dev/null
@@ -1,102 +0,0 @@
-# Screenshot Fix Plan
-
-Feature doc pages with broken screenshots have been **excluded from the website** (`eleventyExcludeFromCollections: true`). Only pages with verified working screenshots are live.
-
-## Current Status
-
-| Page | VS Code Screenshot | Status | Issue |
-|------|-------------------|--------|-------|
-| Homepage | `vscode-homepage-page.png` | LIVE | Shows Calculator.cs with syntax highlighting |
-| Code Completions | `vscode-completions-page.png` | LIVE | Shows IntelliSense dropdown with suggestions |
-| Diagnostics | `vscode-diagnostics-page.png` | EXCLUDED | No red squiggly underlines visible |
-| Hover | `vscode-hover-page.png` | EXCLUDED | No hover tooltip visible |
-| Go to Definition | `vscode-go-to-definition-page.png` | EXCLUDED | No peek definition overlay visible |
-| Profiler | `vscode-profiler-page.png` | EXCLUDED | No profiler sidebar panel visible |
-
-All Zed screenshots show plain code only — Zed cannot trigger features programmatically.
-
-## Root Cause Analysis
-
-The capture script (`src/editors/vscode/screenshots/capture.mjs`) uses `code serve-web` with Playwright. The SharpLsp VSIX is installed to `~/.vscode-server/extensions/` and the extension DOES activate (status bar shows "SharpLsp" and "C#"). The LSP sidecar IS running.
-
-However:
-
-### Diagnostics — LIKELY FIXED (pull diagnostics now implemented)
-Pull diagnostics (`textDocument/diagnostic` and `workspace/diagnostic`) are now implemented in `pull_diagnostics.rs`. **Re-test the screenshot capture to verify diagnostics appear in `code serve-web`.**
-
-### Hover — `textDocument/hover` returns empty or fails silently
-Mouse hover over code tokens (verified correct element targeting via Playwright `boundingBox()`) and keyboard commands (`Cmd+K Cmd+I`, command palette "Show Hover") both fail to produce a visible `.monaco-hover` widget. The LSP sidecar may:
-1. Return an empty hover result for tokens in the test workspace
-2. Fail to resolve the symbol because MSBuildWorkspace hasn't fully loaded the project
-3. Time out before the hover response arrives
-
-**Fix**: Debug the LSP hover handler with the test workspace. Check if `textDocument/hover` returns data for symbols in `Calculator.cs`. A `.csproj` was added (`TestFixtures.csproj`) but the sidecar may need a `.sln` or may fail to locate the project.
-
-### Go to Definition — Peek Definition doesn't render
-`Alt+F12` and command palette "Peek Definition" don't produce a visible peek widget. Similar to hover — the LSP may return empty results or the sidecar hasn't loaded the workspace.
-
-**Fix**: Same as hover — verify the sidecar loads the test workspace and returns definition locations.
-
-### Profiler — SharpLsp sidebar not accessible
-The SharpLsp activity bar icon (`sharplsp-explorer`) is not visible in `code serve-web`. The extension contributes views but they may not render in the web UI. The capture function tries to click the "SharpLsp" tab and "Profiler" tree item but neither is found.
-
-**Fix**: Check if VS Code web supports `viewsContainers` and `views` contributed by extensions. May need a different approach for the profiler screenshot.
-
-## Test Workspace
-
-The test workspace at `src/editors/vscode/test-fixtures/workspace/` now contains:
-- `Calculator.cs` — main test file for screenshots
-- `TestFixtures.csproj` — .NET 9.0 project file (added to enable Roslyn sidecar)
-- `Empty.cs`, `Nested.cs`, `Greeter.fs` — additional test files
-
-## Capture Script
-
-`src/editors/vscode/screenshots/capture.mjs` handles:
-1. Installing the SharpLsp VSIX to both desktop and serve-web extensions directories
-2. Writing workspace settings with `sharplsp.lspPath` pointing to `target/release/sharplsp`
-3. Launching `code serve-web` and connecting via Playwright
-4. Per-screenshot capture functions that trigger features
-5. 30-second wait for LSP sidecar initialization
-
-## Re-enabling Excluded Pages
-
-When a feature's screenshot is fixed:
-1. Replace `eleventyExcludeFromCollections: true` with the original `eleventyNavigation` frontmatter
-2. Add the page to the footer in `src/website/src/_data/navigation.json`
-3. Add the page to `SCREENSHOT_PAGES` in `src/website/tests/screenshots.spec.js`
-
-### Frontmatter to restore
-
-```yaml
-# diagnostics.md
-eleventyNavigation:
- key: Diagnostics
- order: 5
-
-# hover.md
-eleventyNavigation:
- key: Hover and Quick Info
- order: 6
-
-# go-to-definition.md
-eleventyNavigation:
- key: Go to Definition
- order: 7
-
-# profiler.md
-eleventyNavigation:
- key: Profiler
- order: 8
-```
-
-## TODO
-
-- [x] Implement pull diagnostics to fix `code serve-web` diagnostics — `pull_diagnostics.rs`
-- [ ] Re-run screenshot capture and verify diagnostics squiggles appear
-- [ ] Debug hover in `code serve-web` — check sidecar loads test workspace
-- [ ] Debug go-to-definition in `code serve-web`
-- [ ] Debug profiler sidebar in `code serve-web` — check `viewsContainers` API support
-- [ ] Re-enable diagnostics page (restore frontmatter, add to nav + test list)
-- [ ] Re-enable hover page
-- [ ] Re-enable go-to-definition page
-- [ ] Re-enable profiler page
diff --git a/docs/specs/DESIGN-SYSTEM.md b/docs/specs/DESIGN-SYSTEM.md
index 843b1b03..c6634fe9 100644
--- a/docs/specs/DESIGN-SYSTEM.md
+++ b/docs/specs/DESIGN-SYSTEM.md
@@ -126,7 +126,7 @@ Page-level grids and marketing card styles must not leak into `.prose`. Do not r
Screenshots and video of a SharpLsp feature show the real extension, never a mock-up.
-- **Capture.** Product films are shot by `src/website/tools/profiler-film/` (its README says how). Every shot runs on `kit/set.mjs`'s `onSet`: an isolated VS Code (its own user-data and extensions directories, never the user's editor) running the packaged VSIX against a real .NET demo from `demos/`. A walkthrough is one file in `films/`, a list of scenes that `film.mjs` shoots as real-time clips and publishes; `kit/acts/` tells the shared stories (the CPU and memory loops, the language features, the Solution Explorer), so each film supplies only its words, its demo and its edits. The profiler films run on the C# Storefront demo and its F# twin, FsStorefront; the language films run on `Shop.slnx`, both in one solution, and show F# before C#. Every F# claim the docs make is shown on F# code. The composited one-minute film is `films/profiler/`: `capture.mjs` records stills and screencasts, and `render.mjs` turns them into the site's assets.
+- **Capture.** Product films are shot by `src/website/tools/profiler-film/` (its README says how). Every shot runs on `kit/set.mjs`'s `onSet`: an isolated VS Code (its own user-data and extensions directories, never the user's editor) running the packaged VSIX against a real .NET demo from `demos/`. A walkthrough is one file in `films/`, a list of scenes that `film.mjs` shoots as real-time clips and publishes; `kit/acts/` tells the shared stories (the CPU and memory loops, the language features, the Solution Explorer), so each film supplies only its words, its demo and its edits. The profiler films run on the C# Storefront demo and its F# twin, FsStorefront; the language films run on `Shop.slnx`, both in one solution, and show F# before C#. Every F# claim the docs make is shown on F# code. Every feature page's still, and every figure in the launch post, is shot the same way by `films/doc-stills.mjs`; no page shows a capture from any other source. The composited one-minute film is `films/profiler/`: `capture.mjs` records stills and screencasts, and `render.mjs` turns them into the site's assets.
- **Honesty.** Footage plays in real time unless the frame says otherwise: every fast-forwarded stretch carries an on-screen speed marker, and captions name time-lapses. Motion graphics (title cards, camera moves, highlights, the cursor) frame the footage but never alter what the editor shows.
- **Privacy.** No frame may show a user name, home directory or hostname. The demo runs from a neutral temporary path, and the editor runs on a copy-on-write clone of the .NET install under that path, with its login-shell environment off, so every path it prints is neutral. A terminal appears only where the feature runs in one (F# Interactive, a build task, a contributor's command line), and a shell terminal starts with no profile and a bare `$ ` prompt.
- **Formats.**
diff --git a/src/website/src/assets/screenshots/code-folding.png b/src/website/src/assets/screenshots/code-folding.png
deleted file mode 100644
index 05aaa7d9..00000000
Binary files a/src/website/src/assets/screenshots/code-folding.png and /dev/null differ
diff --git a/src/website/src/assets/screenshots/docs/architecture.webp b/src/website/src/assets/screenshots/docs/architecture.webp
new file mode 100644
index 00000000..1f723c6b
Binary files /dev/null and b/src/website/src/assets/screenshots/docs/architecture.webp differ
diff --git a/src/website/src/assets/screenshots/docs/code-folding.webp b/src/website/src/assets/screenshots/docs/code-folding.webp
new file mode 100644
index 00000000..c9a80f74
Binary files /dev/null and b/src/website/src/assets/screenshots/docs/code-folding.webp differ
diff --git a/src/website/src/assets/screenshots/docs/completions-csharp.webp b/src/website/src/assets/screenshots/docs/completions-csharp.webp
new file mode 100644
index 00000000..f4a3f329
Binary files /dev/null and b/src/website/src/assets/screenshots/docs/completions-csharp.webp differ
diff --git a/src/website/src/assets/screenshots/docs/completions.webp b/src/website/src/assets/screenshots/docs/completions.webp
new file mode 100644
index 00000000..92facf19
Binary files /dev/null and b/src/website/src/assets/screenshots/docs/completions.webp differ
diff --git a/src/website/src/assets/screenshots/docs/configuration.webp b/src/website/src/assets/screenshots/docs/configuration.webp
new file mode 100644
index 00000000..9a5b6b13
Binary files /dev/null and b/src/website/src/assets/screenshots/docs/configuration.webp differ
diff --git a/src/website/src/assets/screenshots/docs/context-menus.webp b/src/website/src/assets/screenshots/docs/context-menus.webp
new file mode 100644
index 00000000..19e6caa2
Binary files /dev/null and b/src/website/src/assets/screenshots/docs/context-menus.webp differ
diff --git a/src/website/src/assets/screenshots/docs/definition.webp b/src/website/src/assets/screenshots/docs/definition.webp
new file mode 100644
index 00000000..7a860a56
Binary files /dev/null and b/src/website/src/assets/screenshots/docs/definition.webp differ
diff --git a/src/website/src/assets/screenshots/docs/diagnostics.webp b/src/website/src/assets/screenshots/docs/diagnostics.webp
new file mode 100644
index 00000000..95f1b34c
Binary files /dev/null and b/src/website/src/assets/screenshots/docs/diagnostics.webp differ
diff --git a/src/website/src/assets/screenshots/docs/editors.webp b/src/website/src/assets/screenshots/docs/editors.webp
new file mode 100644
index 00000000..280db5c3
Binary files /dev/null and b/src/website/src/assets/screenshots/docs/editors.webp differ
diff --git a/src/website/src/assets/screenshots/docs/hover.webp b/src/website/src/assets/screenshots/docs/hover.webp
new file mode 100644
index 00000000..184cabb0
Binary files /dev/null and b/src/website/src/assets/screenshots/docs/hover.webp differ
diff --git a/src/website/src/assets/screenshots/docs/navigation.webp b/src/website/src/assets/screenshots/docs/navigation.webp
new file mode 100644
index 00000000..96b4ce3d
Binary files /dev/null and b/src/website/src/assets/screenshots/docs/navigation.webp differ
diff --git a/src/website/src/assets/screenshots/docs/nuget-browse.webp b/src/website/src/assets/screenshots/docs/nuget-browse.webp
new file mode 100644
index 00000000..cd82aff7
Binary files /dev/null and b/src/website/src/assets/screenshots/docs/nuget-browse.webp differ
diff --git a/src/website/src/assets/screenshots/docs/nuget-installed.webp b/src/website/src/assets/screenshots/docs/nuget-installed.webp
new file mode 100644
index 00000000..6e16bbdc
Binary files /dev/null and b/src/website/src/assets/screenshots/docs/nuget-installed.webp differ
diff --git a/src/website/src/assets/screenshots/docs/nuget-package.webp b/src/website/src/assets/screenshots/docs/nuget-package.webp
new file mode 100644
index 00000000..a73dd28c
Binary files /dev/null and b/src/website/src/assets/screenshots/docs/nuget-package.webp differ
diff --git a/src/website/src/assets/screenshots/docs/refactoring-csharp.webp b/src/website/src/assets/screenshots/docs/refactoring-csharp.webp
new file mode 100644
index 00000000..bb271a4d
Binary files /dev/null and b/src/website/src/assets/screenshots/docs/refactoring-csharp.webp differ
diff --git a/src/website/src/assets/screenshots/docs/refactoring.webp b/src/website/src/assets/screenshots/docs/refactoring.webp
new file mode 100644
index 00000000..c7bcc948
Binary files /dev/null and b/src/website/src/assets/screenshots/docs/refactoring.webp differ
diff --git a/src/website/src/assets/screenshots/docs/solution-explorer.webp b/src/website/src/assets/screenshots/docs/solution-explorer.webp
new file mode 100644
index 00000000..dec2b399
Binary files /dev/null and b/src/website/src/assets/screenshots/docs/solution-explorer.webp differ
diff --git a/src/website/src/assets/screenshots/docs/type-members.webp b/src/website/src/assets/screenshots/docs/type-members.webp
new file mode 100644
index 00000000..8012d58f
Binary files /dev/null and b/src/website/src/assets/screenshots/docs/type-members.webp differ
diff --git a/src/website/src/assets/screenshots/nested-classes.png b/src/website/src/assets/screenshots/nested-classes.png
deleted file mode 100644
index ebe1f0e6..00000000
Binary files a/src/website/src/assets/screenshots/nested-classes.png and /dev/null differ
diff --git a/src/website/src/assets/screenshots/solution-explorer.png b/src/website/src/assets/screenshots/solution-explorer.png
deleted file mode 100644
index 524554e8..00000000
Binary files a/src/website/src/assets/screenshots/solution-explorer.png and /dev/null differ
diff --git a/src/website/src/assets/screenshots/vscode-architecture-page.png b/src/website/src/assets/screenshots/vscode-architecture-page.png
deleted file mode 100644
index b14b426a..00000000
Binary files a/src/website/src/assets/screenshots/vscode-architecture-page.png and /dev/null differ
diff --git a/src/website/src/assets/screenshots/vscode-completions-page.png b/src/website/src/assets/screenshots/vscode-completions-page.png
deleted file mode 100644
index c16fa9b7..00000000
Binary files a/src/website/src/assets/screenshots/vscode-completions-page.png and /dev/null differ
diff --git a/src/website/src/assets/screenshots/vscode-configuration-page.png b/src/website/src/assets/screenshots/vscode-configuration-page.png
deleted file mode 100644
index a98656f3..00000000
Binary files a/src/website/src/assets/screenshots/vscode-configuration-page.png and /dev/null differ
diff --git a/src/website/src/assets/screenshots/vscode-context-menu-open-project.png b/src/website/src/assets/screenshots/vscode-context-menu-open-project.png
deleted file mode 100644
index 7780f1c5..00000000
Binary files a/src/website/src/assets/screenshots/vscode-context-menu-open-project.png and /dev/null differ
diff --git a/src/website/src/assets/screenshots/vscode-diagnostics-page.png b/src/website/src/assets/screenshots/vscode-diagnostics-page.png
deleted file mode 100644
index 033aa004..00000000
Binary files a/src/website/src/assets/screenshots/vscode-diagnostics-page.png and /dev/null differ
diff --git a/src/website/src/assets/screenshots/vscode-editors-page.png b/src/website/src/assets/screenshots/vscode-editors-page.png
deleted file mode 100644
index a7def709..00000000
Binary files a/src/website/src/assets/screenshots/vscode-editors-page.png and /dev/null differ
diff --git a/src/website/src/assets/screenshots/vscode-getting-started-page.png b/src/website/src/assets/screenshots/vscode-getting-started-page.png
deleted file mode 100644
index d59383f8..00000000
Binary files a/src/website/src/assets/screenshots/vscode-getting-started-page.png and /dev/null differ
diff --git a/src/website/src/assets/screenshots/vscode-go-to-definition-page.png b/src/website/src/assets/screenshots/vscode-go-to-definition-page.png
deleted file mode 100644
index 7dbd9fc8..00000000
Binary files a/src/website/src/assets/screenshots/vscode-go-to-definition-page.png and /dev/null differ
diff --git a/src/website/src/assets/screenshots/vscode-hover-page.png b/src/website/src/assets/screenshots/vscode-hover-page.png
deleted file mode 100644
index e991455b..00000000
Binary files a/src/website/src/assets/screenshots/vscode-hover-page.png and /dev/null differ
diff --git a/src/website/src/assets/screenshots/vscode-nuget-browse.png b/src/website/src/assets/screenshots/vscode-nuget-browse.png
deleted file mode 100644
index e15921f9..00000000
Binary files a/src/website/src/assets/screenshots/vscode-nuget-browse.png and /dev/null differ
diff --git a/src/website/src/assets/screenshots/vscode-nuget-installed.png b/src/website/src/assets/screenshots/vscode-nuget-installed.png
deleted file mode 100644
index e769a1a9..00000000
Binary files a/src/website/src/assets/screenshots/vscode-nuget-installed.png and /dev/null differ
diff --git a/src/website/src/assets/screenshots/vscode-nuget-package-details.png b/src/website/src/assets/screenshots/vscode-nuget-package-details.png
deleted file mode 100644
index 79c32d0c..00000000
Binary files a/src/website/src/assets/screenshots/vscode-nuget-package-details.png and /dev/null differ
diff --git a/src/website/src/assets/screenshots/vscode-nuget-search.png b/src/website/src/assets/screenshots/vscode-nuget-search.png
deleted file mode 100644
index ba97671e..00000000
Binary files a/src/website/src/assets/screenshots/vscode-nuget-search.png and /dev/null differ
diff --git a/src/website/src/assets/screenshots/vscode-profiler-context-menu.png b/src/website/src/assets/screenshots/vscode-profiler-context-menu.png
deleted file mode 100644
index dadc6c73..00000000
Binary files a/src/website/src/assets/screenshots/vscode-profiler-context-menu.png and /dev/null differ
diff --git a/src/website/src/assets/screenshots/vscode-profiler-page.png b/src/website/src/assets/screenshots/vscode-profiler-page.png
deleted file mode 100644
index 67ff7fb9..00000000
Binary files a/src/website/src/assets/screenshots/vscode-profiler-page.png and /dev/null differ
diff --git a/src/website/src/assets/screenshots/vscode-refactoring.png b/src/website/src/assets/screenshots/vscode-refactoring.png
deleted file mode 100644
index 70d94c48..00000000
Binary files a/src/website/src/assets/screenshots/vscode-refactoring.png and /dev/null differ
diff --git a/src/website/src/assets/screenshots/vscode-solution-explorer-context-menu.png b/src/website/src/assets/screenshots/vscode-solution-explorer-context-menu.png
deleted file mode 100644
index 85848f62..00000000
Binary files a/src/website/src/assets/screenshots/vscode-solution-explorer-context-menu.png and /dev/null differ
diff --git a/src/website/src/blog/introducing-sharplsp.md b/src/website/src/blog/introducing-sharplsp.md
index 799938aa..ae5eefce 100644
--- a/src/website/src/blog/introducing-sharplsp.md
+++ b/src/website/src/blog/introducing-sharplsp.md
@@ -109,8 +109,8 @@ The first thing you see when you open a .NET workspace in SharpLsp is a proper S
This matters more than it sounds. Visual Studio's Solution Explorer has been the mental model for .NET projects since 2002. It represents how .NET developers think about their code — not as files in folders, but as types within namespaces within projects within solutions. Senior engineers who move between Visual Studio on Windows and any other tool on macOS or Linux feel the loss of this model acutely. Every file-tree-based alternative forces a context switch. SharpLsp refuses that context switch. The solution hierarchy is the primary navigation surface, derived directly from MSBuild, and it stays in sync as you edit.
-
- Solution Explorer rendering a real .sln file with full project and type hierarchy.
+
+ Solution Explorer rendering a real solution with its full project and type hierarchy, F# and C# side by side.
The tree also reflects type-level structure. Namespaces expand to show types. Types expand to show members. Reference counts appear inline on each node, giving you a continuous ambient signal about what is used and what might be dead code — without having to run an explicit Find All References query on every symbol you are curious about. When the solution changes on disk — a new file added, a project reference modified — the tree updates without requiring a restart.
@@ -122,7 +122,7 @@ Code folding in most editors is line-range-based — a blunt instrument that fol
The fold ranges are computed entirely in the Rust host without any round-trip to the compiler sidecars. This means they resolve in under one millisecond regardless of whether the workspace has finished loading or whether the compiler is in the middle of a background build. Folding is structural information, not semantic information — and SharpLsp treats it as such, routing it to the fastest possible handler.
-
+ Code folding powered by tree-sitter — sub-millisecond, no compiler needed.
@@ -135,7 +135,7 @@ Completions pull from the full Roslyn semantic model — the same `CompletionSer
When you type a type name that is not yet imported, SharpLsp surfaces it with an auto-import suggestion. When you type `.`, you get the full member list including inherited members and extension methods from all imported namespaces. When you are in a LINQ chain, the completion context is aware of the element type and filters accordingly. This is what Roslyn knows — and SharpLsp exposes all of it directly.
-
+ Semantic completions powered by Roslyn's CompletionService — the same engine behind Visual Studio.
@@ -146,8 +146,8 @@ Completion latency targets are under 100ms at the 50th percentile and under 200m
Hover shows the full method or type signature alongside its XML documentation — parameters, return value description, remarks, exceptions, and examples. This is rendered from the XML doc comments in your source or from the documentation embedded in NuGet packages when hovering over third-party APIs. Roslyn's `DocumentationCommentCompiler` surfaces all of this, including inherited documentation from base classes and interface implementations.
-
- Hover rendering XML doc comments with parameter and return value documentation.
+
+ Hover rendering XML doc comments with parameter documentation.
The Profiler panel visible in the sidebar is not incidental decoration. SharpLsp surfaces all running .NET processes on the machine, so you always know what is executing. When you are tracking down a performance regression or verifying that a service has actually stopped, this ambient process list is immediately useful — you do not have to open Activity Monitor or run `ps aux | grep dotnet`.
@@ -163,8 +163,8 @@ Go to Definition navigates across the full solution graph — through project re
This is not a text grep or an index lookup. It is a Roslyn symbol resolution — the same mechanism Visual Studio uses internally. It handles partial classes, source generators, and nested types correctly because Roslyn's workspace model accounts for all of them. When you press Go to Definition on a symbol defined in a source generator, SharpLsp navigates to the generated source — not the generator implementation. When you navigate into a BCL type, the C# sidecar decompiles the metadata using ICSharpCode.Decompiler and returns the reconstructed source code.
-
- Go to Definition navigating through a multi-level type hierarchy.
+
+ Go to Definition jumping from a call to its declaration in another file.
Go to Definition into decompiled metadata — navigating into `System.Collections.Generic.List` and seeing the implementation — has historically been available only in Visual Studio and Rider. It is one of the features that made those tools worth paying for, because it eliminates the need to keep a browser tab open to docs.microsoft.com while reading unfamiliar code. SharpLsp delivers this in every editor that runs the binary.
@@ -172,8 +172,8 @@ Go to Definition into decompiled metadata — navigating into `System.Collection
The Solution Explorer reflects the same structural understanding. Deeply nested types — inner classes, nested enums, compiler-generated state machine types from async methods — all appear in the correct position in the hierarchy, with reference counts shown inline.
-
- Reference counts and nested type support in the Solution Explorer.
+
+ Types expand to their members in the Solution Explorer.
### Diagnostics
@@ -183,8 +183,8 @@ Diagnostics come from the real Roslyn compiler — not approximations, not tree-
SharpLsp uses the [LSP 3.17 pull-diagnostics model](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_diagnostic), which means diagnostics are only computed when the editor requests them. They are never pushed speculatively during workspace load — when the project graph is incomplete and packages are not yet restored. This solves the "phantom error" problem that has plagued C# tooling in VS Code for years. Anyone who has opened a large solution in OmniSharp and watched hundreds of CS0246 errors appear and then disappear as packages loaded knows exactly why this matters.
-
- Real Roslyn diagnostics, pulled on demand, never pushed prematurely.
+
+ Real compiler diagnostics from Roslyn and the F# Compiler Service, on unsaved code.
The NuGet restore gate runs before `MSBuildWorkspace` opens the solution. SharpLsp detects missing package restore state, runs `dotnet restore` automatically, and only then begins serving semantic requests. The first-open experience takes slightly longer than immediately returning empty state, but every diagnostic that appears is real — there are no red squiggles that exist only because the workspace has not finished loading. This is the behavior .NET developers expect and have historically only gotten in Visual Studio or Rider, where the build system integration ensures packages are present before the IDE begins analysis.
@@ -196,7 +196,7 @@ Code actions come from Roslyn's own `CodeFixProvider` and `CodeRefactoringProvid
This also means third-party Roslyn analyzers work automatically. If your project references a Roslyn analyzer NuGet package — StyleCop, Roslynator, ErrorProne.NET — its diagnostic rules and code fixes appear in SharpLsp's action menu without any additional configuration. The analyzer pipeline is the same one Roslyn runs during compilation.
-
+ Roslyn-powered quick fixes surfaced directly in the editor action menu.
@@ -209,7 +209,7 @@ Right-clicking a project in the Solution Explorer gives you build, rebuild, clea
Build output appears in a dedicated panel, not mixed with LSP logs. Errors from the build link back to the source location in the editor, the same way Visual Studio's Error List does. When you add a project reference through the context menu, the `MSBuildWorkspace` is updated to reflect the new dependency, and the Solution Explorer tree updates to show the reference without requiring a full restart.
-
+ Project-level actions directly from the Solution Explorer context menu.
@@ -220,21 +220,21 @@ Project file mutations go through `Microsoft.Build.Construction` — the MSBuild
The NuGet panel is a full package browser. Search nuget.org (or any configured package source via `nuget.config`), browse available versions, see what is currently installed, inspect package metadata — all without leaving the editor or opening a terminal. The search results pull live from the NuGet v3 API, returning packages with download counts, license identifiers, and version lists.
-
+ NuGet package search pulling live results from nuget.org.
Package management has always been a friction point for .NET developers in editors other than Visual Studio. The VS Code workflow typically involves either using the terminal (`dotnet add package Serilog`) or editing the `.csproj` by hand and waiting for a restore. Neither is as fast as a search-and-click UI with immediate feedback. SharpLsp brings that UI to every editor.
-
+ Installed packages tab showing what is in the active project.
The installed packages view derives its data from the MSBuild project graph — specifically the `PackageReference` items resolved through the NuGet restore graph. This means it reflects the actual state of the project file, not a UI-side cache. If you add a package through the terminal and then open the panel, the new package is there. If you edit the `.csproj` directly, the panel reflects the change on next refresh.
-
+ Package details with license, metadata, and install/remove actions.
diff --git a/src/website/src/docs/architecture.md b/src/website/src/docs/architecture.md
index f181de49..70c3c6d5 100644
--- a/src/website/src/docs/architecture.md
+++ b/src/website/src/docs/architecture.md
@@ -8,7 +8,7 @@ eleventyNavigation:
# Architecture
-
+
SharpLsp separates editor protocol handling from compiler-backed semantic analysis.
diff --git a/src/website/src/docs/completions.md b/src/website/src/docs/completions.md
index 7418b2d6..624619e9 100644
--- a/src/website/src/docs/completions.md
+++ b/src/website/src/docs/completions.md
@@ -6,7 +6,7 @@ eleventyNavigation:
order: 4
---
-
+
# Code Completions
diff --git a/src/website/src/docs/configuration.md b/src/website/src/docs/configuration.md
index 33a00fc3..a6795ca1 100644
--- a/src/website/src/docs/configuration.md
+++ b/src/website/src/docs/configuration.md
@@ -8,7 +8,7 @@ eleventyNavigation:
# Configuration
-
+
SharpLsp has two configuration surfaces:
diff --git a/src/website/src/docs/context-menus.md b/src/website/src/docs/context-menus.md
index 28d18211..6c76d1bf 100644
--- a/src/website/src/docs/context-menus.md
+++ b/src/website/src/docs/context-menus.md
@@ -6,7 +6,7 @@ eleventyNavigation:
order: 9
---
-
+
# Context Menus
diff --git a/src/website/src/docs/diagnostics.md b/src/website/src/docs/diagnostics.md
index 02b4fcf8..23d63e70 100644
--- a/src/website/src/docs/diagnostics.md
+++ b/src/website/src/docs/diagnostics.md
@@ -4,7 +4,7 @@ title: Diagnostics
eleventyExcludeFromCollections: true
---
-
+
# Diagnostics
diff --git a/src/website/src/docs/editors.md b/src/website/src/docs/editors.md
index e2e69965..9dfbc04e 100644
--- a/src/website/src/docs/editors.md
+++ b/src/website/src/docs/editors.md
@@ -8,7 +8,7 @@ eleventyNavigation:
# Editor Setup
-
+
SharpLsp is editor-agnostic at the protocol layer. VS Code and its Open VSX forks are the supported, packaged clients today; a Rider plugin and standalone server archives ship with every release, and the other integrations remain development work.
diff --git a/src/website/src/docs/go-to-definition.md b/src/website/src/docs/go-to-definition.md
index e30ddcfe..839eb559 100644
--- a/src/website/src/docs/go-to-definition.md
+++ b/src/website/src/docs/go-to-definition.md
@@ -4,7 +4,7 @@ title: Go to Definition
eleventyExcludeFromCollections: true
---
-
+
# Navigation
diff --git a/src/website/src/docs/hover.md b/src/website/src/docs/hover.md
index d1b44255..deb0e295 100644
--- a/src/website/src/docs/hover.md
+++ b/src/website/src/docs/hover.md
@@ -4,7 +4,7 @@ title: Hover and Quick Info
eleventyExcludeFromCollections: true
---
-
+
# Hover and Quick Info
diff --git a/src/website/src/docs/nuget.md b/src/website/src/docs/nuget.md
index a83958c4..43c2d4c9 100644
--- a/src/website/src/docs/nuget.md
+++ b/src/website/src/docs/nuget.md
@@ -6,7 +6,7 @@ eleventyNavigation:
order: 8
---
-
+
# NuGet Package Manager
diff --git a/src/website/src/docs/refactoring.md b/src/website/src/docs/refactoring.md
index a783bfc1..2ba8b42b 100644
--- a/src/website/src/docs/refactoring.md
+++ b/src/website/src/docs/refactoring.md
@@ -6,7 +6,7 @@ eleventyNavigation:
order: 7
---
-
+
# Refactoring and Code Actions
diff --git a/src/website/src/ja/blog/introducing-sharplsp.md b/src/website/src/ja/blog/introducing-sharplsp.md
index 8bb6771d..4bf6a5c1 100644
--- a/src/website/src/ja/blog/introducing-sharplsp.md
+++ b/src/website/src/ja/blog/introducing-sharplsp.md
@@ -110,8 +110,8 @@ SharpLsp で .NET ワークスペースを開いたとき最初に目にする
これは聞こえるよりも重要です。Visual Studio のソリューションエクスプローラーは、2002 年以来 .NET プロジェクトの心的モデルとなってきました。これは .NET 開発者がコードを考える方法、つまり「フォルダー内のファイル」ではなく「ソリューション内のプロジェクト内の名前空間内の型」を表しています。Windows の Visual Studio と、macOS や Linux のあらゆるツールを行き来するシニアエンジニアは、このモデルの喪失を強く感じます。ファイルツリーベースの代替はすべてコンテキストスイッチを強います。SharpLsp はそのコンテキストスイッチを拒否します。ソリューション階層が主要なナビゲーション面であり、MSBuild から直接導出され、編集中も同期され続けます。
-
- 実際の .sln ファイルを完全なプロジェクトと型の階層で表示するソリューションエクスプローラー。
+
+ 実際のソリューションを完全なプロジェクトと型の階層で表示するソリューションエクスプローラー。F# と C# が並びます。
ツリーは型レベルの構造も反映します。名前空間を展開すると型が表示され、型を展開するとメンバーが表示されます。各ノードにはインラインで参照数が表示され、何が使われていて何がデッドコードになり得るかを継続的なアンビエントシグナルとして示します。気になるシンボルごとに明示的な「すべての参照を検索」を実行する必要はありません。ディスク上のソリューションが変わったとき、たとえば新しいファイルが追加されたり、プロジェクト参照が変更されたりしたときも、再起動なしにツリーが更新されます。
@@ -123,7 +123,7 @@ SharpLsp で .NET ワークスペースを開いたとき最初に目にする
折りたたみ範囲は、コンパイラーサイドカーへのラウンドトリップなしに、Rust ホスト内で完全に計算されます。つまり、ワークスペースのロードが完了しているか、コンパイラーがバックグラウンドビルドの最中であるかを問わず、1 ミリ秒未満で解決されます。折りたたみは構造情報であってセマンティック情報ではありません。SharpLsp はそれをそうしたものとして扱い、最速のハンドラーへルーティングします。
-
+ tree-sitter による高速なコード折りたたみ — サブミリ秒で、コンパイラー不要。
@@ -136,7 +136,7 @@ SharpLsp で .NET ワークスペースを開いたとき最初に目にする
まだインポートされていない型名を入力すると、SharpLsp は自動インポート提案とともに表示します。`.` を入力すると、すべてのインポート済み名前空間からの継承メンバーと拡張メソッドを含む完全なメンバーリストが得られます。LINQ チェーンの中にいるときは、補完コンテキストが要素型を意識し、それに応じてフィルタリングします。これが Roslyn が知っていることであり、SharpLsp はそれをすべてそのまま公開します。
-
+ Visual Studio を支える同じエンジンによる、Roslyn の CompletionService によるセマンティック補完。
@@ -147,8 +147,8 @@ SharpLsp で .NET ワークスペースを開いたとき最初に目にする
ホバーはメソッドや型の完全なシグネチャを、XML ドキュメント(パラメーター、戻り値の説明、備考、例外、サンプル)とともに表示します。これはソース内の XML ドキュメントコメントから、あるいはサードパーティ API にホバーした際は NuGet パッケージに埋め込まれたドキュメントからレンダリングされます。Roslyn の `DocumentationCommentCompiler` は、基底クラスやインターフェース実装からの継承ドキュメントを含めて、これらすべてを公開します。
-
- パラメーターと戻り値のドキュメントを伴う XML ドキュメントコメントをレンダリングするホバー。
+
+ パラメーターのドキュメントを伴う XML ドキュメントコメントをレンダリングするホバー。
サイドバーに表示される Profiler パネルは飾りではありません。SharpLsp はマシン上で実行中のすべての .NET プロセスを表示するため、何が動いているのかを常に把握できます。パフォーマンス低下を追跡しているとき、あるいはサービスが本当に停止したことを確認したいときに、このアンビエントなプロセス一覧はすぐに役立ちます。Activity Monitor を開いたり、`ps aux | grep dotnet` を実行したりする必要はありません。
@@ -164,8 +164,8 @@ F# にとって、ホバーは C# よりも構造的に重要です。F# コー
これはテキストの grep でもインデックスの検索でもありません。Roslyn のシンボル解決です。Visual Studio が内部で使用しているのと同じ仕組みです。Roslyn のワークスペースモデルがそれらを考慮しているため、部分クラス、ソースジェネレーター、ネスト型を正しく処理します。ソースジェネレーターで定義されたシンボルに対して定義へ移動を押すと、SharpLsp はジェネレーター実装ではなく、生成されたソースに移動します。BCL の型へ移動するときは、C# サイドカーが ICSharpCode.Decompiler を使ってメタデータを逆コンパイルし、再構築されたソースコードを返します。
-
- 多階層の型階層をたどって移動する定義へ移動。
+
+ 呼び出しから別のファイルにある宣言へジャンプする定義へ移動。
逆コンパイルされたメタデータへの定義へ移動、つまり `System.Collections.Generic.List` に入って実装を見るという機能は、歴史的に Visual Studio と Rider のみで利用できました。これらのツールにお金を払う価値があった機能のひとつです。なぜなら、不慣れなコードを読みながら docs.microsoft.com のブラウザータブを開いておく必要がなくなるからです。SharpLsp は、このバイナリが動くあらゆるエディターでこれを実現します。
@@ -173,8 +173,8 @@ F# にとって、ホバーは C# よりも構造的に重要です。F# コー
ソリューションエクスプローラーは、同じ構造的理解を反映します。深くネストした型 — 内部クラス、ネストされた列挙型、async メソッドが生成するコンパイラー生成のステートマシン型 — はすべて階層内の正しい位置に表示され、参照数がインラインで示されます。
-
- ソリューションエクスプローラーでの参照数とネスト型サポート。
+
+ ソリューションエクスプローラーでは、型をメンバーまで展開できます。
### 診断
@@ -184,8 +184,8 @@ F# にとって、ホバーは C# よりも構造的に重要です。F# コー
SharpLsp は [LSP 3.17 のプル診断モデル](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_diagnostic) を採用しています。これは、診断はエディターが要求したときにのみ計算されることを意味します。プロジェクトグラフが不完全でパッケージがまだ復元されていないワークスペースのロード中に、推測でプッシュされることは決してありません。これは、長年にわたり VS Code 上の C# ツールを悩ませてきた「ファントムエラー」問題を解決します。OmniSharp で大きなソリューションを開き、パッケージがロードされるにつれ何百もの CS0246 エラーが現れては消えるのを見たことがある人なら、なぜこれが重要かを正確にご存知でしょう。
-
- 本物の Roslyn 診断を、必要なときに引き出し、早すぎるプッシュは行いません。
+
+ Roslyn と F# Compiler Service による本物のコンパイラー診断。未保存のコードにも。
NuGet 復元ゲートは、`MSBuildWorkspace` がソリューションを開く前に実行されます。SharpLsp は欠落したパッケージ復元状態を検出し、`dotnet restore` を自動的に実行し、その後にのみセマンティックリクエストの提供を開始します。初回オープン体験は、即座に空の状態を返すよりわずかに長くかかります。トレードオフは、そのゲートを通過した後にエディターが受け取るすべての診断が本物であることです。ワークスペースのロードが完了していないという理由だけで存在する赤い波線はありません。これは、.NET 開発者が期待し、歴史的には Visual Studio や Rider でのみ得られていた挙動です。これらのツールでは、ビルドシステムとの統合により、IDE が解析を始める前にパッケージが揃っていることが保証されます。
@@ -197,7 +197,7 @@ NuGet 復元ゲートは、`MSBuildWorkspace` がソリューションを開く
これは、サードパーティの Roslyn アナライザーが自動的に動作することも意味します。プロジェクトが Roslyn アナライザーの NuGet パッケージ — StyleCop、Roslynator、ErrorProne.NET — を参照していれば、その診断ルールとコード修正は追加の設定なしに SharpLsp のアクションメニューに表示されます。アナライザーパイプラインは Roslyn がコンパイル中に実行するのと同じものです。
-
+ エディターのアクションメニューに直接表示される、Roslyn によるクイックフィックス。
@@ -210,7 +210,7 @@ NuGet 復元ゲートは、`MSBuildWorkspace` がソリューションを開く
ビルド出力は専用パネルに表示され、LSP ログとは混ざりません。ビルドからのエラーは、Visual Studio のエラーリストと同じように、エディター内のソース位置にリンクして戻ります。コンテキストメニューからプロジェクト参照を追加すると、`MSBuildWorkspace` が新しい依存関係を反映するように更新され、ソリューションエクスプローラーのツリーは完全な再起動を要さずにその参照を表示するように更新されます。
-
+ ソリューションエクスプローラーのコンテキストメニューから直接実行できるプロジェクトレベルのアクション。
@@ -221,21 +221,21 @@ NuGet 復元ゲートは、`MSBuildWorkspace` がソリューションを開く
NuGet パネルは完全なパッケージブラウザーです。nuget.org(または `nuget.config` 経由で構成された任意のパッケージソース)を検索し、利用可能なバージョンをブラウズし、現在インストールされているものを確認し、パッケージメタデータを点検できます。エディターを離れることもターミナルを開くこともなく、すべて行えます。検索結果は NuGet v3 API からライブで取得され、ダウンロード数、ライセンス識別子、バージョンリスト付きでパッケージを返します。
-
+ nuget.org からライブで結果を取得する NuGet パッケージ検索。
パッケージ管理は、Visual Studio 以外のエディターを使う .NET 開発者にとって常に摩擦点でした。VS Code のワークフローは通常、ターミナル(`dotnet add package Serilog`)を使うか、`.csproj` を手で編集して復元を待つかのどちらかです。どちらも、即時フィードバック付きの検索&クリック UI ほど速くはありません。SharpLsp はその UI をすべてのエディターに届けます。
-
+ アクティブプロジェクトに含まれているものを表示するインストール済みパッケージタブ。
インストール済みパッケージビューのデータは MSBuild プロジェクトグラフ、具体的には NuGet 復元グラフを通じて解決された `PackageReference` 項目から派生しています。つまり、UI 側のキャッシュではなく、プロジェクトファイルの実際の状態を反映します。ターミナルからパッケージを追加した後にパネルを開けば、新しいパッケージはそこにあります。`.csproj` を直接編集すれば、次回の更新時にパネルがその変更を反映します。
-
+ ライセンス、メタデータ、インストール/削除アクション付きのパッケージ詳細。
diff --git a/src/website/src/ja/docs/architecture.md b/src/website/src/ja/docs/architecture.md
index fa4ddbe9..2146b3b7 100644
--- a/src/website/src/ja/docs/architecture.md
+++ b/src/website/src/ja/docs/architecture.md
@@ -9,7 +9,7 @@ eleventyNavigation:
# アーキテクチャ
-
+
SharpLsp は、エディタープロトコルの処理と、コンパイラーに基づくセマンティック解析を分離しています。
diff --git a/src/website/src/ja/docs/completions.md b/src/website/src/ja/docs/completions.md
index 7b9cd5ab..14298865 100644
--- a/src/website/src/ja/docs/completions.md
+++ b/src/website/src/ja/docs/completions.md
@@ -7,7 +7,7 @@ eleventyNavigation:
order: 4
---
-
+
# コード補完
diff --git a/src/website/src/ja/docs/configuration.md b/src/website/src/ja/docs/configuration.md
index 23a6b767..60e2e267 100644
--- a/src/website/src/ja/docs/configuration.md
+++ b/src/website/src/ja/docs/configuration.md
@@ -9,7 +9,7 @@ eleventyNavigation:
# 設定
-
+
SharpLsp には 2 つの設定方法があります。
diff --git a/src/website/src/ja/docs/context-menus.md b/src/website/src/ja/docs/context-menus.md
index cd1e183c..27c908b4 100644
--- a/src/website/src/ja/docs/context-menus.md
+++ b/src/website/src/ja/docs/context-menus.md
@@ -7,7 +7,7 @@ eleventyNavigation:
order: 9
---
-
+
# コンテキストメニュー
diff --git a/src/website/src/ja/docs/diagnostics.md b/src/website/src/ja/docs/diagnostics.md
index f697c489..da8ec7cd 100644
--- a/src/website/src/ja/docs/diagnostics.md
+++ b/src/website/src/ja/docs/diagnostics.md
@@ -5,7 +5,7 @@ lang: ja
eleventyExcludeFromCollections: true
---
-
+
# 診断
diff --git a/src/website/src/ja/docs/editors.md b/src/website/src/ja/docs/editors.md
index 327af541..2c236d66 100644
--- a/src/website/src/ja/docs/editors.md
+++ b/src/website/src/ja/docs/editors.md
@@ -9,7 +9,7 @@ eleventyNavigation:
# エディター設定
-
+
SharpLsp はプロトコル層ではエディターに依存しません。現時点でサポートされ、パッケージとして提供されているクライアントは VS Code です。その他の統合は開発中です。
diff --git a/src/website/src/ja/docs/go-to-definition.md b/src/website/src/ja/docs/go-to-definition.md
index 880ec711..d61e6cd8 100644
--- a/src/website/src/ja/docs/go-to-definition.md
+++ b/src/website/src/ja/docs/go-to-definition.md
@@ -5,7 +5,7 @@ lang: ja
eleventyExcludeFromCollections: true
---
-
+
# ナビゲーション
diff --git a/src/website/src/ja/docs/hover.md b/src/website/src/ja/docs/hover.md
index dbbc3d5f..1cfd8d8b 100644
--- a/src/website/src/ja/docs/hover.md
+++ b/src/website/src/ja/docs/hover.md
@@ -5,7 +5,7 @@ lang: ja
eleventyExcludeFromCollections: true
---
-
+
# ホバーとクイック情報
diff --git a/src/website/src/ja/docs/nuget.md b/src/website/src/ja/docs/nuget.md
index 90c71da9..241d6810 100644
--- a/src/website/src/ja/docs/nuget.md
+++ b/src/website/src/ja/docs/nuget.md
@@ -7,7 +7,7 @@ eleventyNavigation:
order: 8
---
-
+
# NuGet パッケージマネージャー
diff --git a/src/website/src/ja/docs/refactoring.md b/src/website/src/ja/docs/refactoring.md
index ccd9392d..7bb4ea7e 100644
--- a/src/website/src/ja/docs/refactoring.md
+++ b/src/website/src/ja/docs/refactoring.md
@@ -7,7 +7,7 @@ eleventyNavigation:
order: 7
---
-
+
# リファクタリングとコードアクション
diff --git a/src/website/src/zh/blog/introducing-sharplsp.md b/src/website/src/zh/blog/introducing-sharplsp.md
index 9babaa20..a10ad7a8 100644
--- a/src/website/src/zh/blog/introducing-sharplsp.md
+++ b/src/website/src/zh/blog/introducing-sharplsp.md
@@ -110,8 +110,8 @@ SharpLsp 不是承诺。它是已经能运行的软件。VS Code 扩展是第一
这件事比听起来更重要。Visual Studio 的解决方案资源管理器自 2002 年以来一直是 .NET 项目的心智模型。它代表了 .NET 开发者思考代码的方式——不是文件夹中的文件,而是解决方案中的项目中的命名空间中的类型。在 Windows 上的 Visual Studio 与 macOS 或 Linux 上其他工具之间切换的资深工程师,能切身感受到失去这种模型的代价。每一个基于文件树的替代方案都会强迫一次心智切换。SharpLsp 拒绝这种切换。解决方案层级是首要导航界面,直接源自 MSBuild,并在你编辑时保持同步。
-
- 解决方案资源管理器渲染真实的 .sln 文件,并展开完整的项目和类型层级。
+
+ 解决方案资源管理器渲染真实的解决方案,展开完整的项目和类型层级,F# 与 C# 并列。
这棵树还反映类型级结构。命名空间展开显示类型。类型展开显示成员。每个节点旁内联显示引用计数,让你随时获得使用情况和潜在死代码的环境信号——而不需要对每一个你想了解的符号显式地运行一次 Find All References。当解决方案在磁盘上发生变化——新增文件、修改项目引用——树会无需重启即可更新。
@@ -123,7 +123,7 @@ SharpLsp 不是承诺。它是已经能运行的软件。VS Code 扩展是第一
折叠范围完全在 Rust 宿主中计算,不涉及任何到编译器 sidecar 的往返。这意味着无论工作区是否完成加载,编译器是否在后台构建中,折叠都能在 1 毫秒内解析。折叠是结构信息,不是语义信息——SharpLsp 据此处理它,把请求路由到最快的处理器上。
-
+ 由 tree-sitter 驱动的代码折叠——亚毫秒级,无需编译器。
@@ -136,7 +136,7 @@ SharpLsp 不是承诺。它是已经能运行的软件。VS Code 扩展是第一
当你输入一个尚未导入的类型名时,SharpLsp 会带着自动导入建议把它呈现出来。当你输入 `.` 时,你会得到完整成员列表,包含继承成员和来自所有已导入命名空间的扩展方法。当你处于 LINQ 链中时,补全上下文能感知元素类型并相应过滤。这就是 Roslyn 所知道的——SharpLsp 把它直接暴露出来。
-
+ 由 Roslyn CompletionService 驱动的语义补全——与 Visual Studio 背后使用的是同一套引擎。
@@ -147,8 +147,8 @@ SharpLsp 不是承诺。它是已经能运行的软件。VS Code 扩展是第一
悬停显示完整的方法或类型签名,并附上其 XML 文档——参数、返回值描述、备注、异常和示例。这些内容由源代码中的 XML 文档注释渲染,或者在悬停第三方 API 时由 NuGet 包中嵌入的文档渲染。Roslyn 的 `DocumentationCommentCompiler` 暴露了所有这些信息,包括来自基类和接口实现的继承文档。
-
- 悬停渲染 XML 文档注释,包括参数和返回值文档。
+
+ 悬停渲染 XML 文档注释,包括参数文档。
侧边栏中可见的 Profiler 面板并非可有可无的装饰。SharpLsp 显示机器上所有正在运行的 .NET 进程,让你随时知道当前正在执行什么。当你在追查性能回归或确认某个服务确实已经停止时,这种环境进程列表立刻派上用场——你不必打开 Activity Monitor 或运行 `ps aux | grep dotnet`。
@@ -164,8 +164,8 @@ SharpLsp 不是承诺。它是已经能运行的软件。VS Code 扩展是第一
这不是文本 grep,也不是索引查询。这是 Roslyn 的符号解析——与 Visual Studio 内部使用的是同一机制。它能正确处理部分类、source generator 和嵌套类型,因为 Roslyn 的工作区模型对它们都有考虑。当你对一个由 source generator 定义的符号按下跳转到定义时,SharpLsp 导航到生成的源代码——而不是生成器实现。当你导航到 BCL 类型时,C# sidecar 会用 ICSharpCode.Decompiler 反编译元数据并返回重建的源代码。
-
- 跳转到定义可以穿过多层类型层级。
+
+ 跳转到定义从调用跳到另一个文件中的声明。
跳转到反编译元数据中的定义——例如导航进入 `System.Collections.Generic.List` 并查看其实现——历来只有 Visual Studio 和 Rider 才有。它正是让那些工具值得付费的特性之一,因为它免去了你阅读陌生代码时还得开着 docs.microsoft.com 浏览器标签的麻烦。SharpLsp 把这一能力带到每一款运行该二进制的编辑器中。
@@ -173,8 +173,8 @@ SharpLsp 不是承诺。它是已经能运行的软件。VS Code 扩展是第一
解决方案资源管理器反映了同样的结构理解。深度嵌套的类型——内部类、嵌套枚举、来自异步方法的编译器生成状态机类型——都会出现在层级中的正确位置,并内联显示引用计数。
-
- 解决方案资源管理器中的引用计数和嵌套类型支持。
+
+ 在解决方案资源管理器中,类型可以展开到其成员。
### 诊断
@@ -184,8 +184,8 @@ SharpLsp 不是承诺。它是已经能运行的软件。VS Code 扩展是第一
SharpLsp 使用 [LSP 3.17 拉取式诊断模型](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_diagnostic),这意味着诊断只在编辑器请求时计算。它们绝不会在工作区加载期间——项目图不完整、包尚未还原时——被推测式地推送出去。这解决了多年来困扰 VS Code 中 C# 工具的"幽灵错误"问题。任何打开过大型解决方案、看到 OmniSharp 弹出几百个 CS0246 错误又随包加载逐渐消失的人,都明白这件事的重要性。
-
- 真实的 Roslyn 诊断,按需拉取,永远不会过早推送。
+
+ 来自 Roslyn 和 F# Compiler Service 的真实编译器诊断,作用于未保存的代码。
NuGet 还原门会在 `MSBuildWorkspace` 打开解决方案之前运行。SharpLsp 会检测包还原状态,如果缺失就自动运行 `dotnet restore`,然后才开始服务语义请求。首次打开的体验比立刻返回空状态稍慢,但每一个出现的诊断都是真实的——不会有那种仅仅因为工作区还没加载完就出现的红色波浪线。这是 .NET 开发者期望的行为,过去只有在 Visual Studio 或 Rider 中才能获得,因为它们的构建系统集成会确保 IDE 开始分析前包都已就位。
@@ -197,7 +197,7 @@ NuGet 还原门会在 `MSBuildWorkspace` 打开解决方案之前运行。SharpL
这也意味着第三方 Roslyn 分析器自动生效。如果你的项目引用了 Roslyn 分析器 NuGet 包——StyleCop、Roslynator、ErrorProne.NET——它们的诊断规则和代码修复就会出现在 SharpLsp 的操作菜单中,无需额外配置。分析器管线就是 Roslyn 在编译期间运行的同一条管线。
-
+ 由 Roslyn 驱动的快速修复,直接呈现在编辑器操作菜单中。
@@ -210,7 +210,7 @@ NuGet 还原门会在 `MSBuildWorkspace` 打开解决方案之前运行。SharpL
构建输出出现在专用面板中,不会与 LSP 日志混在一起。来自构建的错误像 Visual Studio 的错误列表那样链接回编辑器中的源位置。当你通过上下文菜单添加一个项目引用时,`MSBuildWorkspace` 会更新以反映新依赖,解决方案资源管理器树也会更新显示该引用,无需完全重启。
-
+ 直接从解决方案资源管理器上下文菜单执行项目级操作。
@@ -221,21 +221,21 @@ NuGet 还原门会在 `MSBuildWorkspace` 打开解决方案之前运行。SharpL
NuGet 面板是一个完整的包浏览器。搜索 nuget.org(或通过 `nuget.config` 配置的任何包源)、浏览可用版本、查看当前安装内容、检查包元数据——全部都不需要离开编辑器或打开终端。搜索结果实时来自 NuGet v3 API,返回包的下载次数、许可证标识符和版本列表。
-
+ NuGet 包搜索从 nuget.org 拉取实时结果。
包管理对 Visual Studio 之外的 .NET 开发者来说一直是个摩擦点。VS Code 的工作流通常要么使用终端(`dotnet add package Serilog`),要么手工编辑 `.csproj` 并等待还原。两者都不及一个搜索-点击 UI、立刻反馈来得快。SharpLsp 把这套 UI 带到每一款编辑器。
-
+ 已安装包标签页显示当前活动项目中的内容。
已安装包视图的数据来自 MSBuild 项目图——具体是通过 NuGet 还原图解析的 `PackageReference` 项。这意味着它反映的是项目文件的真实状态,而不是 UI 端缓存。如果你通过终端添加了一个包再打开面板,新包就在那里。如果你直接编辑 `.csproj`,面板会在下次刷新时反映该变化。
-
+ 包详情包含许可证、元数据以及安装和移除操作。
diff --git a/src/website/src/zh/docs/architecture.md b/src/website/src/zh/docs/architecture.md
index f1c820d3..4e168a68 100644
--- a/src/website/src/zh/docs/architecture.md
+++ b/src/website/src/zh/docs/architecture.md
@@ -9,7 +9,7 @@ eleventyNavigation:
# 架构
-
+
SharpLsp 将编辑器协议处理与由编译器提供支持的语义分析分离开来。
diff --git a/src/website/src/zh/docs/completions.md b/src/website/src/zh/docs/completions.md
index 4328814b..0187c0f4 100644
--- a/src/website/src/zh/docs/completions.md
+++ b/src/website/src/zh/docs/completions.md
@@ -7,7 +7,7 @@ eleventyNavigation:
order: 4
---
-
+
# 代码补全
diff --git a/src/website/src/zh/docs/configuration.md b/src/website/src/zh/docs/configuration.md
index 003c57cd..e7eb4cba 100644
--- a/src/website/src/zh/docs/configuration.md
+++ b/src/website/src/zh/docs/configuration.md
@@ -9,7 +9,7 @@ eleventyNavigation:
# 配置
-
+
SharpLsp 有两类配置入口:
diff --git a/src/website/src/zh/docs/context-menus.md b/src/website/src/zh/docs/context-menus.md
index d53149d8..bc195ce8 100644
--- a/src/website/src/zh/docs/context-menus.md
+++ b/src/website/src/zh/docs/context-menus.md
@@ -7,7 +7,7 @@ eleventyNavigation:
order: 9
---
-
+
# 上下文菜单
diff --git a/src/website/src/zh/docs/diagnostics.md b/src/website/src/zh/docs/diagnostics.md
index 539e8103..d1f65be7 100644
--- a/src/website/src/zh/docs/diagnostics.md
+++ b/src/website/src/zh/docs/diagnostics.md
@@ -5,7 +5,7 @@ lang: zh
eleventyExcludeFromCollections: true
---
-
+
# 诊断
diff --git a/src/website/src/zh/docs/editors.md b/src/website/src/zh/docs/editors.md
index c57d0d84..5d03969a 100644
--- a/src/website/src/zh/docs/editors.md
+++ b/src/website/src/zh/docs/editors.md
@@ -9,7 +9,7 @@ eleventyNavigation:
# 编辑器配置
-
+
SharpLsp 在协议层面与编辑器无关。目前受支持且已打包的客户端是 VS Code;其他集成仍在开发中。
diff --git a/src/website/src/zh/docs/go-to-definition.md b/src/website/src/zh/docs/go-to-definition.md
index f6428383..0334a727 100644
--- a/src/website/src/zh/docs/go-to-definition.md
+++ b/src/website/src/zh/docs/go-to-definition.md
@@ -5,7 +5,7 @@ lang: zh
eleventyExcludeFromCollections: true
---
-
+
# 导航
diff --git a/src/website/src/zh/docs/hover.md b/src/website/src/zh/docs/hover.md
index 33edd881..7320a37e 100644
--- a/src/website/src/zh/docs/hover.md
+++ b/src/website/src/zh/docs/hover.md
@@ -5,7 +5,7 @@ lang: zh
eleventyExcludeFromCollections: true
---
-
+
# 悬停与快速信息
diff --git a/src/website/src/zh/docs/nuget.md b/src/website/src/zh/docs/nuget.md
index b8ee6ca3..9ebf7b47 100644
--- a/src/website/src/zh/docs/nuget.md
+++ b/src/website/src/zh/docs/nuget.md
@@ -7,7 +7,7 @@ eleventyNavigation:
order: 8
---
-
+
# NuGet 包管理器
diff --git a/src/website/src/zh/docs/refactoring.md b/src/website/src/zh/docs/refactoring.md
index 0e4764aa..5194770e 100644
--- a/src/website/src/zh/docs/refactoring.md
+++ b/src/website/src/zh/docs/refactoring.md
@@ -7,7 +7,7 @@ eleventyNavigation:
order: 7
---
-
+
# 重构与代码操作
diff --git a/src/website/tests/media.js b/src/website/tests/media.js
index 02a7e000..617a2fc4 100644
--- a/src/website/tests/media.js
+++ b/src/website/tests/media.js
@@ -28,3 +28,18 @@ export const videoObjects = (page) =>
page.locator('script[type="application/ld+json"]').evaluateAll((scripts) =>
scripts.map((script) => JSON.parse(script.textContent)).filter((data) => data['@type'] === 'VideoObject'),
);
+
+const STILL_WIDTH = 2400;
+
+/** Every still in `stills` loads as the 2400px render with real alt text. [WEB-DESIGN-MEDIA] */
+export async function expectStills(stills) {
+ for (const still of await stills.all()) {
+ await still.scrollIntoViewIfNeeded();
+ const rendered = await still.evaluate((img) => ({ complete: img.complete, width: img.naturalWidth, alt: img.alt }));
+ expect(rendered.complete, 'still finishes loading').toBe(true);
+ expect(rendered.width, 'still is the 2400px render').toBe(STILL_WIDTH);
+ // Characters, not words: a CJK description is dense, so the floor is low but real.
+ expect(rendered.alt.length, 'still has meaningful alt text').toBeGreaterThanOrEqual(8);
+ expect(rendered.alt, 'alt text describes the shot, not the file').not.toContain('.webp');
+ }
+}
diff --git a/src/website/tests/profiler-media.spec.js b/src/website/tests/profiler-media.spec.js
index 4cad5659..bfa3e7e7 100644
--- a/src/website/tests/profiler-media.spec.js
+++ b/src/website/tests/profiler-media.spec.js
@@ -1,13 +1,12 @@
// Tests for [WEB-DESIGN-MEDIA]: the profiler film and stills the profiler-film
// tooling renders from the real extension.
import { test, expect } from '@playwright/test';
-import { expectPlayable, playback, sourcesOf } from './media.js';
+import { expectPlayable, expectStills, playback, sourcesOf } from './media.js';
const FILM_POSTER = '/assets/videos/profiler-film-poster.webp';
const WALKTHROUGH_POSTER = '/assets/videos/profiler-csharp-poster.webp';
const FILM_SOURCES = ['/assets/videos/profiler-film.webm', '/assets/videos/profiler-film.mp4'];
const PROFILER_STILLS = 8;
-const STILL_WIDTH = 2400;
const DOCS = ['/docs/profiler/', '/zh/docs/profiler/', '/ja/docs/profiler/'];
const FSHARP_POSTER = '/assets/videos/profiler-fsharp-poster.webp';
@@ -17,19 +16,6 @@ const FSHARP_DOCS = ['/docs/fsharp-profiling/', '/zh/docs/fsharp-profiling/', '/
const RECORDING_SOURCES = ['/assets/videos/profiler-cpu-recording.mp4'];
const AUTOPLAYING = ['section.hero video', 'section[aria-labelledby="profiler-title"] video'];
-/** Every profiler still on the page loads as the 2400px render with real alt text. */
-async function expectStills(stills) {
- for (const still of await stills.all()) {
- await still.scrollIntoViewIfNeeded();
- const rendered = await still.evaluate((img) => ({ complete: img.complete, width: img.naturalWidth, alt: img.alt }));
- expect(rendered.complete, 'still finishes loading').toBe(true);
- expect(rendered.width, 'still is the 2400px render').toBe(STILL_WIDTH);
- // Characters, not words: a CJK description is dense, so the floor is low but real.
- expect(rendered.alt.length, 'still has meaningful alt text').toBeGreaterThanOrEqual(8);
- expect(rendered.alt, 'alt text describes the shot, not the file').not.toContain('.webp');
- }
-}
-
async function expectSilentLoop(request, video, sources) {
await expect(video).toHaveAttribute('autoplay', '');
await expect(video).toHaveAttribute('loop', '');
diff --git a/src/website/tests/screenshots.spec.js b/src/website/tests/screenshots.spec.js
index cb5b60e8..e13f8ad9 100644
--- a/src/website/tests/screenshots.spec.js
+++ b/src/website/tests/screenshots.spec.js
@@ -1,4 +1,5 @@
import { test, expect } from '@playwright/test';
+import { expectStills } from './media.js';
const DOC_PATHS = [
'/docs/architecture/',
@@ -58,3 +59,44 @@ test.describe('Docs screenshots', () => {
});
}
});
+
+// [WEB-DESIGN-MEDIA] Each feature page opens on its still from the film kit
+// (films/doc-stills.mjs): the real extension on the Shop demo, never an old capture.
+const DOC_STILLS = {
+ 'architecture/': 'architecture',
+ 'editors/': 'editors',
+ 'configuration/': 'configuration',
+ 'completions/': 'completions',
+ 'hover/': 'hover',
+ 'go-to-definition/': 'navigation',
+ 'diagnostics/': 'diagnostics',
+ 'refactoring/': 'refactoring',
+ 'context-menus/': 'context-menus',
+ 'nuget/': 'nuget-browse',
+};
+
+test.describe('Docs stills', () => {
+ for (const [page, still] of Object.entries(DOC_STILLS)) {
+ for (const lang of ['', '/ja', '/zh']) {
+ test(`${lang}/docs/${page} opens on the ${still} still, at full size with alt text`, async ({ page: browser }) => {
+ await browser.goto(`${lang}/docs/${page}`);
+ const shot = browser.locator('img[src^="/assets/screenshots/docs/"]');
+ await expect(shot).toHaveCount(1);
+ await expect(shot).toHaveAttribute('src', `/assets/screenshots/docs/${still}.webp`);
+ await expect(shot).toHaveAttribute('width', '2400');
+ await expect(shot).toHaveAttribute('height', '1500');
+ await expectStills(shot);
+ });
+ }
+ }
+
+ for (const lang of ['', '/ja', '/zh']) {
+ test(`${lang}/blog/introducing-sharplsp/ shows twelve current stills, none of the old captures`, async ({ page }) => {
+ await page.goto(`${lang}/blog/introducing-sharplsp/`);
+ const stills = page.locator('figure img[src^="/assets/screenshots/docs/"]');
+ await expect(stills).toHaveCount(12);
+ await expectStills(stills);
+ await expect(page.locator('img[src$=".png"][src^="/assets/screenshots/"]')).toHaveCount(0);
+ });
+ }
+});
diff --git a/src/website/tools/profiler-film/README.md b/src/website/tools/profiler-film/README.md
index fbe2eb5d..e7f2a124 100644
--- a/src/website/tools/profiler-film/README.md
+++ b/src/website/tools/profiler-film/README.md
@@ -43,6 +43,7 @@ A shoot takes a few minutes and lands in `out//`, which is gitignored. Pub
| `architecture` | `architecture` | Architecture |
| `contributing` | `contributing` | Contributing |
| `films/profiler/` | `profiler-film` | The home page: the one-minute composited film (`capture.mjs`, then `render.mjs`) |
+| `doc-stills` | (stills only) | Each feature page's still and the launch post's figures, in `screenshots/docs/` |
Every film but `films/profiler/` is narrated. The Zed page has none: Zed is a native application, and filming it would mean recording the real desktop.
@@ -72,6 +73,8 @@ A scene is either a clip or a step off camera:
- `{ clip, act, speed, retries }`: `act(shot)` is recorded in real time as `.mp4`. It plays back at `speed`, and the frame shows a speed badge whenever `speed` is above 1. `retries` shoots a failed take again. `scene(clip, step, caption, act, { speed, retries })` builds one with its numbered caption.
- `{ offCamera }`: `offCamera(shot)` runs between clips and is never recorded. Use it for setup, rebuilds and stills.
+A film may also take stills (`snap(shot, name)`), published as `screenshots//.webp` (`profiler` unless the film names another folder). A film with no clips, like `doc-stills`, publishes only its stills.
+
`shot` is `{ page, set, app, workspace, stills }`: the Playwright page, the recorder, the demo process, the staged workspace and the stills folder.
The kit tells the common stories, so a film only supplies its words:
diff --git a/src/website/tools/profiler-film/film.mjs b/src/website/tools/profiler-film/film.mjs
index 8f525d40..e0554f50 100644
--- a/src/website/tools/profiler-film/film.mjs
+++ b/src/website/tools/profiler-film/film.mjs
@@ -23,6 +23,9 @@ const out = path.join(filmsOut, id);
const narration = film.narration && JSON.parse(fs.readFileSync(path.join(films, film.narration), 'utf8'));
if (!process.argv.includes('--publish')) await shootWalkthrough({ demo: film.demo, out, scenes: film.scenes, settings: film.settings });
-const length = await publishWalkthrough({ name: film.name, out, scenes: film.scenes, posterAt: film.poster, narration });
-if (fs.existsSync(path.join(out, 'stills'))) publishStills(path.join(out, 'stills'));
-process.stdout.write(`${film.name}: ${length.toFixed(1)}s published\n`);
+if (fs.existsSync(path.join(out, 'stills'))) publishStills(path.join(out, 'stills'), film.stillsFolder);
+// A film of stills alone has no clips to cut.
+if (film.scenes.some((scene) => scene.clip)) {
+ const length = await publishWalkthrough({ name: film.name, out, scenes: film.scenes, posterAt: film.poster, narration });
+ process.stdout.write(`${film.name}: ${length.toFixed(1)}s published\n`);
+} else process.stdout.write(`${film.name}: stills published\n`);
diff --git a/src/website/tools/profiler-film/films/architecture.mjs b/src/website/tools/profiler-film/films/architecture.mjs
index 07b085fa..d2feadec 100644
--- a/src/website/tools/profiler-film/films/architecture.mjs
+++ b/src/website/tools/profiler-film/films/architecture.mjs
@@ -4,26 +4,15 @@
// answered by tree-sitter in the host with no engine involved.
//
// node src/website/tools/profiler-film/film.mjs architecture
-import { runCommand } from '../kit/editor.mjs';
-import { LANGUAGE, hoverEach, scene, typeText } from '../kit/acts/language.mjs';
+import { LANGUAGE, hoverEach, scene, showLog } from '../kit/acts/language.mjs';
import { AT_CHECKOUT, AT_PIPELINE, ready } from '../kit/acts/shop.mjs';
import { SHOP } from '../kit/set.mjs';
import { openFile, sleep } from '../kit/workbench.mjs';
const PIPELINE = 'CheckoutPipeline.cs';
-/** The SharpLsp log in the panel, showing only lines that mention `text`. */
-async function logFiltered(page, text) {
- await runCommand(page, 'SharpLsp: Show Output Channel');
- const filter = page.locator('.part.panel input[placeholder^="Filter"]').first();
- await filter.click();
- await page.keyboard.press('Meta+A');
- await typeText(page, text, 60);
- await sleep(1200);
-}
-
async function routing({ page }) {
- await logFiltered(page, 'method=textDocument/hover');
+ await showLog(page, 'method=textDocument/hover');
await openFile(page, 'Checkout.fs');
// Words the warm-up never hovered: the host answers a repeat from its cache, without a sidecar.
await hoverEach(page, [{ line: 28, word: 'pickBasket' }], { scroll: 300, hold: 3500 });
diff --git a/src/website/tools/profiler-film/films/configuration.mjs b/src/website/tools/profiler-film/films/configuration.mjs
index 32853b7a..3518fc11 100644
--- a/src/website/tools/profiler-film/films/configuration.mjs
+++ b/src/website/tools/profiler-film/films/configuration.mjs
@@ -34,6 +34,9 @@ const TOML = [
'monorepo = false',
];
const MONOREPO = TOML.indexOf('monorepo = false') + 1;
+
+/** The file as staged beside the solution. */
+export const configurationToml = `${TOML.join('\n')}\n`;
const SESSIONS = 'SessionCache.cs';
const PROBE = { line: 8, word: 'Remember' };
@@ -76,7 +79,7 @@ export default {
narration: 'configuration.narration.json',
poster: { scene: 'toml', at: 5 },
scenes: [
- { offCamera: ({ workspace }) => fs.writeFileSync(path.join(workspace, 'sharplsp.toml'), `${TOML.join('\n')}\n`) },
+ { offCamera: ({ workspace }) => fs.writeFileSync(path.join(workspace, 'sharplsp.toml'), configurationToml) },
{ offCamera: prepareCode(SESSIONS, PROBE) },
scene('settings', 1, 'Editor settings: VS Code\'s Settings, under SharpLsp', settings),
scene('toml', 2, 'Server settings: sharplsp.toml, beside the solution', monorepo),
diff --git a/src/website/tools/profiler-film/films/doc-stills.mjs b/src/website/tools/profiler-film/films/doc-stills.mjs
new file mode 100644
index 00000000..6ba233a9
--- /dev/null
+++ b/src/website/tools/profiler-film/films/doc-stills.mjs
@@ -0,0 +1,171 @@
+// Implements [WEB-DESIGN-MEDIA]. The docs pages' and the launch post's stills
+// (`screenshots/docs/.webp`), shot on the Shop demo in the same isolated
+// editor as the films: no clips, only clean frames of each feature at work.
+//
+// node src/website/tools/profiler-film/film.mjs doc-stills
+import fs from 'node:fs';
+import path from 'node:path';
+import { snap } from '../kit/camera.mjs';
+import { ACTIONS, SUGGEST, clickOn, hoverOn } from '../kit/code.mjs';
+import { runCommand } from '../kit/editor.mjs';
+import { openMenu, unfold } from '../kit/acts/explorer.mjs';
+import { LANGUAGE, hidePanel, navigate, reopen, setPane, showLog, typeText } from '../kit/acts/language.mjs';
+import { AT_CHECKOUT, AT_PIPELINE, ready } from '../kit/acts/shop.mjs';
+import { SHOP } from '../kit/set.mjs';
+import { openFile, panel, sleep, typeAt } from '../kit/workbench.mjs';
+import { configurationToml } from './configuration.mjs';
+
+const CSHARP = /^Storefront \(Storefront\.csproj\)/;
+const FSHARP = /^FsStorefront \(FsStorefront\.fsproj\)/;
+
+/** Off camera: set the editor up with `act(shot)`, then take the still `name`. */
+const take = (name, act, options) => ({
+ offCamera: async (shot) => {
+ await act(shot);
+ await snap(shot, name, options);
+ },
+});
+
+async function solutionTree({ page }) {
+ await unfold(page, FSHARP);
+ await unfold(page, CSHARP);
+ await unfold(page, /^Storefront$/);
+ await openFile(page, 'CheckoutPipeline.cs');
+}
+
+/** A clean `file` with `text` typed after `line`: the edit every code still starts from. */
+async function typedInto(page, file, line, text) {
+ await reopen(file)({ page });
+ await typeAt(page, { line, append: true, text });
+}
+
+/** `text` typed into `file` after `line` ends on a trigger character; the list it opens, narrowed by `filter`. */
+const completing = (file, line, text, filter) => async ({ page }) => {
+ await typedInto(page, file, line, text);
+ await page.locator(SUGGEST).waitFor({ timeout: 15_000 });
+ await typeText(page, filter, 110);
+ await sleep(1200);
+};
+
+/** `text` typed into `file` after `line`, then the quick fixes (Cmd+.) at `spot`. */
+const fixing = (file, line, text, spot) => async ({ page }) => {
+ await typedInto(page, file, line, text);
+ await page.keyboard.press('Escape');
+ await sleep(2500);
+ await clickOn(page, spot);
+ await page.keyboard.press('Meta+.');
+ await page.locator(ACTIONS).first().waitFor({ timeout: 30_000 });
+ await sleep(1500);
+};
+
+async function nugetInstalled({ page }) {
+ const browser = panel(page);
+ await browser.locator('.btn-primary', { hasText: 'Install' }).click();
+ await browser.locator('.btn-danger', { hasText: 'Remove' }).waitFor({ timeout: 180_000 });
+ await browser.getByText('Installed', { exact: true }).first().click();
+ await sleep(4000);
+}
+
+async function problems({ page }) {
+ await typedInto(page, 'Checkout.fs', 22, '\n\nlet count: int = "12"');
+ await openFile(page, 'CheckoutPipeline.cs');
+ await typeAt(page, { line: 12, append: true, text: '\n int items = "twelve";' });
+ await page.keyboard.press('Escape');
+ await sleep(4000);
+ await page.keyboard.press('Meta+Shift+M');
+ await sleep(2000);
+}
+
+async function nugetBrowser({ page }) {
+ await openMenu(page, FSHARP);
+ await page.locator('.monaco-menu .action-label').filter({ hasText: /^Browse NuGet Packages$/ }).first().click();
+ await panel(page).locator('.package-item').first().waitFor({ timeout: 60_000 });
+ await sleep(4000);
+}
+
+async function nugetPackage({ page }) {
+ const browser = panel(page);
+ await browser.locator('#searchInput').click();
+ await typeText(page, 'FsToolkit.ErrorHandling', 50);
+ await browser.locator('.package-item', { hasText: 'FsToolkit.ErrorHandling' }).first().click();
+ await browser.locator('.btn-primary', { hasText: 'Install' }).waitFor({ timeout: 60_000 });
+ await sleep(4000);
+}
+
+export default {
+ name: 'doc-stills',
+ demo: SHOP,
+ settings: LANGUAGE,
+ stillsFolder: 'docs',
+ scenes: [
+ { offCamera: ({ workspace }) => fs.writeFileSync(path.join(workspace, 'sharplsp.toml'), configurationToml) },
+ ...ready(AT_PIPELINE, AT_CHECKOUT),
+ take('solution-explorer', solutionTree),
+ take('type-members', ({ page }) => unfold(page, /^CustomerSession$/)),
+ take('context-menus', ({ page }) => openMenu(page, CSHARP)),
+ take('hover', async ({ page }) => {
+ await page.keyboard.press('Escape');
+ // The launch post points at the Processes view beside the hover.
+ await setPane(page, 'Processes', true);
+ await openFile(page, 'Program.cs');
+ await hoverOn(page, { line: 5, word: 'Generate' });
+ await sleep(1200);
+ }, { hovering: true }),
+ take('completions', async (shot) => {
+ await setPane(shot.page, 'Processes', false);
+ await completing('Checkout.fs', 21, '\n\nlet cheapest (basket: Product list) =\n basket |> List.', 'min')(shot);
+ }),
+ take('completions-csharp', completing('CheckoutPipeline.cs', 14, '\n\n public static Product Cheapest(List basket) =>\n basket.', 'Min')),
+ take('definition', async ({ page }) => {
+ await reopen('Program.cs')({ page });
+ await navigate(page, { line: 10, word: 'Checkout' }, 'F12');
+ await sleep(1500);
+ }),
+ take('navigation', async ({ page }) => {
+ await reopen('CheckoutPipeline.cs')({ page });
+ await navigate(page, { line: 16, word: 'Product' }, 'Shift+F12');
+ await page.locator('.peekview-widget').first().waitFor();
+ await sleep(2000);
+ }),
+ take('diagnostics', problems),
+ take('refactoring', async (shot) => {
+ await hidePanel(shot.page);
+ await fixing('Sessions.fs', 10, '\n\nlet describe stage =\n match stage with\n | Browsing -> "still browsing"', { line: 13, word: 'match' })(shot);
+ }),
+ take('refactoring-csharp', async (shot) => {
+ await shot.page.keyboard.press('Escape');
+ await fixing('CheckoutPipeline.cs', 12, '\n var receipt = Receipt(invoice);', { line: 13, word: 'Receipt' })(shot);
+ }),
+ take('code-folding', async ({ page }) => {
+ await page.keyboard.press('Escape');
+ await reopen('CheckoutPipeline.cs')({ page });
+ for (const key of ['Meta+K', 'Meta+0']) await page.keyboard.press(key);
+ await sleep(1500);
+ }),
+ take('architecture', async ({ page }) => {
+ for (const key of ['Meta+K', 'Meta+J']) await page.keyboard.press(key);
+ await showLog(page, 'method=textDocument/hover');
+ await openFile(page, 'Checkout.fs');
+ await hoverOn(page, { line: 13, word: 'pickBasket' });
+ await openFile(page, 'CheckoutPipeline.cs');
+ await hoverOn(page, { line: 11, word: 'PriceBasket' });
+ await sleep(1500);
+ }, { hovering: true }),
+ take('configuration', async ({ page }) => {
+ await hidePanel(page);
+ await reopen('sharplsp.toml')({ page });
+ await sleep(1500);
+ }),
+ take('editors', async ({ page }) => {
+ await page.locator('.command-center .command-center-center').click();
+ await typeText(page, '>SharpLsp', 60);
+ await sleep(2500);
+ }),
+ take('nuget-browse', async (shot) => {
+ await shot.page.keyboard.press('Escape');
+ await nugetBrowser(shot);
+ }),
+ take('nuget-package', nugetPackage),
+ take('nuget-installed', nugetInstalled),
+ ],
+};
diff --git a/src/website/tools/profiler-film/kit/acts/language.mjs b/src/website/tools/profiler-film/kit/acts/language.mjs
index 8ac837a5..4601decb 100644
--- a/src/website/tools/profiler-film/kit/acts/language.mjs
+++ b/src/website/tools/profiler-film/kit/acts/language.mjs
@@ -97,19 +97,37 @@ export const settingsEditor = {
},
};
+/** The SharpLsp log in the panel, showing only the lines that mention `text`. */
+export async function showLog(page, text) {
+ await runCommand(page, 'SharpLsp: Show Output Channel');
+ await page.locator('.part.panel input[placeholder^="Filter"]').first().click();
+ await page.keyboard.press('Meta+A');
+ await typeText(page, text, 60);
+ await sleep(1200);
+}
+
+/** Hide the bottom panel when it is showing. */
+export async function hidePanel(page) {
+ if (await page.locator('.part.panel').isVisible().catch(() => false)) await runCommand(page, 'View: Toggle Panel Visibility');
+}
+
/** Off camera: open `file` until `probe` answers a hover, so its language's engine is warm before it is filmed. */
export const warm = (file, probe) => async ({ page }) => {
await openFile(page, file);
await awaitSemantics(page, probe);
};
-/** Off camera: every editor closed without saving, `file` open. */
+/** Off camera: every editor closed without saving, `file` open. Fails if an edited editor survives. */
export const reopen = (file) => async ({ page }) => {
await runCommand(page, 'View: Close All Editors');
- for (let attempt = 0; attempt < 3 && (await page.locator('.dialog-box').isVisible().catch(() => false)); attempt++) {
- await page.locator('.dialog-box .monaco-button', { hasText: "Don't Save" }).click();
+ const edited = page.locator('.tabs-container .tab.dirty');
+ // The save prompt opens a moment after the close: answer it until no edited editor is left.
+ for (let attempt = 0; attempt < 10 && (await edited.count()) > 0; attempt++) {
+ const dontSave = page.getByRole('dialog').getByRole('button', { name: "Don't Save" });
+ if (await dontSave.isVisible().catch(() => false)) await dontSave.click();
await sleep(500);
}
+ if ((await edited.count()) > 0) throw new Error('an edited editor stayed open');
await openFile(page, file);
};
diff --git a/src/website/tools/profiler-film/kit/camera.mjs b/src/website/tools/profiler-film/kit/camera.mjs
index 43c5c49b..67123130 100644
--- a/src/website/tools/profiler-film/kit/camera.mjs
+++ b/src/website/tools/profiler-film/kit/camera.mjs
@@ -51,12 +51,13 @@ const showCaption = (page, shown) =>
);
/**
- * A clean frame `/.png`: no hover, and the step caption and pointer
- * hidden only for the instant of the shot (Do Not Disturb keeps toasts off screen).
+ * A clean frame `/.png`: the step caption and pointer hidden only for
+ * the instant of the shot (Do Not Disturb keeps toasts off screen), and the
+ * mouse parked first, so no hover shows, unless `hovering` keeps the one open.
*/
-export async function still(page, dir, name) {
+export async function still(page, dir, name, { hovering = false } = {}) {
fs.mkdirSync(dir, { recursive: true });
- await page.mouse.move(PARK.x, PARK.y);
+ if (!hovering) await page.mouse.move(PARK.x, PARK.y);
await sleep(700);
await showCaption(page, false);
await page.screenshot({ path: path.join(dir, `${name}.png`) });
@@ -64,4 +65,4 @@ export async function still(page, dir, name) {
}
/** A walkthrough's still, into the film's own stills folder; published as `.webp`. */
-export const snap = (shot, name) => still(shot.page, shot.stills, name);
+export const snap = (shot, name, options) => still(shot.page, shot.stills, name, options);
diff --git a/src/website/tools/profiler-film/kit/editor.mjs b/src/website/tools/profiler-film/kit/editor.mjs
index 35f5187f..39255070 100644
--- a/src/website/tools/profiler-film/kit/editor.mjs
+++ b/src/website/tools/profiler-film/kit/editor.mjs
@@ -54,8 +54,10 @@ const FILM_SETTINGS = {
'window.commandCenter': true,
'window.restoreWindows': 'none',
'window.title': '${rootName}',
- // Context menus drawn in the workbench, where the screencast sees them; a native menu never reaches a frame.
+ // Context menus and dialogs drawn in the workbench, where the screencast sees them and a film can
+ // answer them; a native menu or save dialog never reaches a frame.
'window.menuStyle': 'custom',
+ 'window.dialogStyle': 'custom',
'editor.fontSize': 15,
'editor.lineHeight': 24,
'editor.minimap.enabled': false,
diff --git a/src/website/tools/profiler-film/kit/encode.mjs b/src/website/tools/profiler-film/kit/encode.mjs
index a50fb4af..0342e5fe 100644
--- a/src/website/tools/profiler-film/kit/encode.mjs
+++ b/src/website/tools/profiler-film/kit/encode.mjs
@@ -9,7 +9,8 @@ const tool = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
/** Every shoot's working files, gitignored: out//. */
export const out = path.join(tool, 'out');
export const videos = path.resolve(tool, '../../src/assets/videos');
-export const shots = path.resolve(tool, '../../src/assets/screenshots/profiler');
+/** The site's stills: a film publishes into its own folder here, `profiler` unless it names another. */
+export const shots = path.resolve(tool, '../../src/assets/screenshots');
/** H.264 for reach: every site video has an MP4. */
export const H264 = ['-c:v', 'libx264', '-preset', 'veryslow', '-crf', '26', '-pix_fmt', 'yuv420p', '-movflags', '+faststart', '-an'];
@@ -47,10 +48,11 @@ export function poster(video, at, name) {
webp(frame, path.join(videos, `${name}-poster.webp`), 1920);
}
-/** Every PNG still under `dir`, as the site's 2400px WebP of the same name. */
-export function publishStills(dir) {
- fs.mkdirSync(shots, { recursive: true });
+/** Every PNG still under `dir`, as the site's 2400px WebP of the same name in `screenshots/`. */
+export function publishStills(dir, folder = 'profiler') {
+ const target = path.join(shots, folder);
+ fs.mkdirSync(target, { recursive: true });
for (const file of fs.readdirSync(dir).filter((name) => name.endsWith('.png'))) {
- webp(path.join(dir, file), path.join(shots, file.replace(/\.png$/, '.webp')), 2400);
+ webp(path.join(dir, file), path.join(target, file.replace(/\.png$/, '.webp')), 2400);
}
}