From c42015075a217312240571c6f204b23adeaadcf1 Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Wed, 7 Oct 2026 15:24:28 +0100 Subject: [PATCH 1/3] Add a first-install LiquidJava walkthrough Co-authored-by: Codex --- client/.vscodeignore | 1 + client/README.md | 6 ++- client/media/walkthrough/context.md | 9 ++++ client/media/walkthrough/diagnostics.md | 11 +++++ client/media/walkthrough/state-machine.md | 16 ++++++ client/media/walkthrough/verification.md | 13 +++++ client/package.json | 60 ++++++++++++++++++++++- client/src/services/commands.ts | 5 +- client/src/services/walkthrough.ts | 7 +++ client/src/test/walkthrough.test.ts | 45 +++++++++++++++++ 10 files changed, 170 insertions(+), 3 deletions(-) create mode 100644 client/media/walkthrough/context.md create mode 100644 client/media/walkthrough/diagnostics.md create mode 100644 client/media/walkthrough/state-machine.md create mode 100644 client/media/walkthrough/verification.md create mode 100644 client/src/services/walkthrough.ts create mode 100644 client/src/test/walkthrough.test.ts diff --git a/client/.vscodeignore b/client/.vscodeignore index d42f18e5..fcc43ef2 100644 --- a/client/.vscodeignore +++ b/client/.vscodeignore @@ -26,6 +26,7 @@ test/** *.md !README.md !USAGE.md +!media/walkthrough/*.md # Source maps (comment out if you want to include them) **/*.map diff --git a/client/README.md b/client/README.md index 3564a915..808906d6 100644 --- a/client/README.md +++ b/client/README.md @@ -38,6 +38,10 @@ dependencies { A repository with LiquidJava examples is available at [liquidjava-examples](https://github.com/liquid-java/liquidjava-examples). You can try them out without setting up your local environment using [GitHub Codespaces](https://codespaces.new/liquid-java/liquidjava-examples). +### Getting started in VS Code + +The **Get Started with LiquidJava** walkthrough introduces verification, diagnostics, context, and state machines. It opens once on installation, following VS Code’s walkthrough preferences. You can dismiss it at any time and reopen it with **LiquidJava: Show Walkthrough** from the Command Palette or **LiquidJava: Show Commands**. VS Code saves its progress across workspaces. + ### What are Liquid Types? Liquid types extend a language with **logical predicates** over the basic types. They allow developers to restrict the values that a variable, parameter or return value can have. These kinds of constraints help to catch more bugs before the program is executed. For example, they allow us to prevent bugs like array index out-of-bounds or division by zero at compile-time. @@ -138,4 +142,4 @@ For more information, check the following repositories: - [vscode-liquidjava](https://github.com/liquid-java/vscode-liquidjava): Source code of this VS Code extension - [liquidjava-examples](https://github.com/liquid-java/liquidjava-examples): Examples of how to use LiquidJava - [liquid-java-external-libs](https://github.com/liquid-java/liquid-java-external-libs): Examples of how to use LiquidJava to refine external libraries -- [liquidjava-fsm](https://github.com/liquid-java/liquidjava-fsm): State machine parser used by the VS Code language server \ No newline at end of file +- [liquidjava-fsm](https://github.com/liquid-java/liquidjava-fsm): State machine parser used by the VS Code language server diff --git a/client/media/walkthrough/context.md b/client/media/walkthrough/context.md new file mode 100644 index 00000000..cc03caae --- /dev/null +++ b/client/media/walkthrough/context.md @@ -0,0 +1,9 @@ +# Inspect the verifier's context + +The **Context** tab follows your cursor or selection in the active Java file after verification. + +- **Variables** show refinements known at the selected position. +- **Aliases** explain reusable predicates declared with `@RefinementAlias`. +- **Ghosts** describe additional object properties declared with `@Ghost`. + +Move the cursor after an assignment to see the facts available there. Expand or collapse sections to focus on the information you need. If no context is available, verify the file and place the cursor inside a method. diff --git a/client/media/walkthrough/diagnostics.md b/client/media/walkthrough/diagnostics.md new file mode 100644 index 00000000..6cc52de2 --- /dev/null +++ b/client/media/walkthrough/diagnostics.md @@ -0,0 +1,11 @@ +# Read a diagnostic + +For an assignment that violates a refinement: + +- **Found** describes the value the verifier inferred. +- **Expected** describes the constraint the value must satisfy. +- **Location** takes you to the relevant source code. +- A **Counterexample** shows values that violate the constraint, when available. +- A **Hint** suggests a next step, when available. + +Use **View related context** to inspect the facts at the error. Fix the Java code or its annotation, then save or verify again to check the result. diff --git a/client/media/walkthrough/state-machine.md b/client/media/walkthrough/state-machine.md new file mode 100644 index 00000000..7bea22fb --- /dev/null +++ b/client/media/walkthrough/state-machine.md @@ -0,0 +1,16 @@ +# Follow object states + +```java +@StateSet({"open", "closed"}) +class MyFile { + @StateRefinement(to = "open(this)") + MyFile() {} + + @StateRefinement(from = "open(this)", to = "closed(this)") + void close() {} +} +``` + +The **State Machine** tab visualizes the declared states and method transitions. Here, construction establishes `open`, and `close()` requires `open` and establishes `closed`. + +Use **Expand Conditions** to inspect preconditions and postconditions. On a state error, **View error on state machine** connects the diagnostic to the diagram. Files without state annotations may have no diagram. diff --git a/client/media/walkthrough/verification.md b/client/media/walkthrough/verification.md new file mode 100644 index 00000000..3cc23819 --- /dev/null +++ b/client/media/walkthrough/verification.md @@ -0,0 +1,13 @@ +# Verify refinements + +```java +@Refinement("_ > 0") +int count = 3; +count = -1; // refinement error +``` + +The refinement restricts `count` to positive values. LiquidJava rejects the assignment of `-1` at verification time. + +Open or save the Java file to verify it, or run **LiquidJava: Verify** from the Command Palette. Check the LiquidJava status bar for the result. + +Use the installation instructions in the extension's README to add `liquidjava-api` to your project. If the verifier is stopped, run **LiquidJava: Start**. diff --git a/client/package.json b/client/package.json index b5891b5b..68fdb0ca 100644 --- a/client/package.json +++ b/client/package.json @@ -92,6 +92,11 @@ "title": "Show Commands", "category": "LiquidJava" }, + { + "command": "liquidjava.showWalkthrough", + "title": "Show Walkthrough", + "category": "LiquidJava" + }, { "command": "liquidjava.showLogs", "title": "Show Logs", @@ -149,7 +154,60 @@ "[java]": { "editor.autoClosingBrackets": "always" } - } + }, + "walkthroughs": [ + { + "id": "getStarted", + "title": "Get Started with LiquidJava", + "description": "Verify refinements, understand diagnostics, inspect context, and explore object states.", + "steps": [ + { + "id": "verification", + "title": "Verify your Java code", + "description": "LiquidJava checks refinements and object states before you run your program. Open a Java project with the `liquidjava-api` dependency and a Java runtime available in `JAVA_HOME` or `PATH`. Files are verified when opened or saved; use **LiquidJava: Verify** to check the active Java file manually. The status bar shows the verification result.\n[Verify the active Java file](command:liquidjava.verify)", + "media": { + "markdown": "media/walkthrough/verification.md" + }, + "completionEvents": [ + "onCommand:liquidjava.verify" + ] + }, + { + "id": "diagnostics", + "title": "Understand verification diagnostics", + "description": "Open the LiquidJava view and select **Verification**. Compare **Found** with **Expected**, inspect a counterexample or hint when available, and click a location to jump to the source. Switch between **Current file** and **Workspace** to find other errors.\n[Open the LiquidJava view](command:liquidjava.showView)", + "media": { + "markdown": "media/walkthrough/diagnostics.md" + }, + "completionEvents": [ + "onStepSelected" + ] + }, + { + "id": "context", + "title": "Inspect the context at your cursor", + "description": "In the LiquidJava view, select **Context** and move the cursor or select code in a Java file. Inspect variable refinements, predicate aliases, and ghost variables to understand what the verifier knows at that position. Click a variable to navigate to its source.\n[Open the LiquidJava view](command:liquidjava.showView)", + "media": { + "markdown": "media/walkthrough/context.md" + }, + "completionEvents": [ + "onStepSelected" + ] + }, + { + "id": "stateMachine", + "title": "Explore object state machines", + "description": "Open a Java class with `@StateSet` and `@StateRefinement`, then select **State Machine** in the LiquidJava view. Follow method transitions and their conditions; pan and zoom to inspect the diagram. For a state error, use **View error on state machine** in its diagnostic to see the related states and call.\n[Open the LiquidJava view](command:liquidjava.showView)", + "media": { + "markdown": "media/walkthrough/state-machine.md" + }, + "completionEvents": [ + "onStepSelected" + ] + } + ] + } + ] }, "scripts": { "vscode:prepublish": "npm run package", diff --git a/client/src/services/commands.ts b/client/src/services/commands.ts index 9998b151..b4a08739 100644 --- a/client/src/services/commands.ts +++ b/client/src/services/commands.ts @@ -1,10 +1,12 @@ import * as vscode from "vscode"; import { startExtension, stopExtension, restartExtension } from "../extension"; import { verify } from "./diagnostics"; +import { showWalkthrough } from './walkthrough'; const commandIcons: Record = { "liquidjava.showLogs": "$(output)", "liquidjava.showView": "$(window)", + "liquidjava.showWalkthrough": "$(book)", "liquidjava.start": "$(play)", "liquidjava.stop": "$(debug-stop)", "liquidjava.restart": "$(debug-restart)", @@ -12,6 +14,7 @@ const commandIcons: Record = { }; const commandHandlers: Record Promise> = { + "liquidjava.showWalkthrough": showWalkthrough, "liquidjava.start": async (context) => await startExtension(context), "liquidjava.stop": async () => await stopExtension(), "liquidjava.restart": async (context) => await restartExtension(context), @@ -51,4 +54,4 @@ export function registerCommands(context: vscode.ExtensionContext) { if (selected) vscode.commands.executeCommand(selected.command); }) ); -} \ No newline at end of file +} diff --git a/client/src/services/walkthrough.ts b/client/src/services/walkthrough.ts new file mode 100644 index 00000000..1e8255d7 --- /dev/null +++ b/client/src/services/walkthrough.ts @@ -0,0 +1,7 @@ +import * as vscode from 'vscode'; + +export const WALKTHROUGH_ID = 'AlcidesFonseca.liquid-java#getStarted'; + +export async function showWalkthrough(): Promise { + await vscode.commands.executeCommand('workbench.action.openWalkthrough', WALKTHROUGH_ID); +} diff --git a/client/src/test/walkthrough.test.ts b/client/src/test/walkthrough.test.ts new file mode 100644 index 00000000..7bf6a4ee --- /dev/null +++ b/client/src/test/walkthrough.test.ts @@ -0,0 +1,45 @@ +import * as assert from 'node:assert/strict'; +import { readFile } from 'node:fs/promises'; +import * as vscode from 'vscode'; +import { WALKTHROUGH_ID } from '../services/walkthrough'; +import type { LiquidJavaTestApi } from '../types/test-api'; + +suite('LiquidJava walkthrough', () => { + let installed: vscode.Extension; + + suiteSetup(async () => { + installed = vscode.extensions.getExtension('AlcidesFonseca.liquid-java')!; + assert.ok(installed); + await installed.activate(); + }); + + teardown(async () => { + await vscode.commands.executeCommand('workbench.action.closeAllEditors'); + }); + + test('contributes four steps with packaged content and registered actions', async () => { + const walkthrough = installed.packageJSON.contributes.walkthroughs.find( + (entry: { id: string }) => `${installed.id}#${entry.id}` === WALKTHROUGH_ID); + assert.ok(walkthrough); + assert.deepEqual(walkthrough.steps.map((step: { id: string }) => step.id), + ['verification', 'diagnostics', 'context', 'stateMachine']); + const commands = await vscode.commands.getCommands(true); + assert.ok(commands.includes('liquidjava.showWalkthrough')); + for (const step of walkthrough.steps) { + const content = await readFile(vscode.Uri.joinPath(installed.extensionUri, step.media.markdown).fsPath, 'utf8'); + assert.ok(content.trim(), `${step.id} must have readable content`); + const links = [...step.description.matchAll(/\]\(command:([^)?]+)\)/g)]; + assert.ok(links.length > 0, `${step.id} must have an action`); + for (const link of links) assert.ok(commands.includes(link[1]), `unregistered action: ${link[1]}`); + } + }); + + test('reopens the native walkthrough repeatedly after dismissal', async () => { + await vscode.commands.executeCommand('workbench.action.closeAllEditors'); + await vscode.commands.executeCommand('liquidjava.showWalkthrough'); + assert.ok(vscode.window.tabGroups.activeTabGroup.activeTab, 'the registered command must reopen the walkthrough'); + await vscode.commands.executeCommand('workbench.action.closeAllEditors'); + await vscode.commands.executeCommand('liquidjava.showWalkthrough'); + assert.ok(vscode.window.tabGroups.activeTabGroup.activeTab, 'manual reopening must remain available'); + }); +}); From f7164d583ae3042bd6014ed073895e41ef683d59 Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Wed, 7 Oct 2026 15:37:15 +0100 Subject: [PATCH 2/3] Simplify the walkthrough and explain project setup Co-authored-by: Codex --- client/README.md | 2 +- client/media/walkthrough/annotations.md | 23 +++++++ client/media/walkthrough/commands.md | 5 ++ client/media/walkthrough/context.md | 10 +-- client/media/walkthrough/diagnostics.md | 12 +--- client/media/walkthrough/logs.md | 5 ++ client/media/walkthrough/setup.md | 5 ++ client/media/walkthrough/state-machine.md | 17 +---- client/media/walkthrough/tutorial.md | 5 ++ client/media/walkthrough/verification.md | 17 ++--- client/package.json | 75 ++++++++++++++++++++--- client/src/test/walkthrough.test.ts | 31 +++++++++- 12 files changed, 152 insertions(+), 55 deletions(-) create mode 100644 client/media/walkthrough/annotations.md create mode 100644 client/media/walkthrough/commands.md create mode 100644 client/media/walkthrough/logs.md create mode 100644 client/media/walkthrough/setup.md create mode 100644 client/media/walkthrough/tutorial.md diff --git a/client/README.md b/client/README.md index 808906d6..083240c3 100644 --- a/client/README.md +++ b/client/README.md @@ -40,7 +40,7 @@ A repository with LiquidJava examples is available at [liquidjava-examples](http ### Getting started in VS Code -The **Get Started with LiquidJava** walkthrough introduces verification, diagnostics, context, and state machines. It opens once on installation, following VS Code’s walkthrough preferences. You can dismiss it at any time and reopen it with **LiquidJava: Show Walkthrough** from the Command Palette or **LiquidJava: Show Commands**. VS Code saves its progress across workspaces. +The **Get Started with LiquidJava** walkthrough covers Java setup and the Maven/Gradle annotation dependency, then introduces verification and its status indicator, diagnostics, context, state machines, the Command Palette, and logs. It also links to the [interactive tutorial](https://liquid-java.github.io/liquidjava-interactive-tutorial/). It opens once on installation, following VS Code’s walkthrough preferences. You can dismiss it at any time and reopen it with **LiquidJava: Show Walkthrough** from the Command Palette or **LiquidJava: Show Commands**. VS Code saves its progress across workspaces. ### What are Liquid Types? diff --git a/client/media/walkthrough/annotations.md b/client/media/walkthrough/annotations.md new file mode 100644 index 00000000..b88406ee --- /dev/null +++ b/client/media/walkthrough/annotations.md @@ -0,0 +1,23 @@ +## Maven — `pom.xml` + +```xml + + io.github.liquid-java + liquidjava-api + 0.0.7 + +``` + +## Gradle — `build.gradle` + +```groovy +repositories { + mavenCentral() +} + +dependencies { + implementation 'io.github.liquid-java:liquidjava-api:0.0.7' +} +``` + +Reload your Java project after changing dependencies. Import annotations from `liquidjava.specification`, for example `liquidjava.specification.Refinement`. diff --git a/client/media/walkthrough/commands.md b/client/media/walkthrough/commands.md new file mode 100644 index 00000000..29ad748f --- /dev/null +++ b/client/media/walkthrough/commands.md @@ -0,0 +1,5 @@ +# Run a command + +Press **F1** to open the Command Palette, then type **LiquidJava**. + +Use **Start**, **Stop**, or **Restart** to control the verifier. Use **Verify** after returning to a Java editor. diff --git a/client/media/walkthrough/context.md b/client/media/walkthrough/context.md index cc03caae..f45f2ea6 100644 --- a/client/media/walkthrough/context.md +++ b/client/media/walkthrough/context.md @@ -1,9 +1,5 @@ -# Inspect the verifier's context +# See what the verifier knows -The **Context** tab follows your cursor or selection in the active Java file after verification. +Move the cursor in a verified Java file to update the context. -- **Variables** show refinements known at the selected position. -- **Aliases** explain reusable predicates declared with `@RefinementAlias`. -- **Ghosts** describe additional object properties declared with `@Ghost`. - -Move the cursor after an assignment to see the facts available there. Expand or collapse sections to focus on the information you need. If no context is available, verify the file and place the cursor inside a method. +Expand **Variables**, **Aliases**, or **Ghosts**. Click a variable to reveal its source. diff --git a/client/media/walkthrough/diagnostics.md b/client/media/walkthrough/diagnostics.md index 6cc52de2..9ef3bc5f 100644 --- a/client/media/walkthrough/diagnostics.md +++ b/client/media/walkthrough/diagnostics.md @@ -1,11 +1,5 @@ -# Read a diagnostic +# Find the cause -For an assignment that violates a refinement: +Compare **Found** and **Expected**, then inspect a **Hint** or **Counterexample** when available. -- **Found** describes the value the verifier inferred. -- **Expected** describes the constraint the value must satisfy. -- **Location** takes you to the relevant source code. -- A **Counterexample** shows values that violate the constraint, when available. -- A **Hint** suggests a next step, when available. - -Use **View related context** to inspect the facts at the error. Fix the Java code or its annotation, then save or verify again to check the result. +Switch between **Current file** and **Workspace** to find other errors. diff --git a/client/media/walkthrough/logs.md b/client/media/walkthrough/logs.md new file mode 100644 index 00000000..e979b633 --- /dev/null +++ b/client/media/walkthrough/logs.md @@ -0,0 +1,5 @@ +# Inspect the logger + +Run **LiquidJava: Show Logs** to open the **LiquidJava** output channel. + +Entries include a timestamp, **CLIENT** or **SERVER** source, and **INFO** or **ERROR** level. diff --git a/client/media/walkthrough/setup.md b/client/media/walkthrough/setup.md new file mode 100644 index 00000000..f0406524 --- /dev/null +++ b/client/media/walkthrough/setup.md @@ -0,0 +1,5 @@ +# Before you start + +Make a Java runtime available through **JAVA_HOME** or **PATH**. + +Install **Language Support for Java by Red Hat** and open the folder containing your Java project. diff --git a/client/media/walkthrough/state-machine.md b/client/media/walkthrough/state-machine.md index 7bea22fb..d5a30def 100644 --- a/client/media/walkthrough/state-machine.md +++ b/client/media/walkthrough/state-machine.md @@ -1,16 +1,5 @@ -# Follow object states +# Follow the transitions -```java -@StateSet({"open", "closed"}) -class MyFile { - @StateRefinement(to = "open(this)") - MyFile() {} +Use **Expand Conditions** to inspect method preconditions and postconditions. - @StateRefinement(from = "open(this)", to = "closed(this)") - void close() {} -} -``` - -The **State Machine** tab visualizes the declared states and method transitions. Here, construction establishes `open`, and `close()` requires `open` and establishes `closed`. - -Use **Expand Conditions** to inspect preconditions and postconditions. On a state error, **View error on state machine** connects the diagnostic to the diagram. Files without state annotations may have no diagram. +From a state error, choose **View error on state machine** to see the related states and call. diff --git a/client/media/walkthrough/tutorial.md b/client/media/walkthrough/tutorial.md new file mode 100644 index 00000000..873f21cd --- /dev/null +++ b/client/media/walkthrough/tutorial.md @@ -0,0 +1,5 @@ +# Try it in your browser + +Edit short Java examples and run the LiquidJava verifier directly in the interactive tutorial. + +Learn about refinements, contracts, object states, and ghost variables through exercises. diff --git a/client/media/walkthrough/verification.md b/client/media/walkthrough/verification.md index 3cc23819..45d6d013 100644 --- a/client/media/walkthrough/verification.md +++ b/client/media/walkthrough/verification.md @@ -1,13 +1,8 @@ -# Verify refinements +# Read the status indicator -```java -@Refinement("_ > 0") -int count = 3; -count = -1; // refinement error -``` +- **Spinning arrows:** checking your code. +- **Check mark:** verification passed. +- **Cross:** verification failed or the verifier crashed. +- **Slashed circle:** verifier stopped. -The refinement restricts `count` to positive values. LiquidJava rejects the assignment of `-1` at verification time. - -Open or save the Java file to verify it, or run **LiquidJava: Verify** from the Command Palette. Check the LiquidJava status bar for the result. - -Use the installation instructions in the extension's README to add `liquidjava-api` to your project. If the verifier is stopped, run **LiquidJava: Start**. +Hover for details. Click the indicator to open LiquidJava commands. diff --git a/client/package.json b/client/package.json index 68fdb0ca..a86cb68a 100644 --- a/client/package.json +++ b/client/package.json @@ -159,23 +159,56 @@ { "id": "getStarted", "title": "Get Started with LiquidJava", - "description": "Verify refinements, understand diagnostics, inspect context, and explore object states.", + "description": "Set up your project and discover verification, views, commands, and logs.", "steps": [ + { + "id": "setup", + "title": "Set up Java support", + "description": "Install Java and **Language Support for Java by Red Hat**, then open your Java project folder.\n[Get Java language support](https://marketplace.visualstudio.com/items?itemName=redhat.java)", + "media": { + "markdown": "media/walkthrough/setup.md" + }, + "completionEvents": [ + "onStepSelected" + ] + }, + { + "id": "annotations", + "title": "Add the annotation API", + "description": "Add `liquidjava-api` to your Maven or Gradle project to use annotations such as `@Refinement`.", + "media": { + "markdown": "media/walkthrough/annotations.md" + }, + "completionEvents": [ + "onStepSelected" + ] + }, { "id": "verification", - "title": "Verify your Java code", - "description": "LiquidJava checks refinements and object states before you run your program. Open a Java project with the `liquidjava-api` dependency and a Java runtime available in `JAVA_HOME` or `PATH`. Files are verified when opened or saved; use **LiquidJava: Verify** to check the active Java file manually. The status bar shows the verification result.\n[Verify the active Java file](command:liquidjava.verify)", + "title": "Verification and status", + "description": "Open or save a Java file to verify it. Watch the **LiquidJava** indicator in the bottom status bar.", "media": { "markdown": "media/walkthrough/verification.md" }, "completionEvents": [ - "onCommand:liquidjava.verify" + "onStepSelected" + ] + }, + { + "id": "commands", + "title": "Find LiquidJava commands", + "description": "Open the **Command Palette**, type `LiquidJava`, and choose a command.\n[Open Command Palette](command:workbench.action.showCommands)", + "media": { + "markdown": "media/walkthrough/commands.md" + }, + "completionEvents": [ + "onStepSelected" ] }, { "id": "diagnostics", - "title": "Understand verification diagnostics", - "description": "Open the LiquidJava view and select **Verification**. Compare **Found** with **Expected**, inspect a counterexample or hint when available, and click a location to jump to the source. Switch between **Current file** and **Workspace** to find other errors.\n[Open the LiquidJava view](command:liquidjava.showView)", + "title": "Understand diagnostics", + "description": "The **Verification** tab compares **Found** with **Expected**. Click a location to jump to the error.\n[Open LiquidJava view](command:liquidjava.showView)", "media": { "markdown": "media/walkthrough/diagnostics.md" }, @@ -185,8 +218,8 @@ }, { "id": "context", - "title": "Inspect the context at your cursor", - "description": "In the LiquidJava view, select **Context** and move the cursor or select code in a Java file. Inspect variable refinements, predicate aliases, and ghost variables to understand what the verifier knows at that position. Click a variable to navigate to its source.\n[Open the LiquidJava view](command:liquidjava.showView)", + "title": "Inspect context", + "description": "The **Context** tab shows variable refinements, aliases, and ghosts at your cursor or selection.\n[Open LiquidJava view](command:liquidjava.showView)", "media": { "markdown": "media/walkthrough/context.md" }, @@ -196,14 +229,36 @@ }, { "id": "stateMachine", - "title": "Explore object state machines", - "description": "Open a Java class with `@StateSet` and `@StateRefinement`, then select **State Machine** in the LiquidJava view. Follow method transitions and their conditions; pan and zoom to inspect the diagram. For a state error, use **View error on state machine** in its diagnostic to see the related states and call.\n[Open the LiquidJava view](command:liquidjava.showView)", + "title": "Explore state machines", + "description": "The **State Machine** tab shows object states and method transitions declared with `@StateSet` and `@StateRefinement`.\n[Open LiquidJava view](command:liquidjava.showView)", "media": { "markdown": "media/walkthrough/state-machine.md" }, "completionEvents": [ "onStepSelected" ] + }, + { + "id": "logs", + "title": "Check the logs", + "description": "The **LiquidJava** output channel records extension and verifier activity. Use it to investigate startup or verification problems.\n[Show LiquidJava logs](command:liquidjava.showLogs)", + "media": { + "markdown": "media/walkthrough/logs.md" + }, + "completionEvents": [ + "onStepSelected" + ] + }, + { + "id": "tutorial", + "title": "Learn LiquidJava interactively", + "description": "Try refinements, method contracts, object states, and ghosts in the browser. No local setup needed.\n[Open interactive tutorial](https://liquid-java.github.io/liquidjava-interactive-tutorial/)", + "media": { + "markdown": "media/walkthrough/tutorial.md" + }, + "completionEvents": [ + "onStepSelected" + ] } ] } diff --git a/client/src/test/walkthrough.test.ts b/client/src/test/walkthrough.test.ts index 7bf6a4ee..d98ca668 100644 --- a/client/src/test/walkthrough.test.ts +++ b/client/src/test/walkthrough.test.ts @@ -17,21 +17,38 @@ suite('LiquidJava walkthrough', () => { await vscode.commands.executeCommand('workbench.action.closeAllEditors'); }); - test('contributes four steps with packaged content and registered actions', async () => { + test('contributes the tour with readable content and usable actions', async () => { const walkthrough = installed.packageJSON.contributes.walkthroughs.find( (entry: { id: string }) => `${installed.id}#${entry.id}` === WALKTHROUGH_ID); assert.ok(walkthrough); + assert.equal(walkthrough.title, 'Get Started with LiquidJava'); assert.deepEqual(walkthrough.steps.map((step: { id: string }) => step.id), - ['verification', 'diagnostics', 'context', 'stateMachine']); + ['setup', 'annotations', 'verification', 'commands', 'diagnostics', 'context', 'stateMachine', 'logs', 'tutorial']); const commands = await vscode.commands.getCommands(true); assert.ok(commands.includes('liquidjava.showWalkthrough')); for (const step of walkthrough.steps) { const content = await readFile(vscode.Uri.joinPath(installed.extensionUri, step.media.markdown).fsPath, 'utf8'); assert.ok(content.trim(), `${step.id} must have readable content`); const links = [...step.description.matchAll(/\]\(command:([^)?]+)\)/g)]; - assert.ok(links.length > 0, `${step.id} must have an action`); for (const link of links) assert.ok(commands.includes(link[1]), `unregistered action: ${link[1]}`); + assert.ok(!links.some(link => link[1] === 'liquidjava.verify'), + 'walkthrough actions must work without an active Java editor'); } + const description = (id: string) => walkthrough.steps.find((step: { id: string }) => step.id === id).description; + assert.ok(description('commands').includes('(command:workbench.action.showCommands)')); + assert.ok(description('logs').includes('(command:liquidjava.showLogs)')); + assert.ok(description('tutorial').includes('(https://liquid-java.github.io/liquidjava-interactive-tutorial/)')); + }); + + test('uses the README dependency snippets for Maven and Gradle setup', async () => { + const readme = await readFile(vscode.Uri.joinPath(installed.extensionUri, 'README.md').fsPath, 'utf8'); + const setup = await readFile(vscode.Uri.joinPath(installed.extensionUri, 'media/walkthrough/annotations.md').fsPath, 'utf8'); + for (const language of ['xml', 'groovy']) { + const snippet = readme.match(new RegExp('```' + language + '\\n[\\s\\S]*?```'))?.[0]; + assert.ok(snippet, `README must include the ${language} dependency snippet`); + assert.ok(setup.includes(snippet), `walkthrough ${language} dependency must match the README`); + } + assert.ok(setup.includes('liquidjava.specification.Refinement')); }); test('reopens the native walkthrough repeatedly after dismissal', async () => { @@ -42,4 +59,12 @@ suite('LiquidJava walkthrough', () => { await vscode.commands.executeCommand('liquidjava.showWalkthrough'); assert.ok(vscode.window.tabGroups.activeTabGroup.activeTab, 'manual reopening must remain available'); }); + + test('opens the logs from the walkthrough without an active Java editor', async () => { + await vscode.commands.executeCommand('workbench.action.closeAllEditors'); + await vscode.commands.executeCommand('liquidjava.showWalkthrough'); + assert.equal(vscode.window.activeTextEditor, undefined); + await vscode.commands.executeCommand('liquidjava.showLogs'); + assert.ok(vscode.window.tabGroups.activeTabGroup.activeTab, 'opening logs must keep the walkthrough available'); + }); }); From 48ae2a2a27e81bb952d0776ac9c13869b61236d9 Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Wed, 7 Oct 2026 15:56:44 +0100 Subject: [PATCH 3/3] Make walkthrough setup visible and expand feature guidance Co-authored-by: Codex --- client/README.md | 2 +- client/media/walkthrough/commands.md | 6 +-- client/media/walkthrough/context.md | 8 ++- client/media/walkthrough/diagnostics.md | 8 ++- client/media/walkthrough/setup.md | 5 -- client/media/walkthrough/state-machine.md | 7 ++- client/media/walkthrough/tutorial.md | 6 +-- client/package.json | 45 ++++++++-------- client/src/services/commands.ts | 6 ++- client/src/services/walkthrough.ts | 9 ++++ client/src/test/walkthrough.test.ts | 63 ++++++++++++++++++++--- 11 files changed, 109 insertions(+), 56 deletions(-) delete mode 100644 client/media/walkthrough/setup.md diff --git a/client/README.md b/client/README.md index 083240c3..7b359afc 100644 --- a/client/README.md +++ b/client/README.md @@ -40,7 +40,7 @@ A repository with LiquidJava examples is available at [liquidjava-examples](http ### Getting started in VS Code -The **Get Started with LiquidJava** walkthrough covers Java setup and the Maven/Gradle annotation dependency, then introduces verification and its status indicator, diagnostics, context, state machines, the Command Palette, and logs. It also links to the [interactive tutorial](https://liquid-java.github.io/liquidjava-interactive-tutorial/). It opens once on installation, following VS Code’s walkthrough preferences. You can dismiss it at any time and reopen it with **LiquidJava: Show Walkthrough** from the Command Palette or **LiquidJava: Show Commands**. VS Code saves its progress across workspaces. +The **Get Started with LiquidJava** walkthrough introduces LiquidJava and its interactive tutorial, includes copyable Maven/Gradle annotation dependencies, then explains verification and its status indicator, diagnostics, context, state machines, the Command Palette, and logs. It also links to the [interactive tutorial](https://liquid-java.github.io/liquidjava-interactive-tutorial/). It opens once on installation, following VS Code’s walkthrough preferences. You can dismiss it at any time and reopen it with **LiquidJava: Show Walkthrough** from the Command Palette or **LiquidJava: Show Commands**. VS Code saves its progress across workspaces. ### What are Liquid Types? diff --git a/client/media/walkthrough/commands.md b/client/media/walkthrough/commands.md index 29ad748f..231fc5c4 100644 --- a/client/media/walkthrough/commands.md +++ b/client/media/walkthrough/commands.md @@ -1,5 +1,5 @@ -# Run a command +# LiquidJava commands -Press **F1** to open the Command Palette, then type **LiquidJava**. +Click the status indicator or **Show LiquidJava commands** to open the LiquidJava-only command list. -Use **Start**, **Stop**, or **Restart** to control the verifier. Use **Verify** after returning to a Java editor. +Use **Start**, **Stop**, or **Restart** to control the verifier. Run **Verify** after returning to a Java editor. diff --git a/client/media/walkthrough/context.md b/client/media/walkthrough/context.md index f45f2ea6..4b8349ff 100644 --- a/client/media/walkthrough/context.md +++ b/client/media/walkthrough/context.md @@ -1,5 +1,3 @@ -# See what the verifier knows - -Move the cursor in a verified Java file to update the context. - -Expand **Variables**, **Aliases**, or **Ghosts**. Click a variable to reveal its source. +The **Context** tab shows **Variables**, **Aliases**, and **Ghosts** after verification. Expand or collapse each section. +Local variables are filtered by the cursor: only declarations before it and in enclosing scopes appear. Select a range to inspect declarations that overlap it; global variables are also included. +Click a variable to reveal its source. Variables relevant to a nearby error are highlighted alongside the failing refinement. diff --git a/client/media/walkthrough/diagnostics.md b/client/media/walkthrough/diagnostics.md index 9ef3bc5f..410e36a7 100644 --- a/client/media/walkthrough/diagnostics.md +++ b/client/media/walkthrough/diagnostics.md @@ -1,5 +1,3 @@ -# Find the cause - -Compare **Found** and **Expected**, then inspect a **Hint** or **Counterexample** when available. - -Switch between **Current file** and **Workspace** to find other errors. +The **Verification** tab compares **Found** with **Expected**. Click a location or variable to jump to its source. +When a simplification history is available, use **Previous simplification** and **Next simplification** to inspect highlighted changes. Inspect a **Hint** or **Counterexample** when available. +Switch between **Current file** and **Workspace**, or use **View related context** and **View error on state machine** to investigate an error. diff --git a/client/media/walkthrough/setup.md b/client/media/walkthrough/setup.md deleted file mode 100644 index f0406524..00000000 --- a/client/media/walkthrough/setup.md +++ /dev/null @@ -1,5 +0,0 @@ -# Before you start - -Make a Java runtime available through **JAVA_HOME** or **PATH**. - -Install **Language Support for Java by Red Hat** and open the folder containing your Java project. diff --git a/client/media/walkthrough/state-machine.md b/client/media/walkthrough/state-machine.md index d5a30def..a12bc4bb 100644 --- a/client/media/walkthrough/state-machine.md +++ b/client/media/walkthrough/state-machine.md @@ -1,5 +1,4 @@ -# Follow the transitions - -Use **Expand Conditions** to inspect method preconditions and postconditions. - +The **State Machine** tab shows object states and method transitions declared with `@StateSet` and `@StateRefinement`. +Pan the diagram; use **Zoom In**, **Zoom Out**, **Reset Zoom**, and **Toggle Orientation** to inspect it. +Use **Expand Conditions** or **Collapse Conditions** to show or hide preconditions and postconditions. **Copy Mermaid Source** copies the diagram definition. From a state error, choose **View error on state machine** to see the related states and call. diff --git a/client/media/walkthrough/tutorial.md b/client/media/walkthrough/tutorial.md index 873f21cd..e7ff5325 100644 --- a/client/media/walkthrough/tutorial.md +++ b/client/media/walkthrough/tutorial.md @@ -1,5 +1,5 @@ -# Try it in your browser +# Learn LiquidJava -Edit short Java examples and run the LiquidJava verifier directly in the interactive tutorial. +LiquidJava is an additional type checker for Java, based on **liquid types** and **typestates**, which provides stronger safety guarantees to Java programs at compile-time. -Learn about refinements, contracts, object states, and ghost variables through exercises. +Edit short Java examples and run verification in the browser with the interactive tutorial. No local setup needed. diff --git a/client/package.json b/client/package.json index a86cb68a..814ca377 100644 --- a/client/package.json +++ b/client/package.json @@ -130,6 +130,16 @@ "command": "liquidjava.verify", "title": "Verify", "category": "LiquidJava" + }, + { + "command": "liquidjava.copyMavenDependency", + "title": "Copy Maven Dependency", + "category": "LiquidJava" + }, + { + "command": "liquidjava.copyGradleDependency", + "title": "Copy Gradle Dependency", + "category": "LiquidJava" } ], "menus": { @@ -159,14 +169,14 @@ { "id": "getStarted", "title": "Get Started with LiquidJava", - "description": "Set up your project and discover verification, views, commands, and logs.", + "description": "Learn LiquidJava, add annotations, and discover verification, views, commands, and logs.", "steps": [ { - "id": "setup", - "title": "Set up Java support", - "description": "Install Java and **Language Support for Java by Red Hat**, then open your Java project folder.\n[Get Java language support](https://marketplace.visualstudio.com/items?itemName=redhat.java)", + "id": "tutorial", + "title": "Learn LiquidJava", + "description": "LiquidJava is an additional type checker for Java, based on **liquid types** and **typestates**, which provides stronger safety guarantees to Java programs at compile-time.\nLearn refinements, method contracts, object states, and ghost variables with short examples in the interactive tutorial.\n[Open interactive tutorial](https://liquid-java.github.io/liquidjava-interactive-tutorial/)", "media": { - "markdown": "media/walkthrough/setup.md" + "markdown": "media/walkthrough/tutorial.md" }, "completionEvents": [ "onStepSelected" @@ -175,7 +185,7 @@ { "id": "annotations", "title": "Add the annotation API", - "description": "Add `liquidjava-api` to your Maven or Gradle project to use annotations such as `@Refinement`.", + "description": "Add ``liquidjava-api`` to your project to use annotations such as ``@Refinement``.\n**Maven — pom.xml**\n````\n`` io.github.liquid-java``\n`` liquidjava-api``\n`` 0.0.7``\n````\n[Copy Maven dependency](command:liquidjava.copyMavenDependency)\n**Gradle — build.gradle**\n``repositories {``\n`` mavenCentral()``\n``}``\n``dependencies {``\n`` implementation 'io.github.liquid-java:liquidjava-api:0.0.7'``\n``}``\n[Copy Gradle dependency](command:liquidjava.copyGradleDependency)\nReload the Java project after changing dependencies. Import annotations from ``liquidjava.specification``.", "media": { "markdown": "media/walkthrough/annotations.md" }, @@ -186,7 +196,7 @@ { "id": "verification", "title": "Verification and status", - "description": "Open or save a Java file to verify it. Watch the **LiquidJava** indicator in the bottom status bar.", + "description": "Open or save a Java file to verify it. Watch the **LiquidJava** indicator in the bottom status bar.\nA spinner means checking; a check mark means passed; a cross means failed or crashed; a slashed circle means stopped. Hover for details or click for LiquidJava commands.", "media": { "markdown": "media/walkthrough/verification.md" }, @@ -197,7 +207,7 @@ { "id": "commands", "title": "Find LiquidJava commands", - "description": "Open the **Command Palette**, type `LiquidJava`, and choose a command.\n[Open Command Palette](command:workbench.action.showCommands)", + "description": "Use the status indicator or **Show LiquidJava commands** below to list only LiquidJava actions.\nChoose **Start**, **Stop**, or **Restart** to control the verifier. Use **Verify** after returning to a Java editor. You can also press **F1** and search for LiquidJava in the Command Palette.\n[Show LiquidJava commands](command:liquidjava.showCommands)", "media": { "markdown": "media/walkthrough/commands.md" }, @@ -208,7 +218,7 @@ { "id": "diagnostics", "title": "Understand diagnostics", - "description": "The **Verification** tab compares **Found** with **Expected**. Click a location to jump to the error.\n[Open LiquidJava view](command:liquidjava.showView)", + "description": "The **Verification** tab compares **Found** with **Expected**. Click a location or variable to jump to its source.\nWhen a simplification history is available, use **Previous simplification** and **Next simplification** to inspect highlighted changes. Inspect a **Hint** or **Counterexample** when available.\nSwitch between **Current file** and **Workspace**, or use **View related context** and **View error on state machine** to investigate an error.\n[Open LiquidJava view](command:liquidjava.showView)", "media": { "markdown": "media/walkthrough/diagnostics.md" }, @@ -219,7 +229,7 @@ { "id": "context", "title": "Inspect context", - "description": "The **Context** tab shows variable refinements, aliases, and ghosts at your cursor or selection.\n[Open LiquidJava view](command:liquidjava.showView)", + "description": "The **Context** tab shows **Variables**, **Aliases**, and **Ghosts** after verification. Expand or collapse each section.\nLocal variables are filtered by the cursor: only declarations before it and in enclosing scopes appear. Select a range to inspect declarations that overlap it; global variables are also included.\nClick a variable to reveal its source. Variables relevant to a nearby error are highlighted alongside the failing refinement.\n[Open LiquidJava view](command:liquidjava.showView)", "media": { "markdown": "media/walkthrough/context.md" }, @@ -230,7 +240,7 @@ { "id": "stateMachine", "title": "Explore state machines", - "description": "The **State Machine** tab shows object states and method transitions declared with `@StateSet` and `@StateRefinement`.\n[Open LiquidJava view](command:liquidjava.showView)", + "description": "The **State Machine** tab shows object states and method transitions declared with ``@StateSet`` and ``@StateRefinement``.\nPan the diagram; use **Zoom In**, **Zoom Out**, **Reset Zoom**, and **Toggle Orientation** to inspect it.\nUse **Expand Conditions** or **Collapse Conditions** to show or hide preconditions and postconditions. **Copy Mermaid Source** copies the diagram definition.\nFrom a state error, choose **View error on state machine** to see the related states and call.\n[Open LiquidJava view](command:liquidjava.showView)", "media": { "markdown": "media/walkthrough/state-machine.md" }, @@ -241,24 +251,13 @@ { "id": "logs", "title": "Check the logs", - "description": "The **LiquidJava** output channel records extension and verifier activity. Use it to investigate startup or verification problems.\n[Show LiquidJava logs](command:liquidjava.showLogs)", + "description": "The **LiquidJava** output channel records extension and verifier activity. Use it to investigate startup or verification problems.\nEach entry includes a timestamp, **CLIENT** or **SERVER** source, and **INFO** or **ERROR** level.\n[Show LiquidJava logs](command:liquidjava.showLogs)", "media": { "markdown": "media/walkthrough/logs.md" }, "completionEvents": [ "onStepSelected" ] - }, - { - "id": "tutorial", - "title": "Learn LiquidJava interactively", - "description": "Try refinements, method contracts, object states, and ghosts in the browser. No local setup needed.\n[Open interactive tutorial](https://liquid-java.github.io/liquidjava-interactive-tutorial/)", - "media": { - "markdown": "media/walkthrough/tutorial.md" - }, - "completionEvents": [ - "onStepSelected" - ] } ] } diff --git a/client/src/services/commands.ts b/client/src/services/commands.ts index b4a08739..61fd0268 100644 --- a/client/src/services/commands.ts +++ b/client/src/services/commands.ts @@ -1,12 +1,14 @@ import * as vscode from "vscode"; import { startExtension, stopExtension, restartExtension } from "../extension"; import { verify } from "./diagnostics"; -import { showWalkthrough } from './walkthrough'; +import { copyWalkthroughDependency, showWalkthrough } from './walkthrough'; const commandIcons: Record = { "liquidjava.showLogs": "$(output)", "liquidjava.showView": "$(window)", "liquidjava.showWalkthrough": "$(book)", + "liquidjava.copyMavenDependency": "$(copy)", + "liquidjava.copyGradleDependency": "$(copy)", "liquidjava.start": "$(play)", "liquidjava.stop": "$(debug-stop)", "liquidjava.restart": "$(debug-restart)", @@ -15,6 +17,8 @@ const commandIcons: Record = { const commandHandlers: Record Promise> = { "liquidjava.showWalkthrough": showWalkthrough, + "liquidjava.copyMavenDependency": async context => await copyWalkthroughDependency(context, 'xml'), + "liquidjava.copyGradleDependency": async context => await copyWalkthroughDependency(context, 'groovy'), "liquidjava.start": async (context) => await startExtension(context), "liquidjava.stop": async () => await stopExtension(), "liquidjava.restart": async (context) => await restartExtension(context), diff --git a/client/src/services/walkthrough.ts b/client/src/services/walkthrough.ts index 1e8255d7..e9661285 100644 --- a/client/src/services/walkthrough.ts +++ b/client/src/services/walkthrough.ts @@ -5,3 +5,12 @@ export const WALKTHROUGH_ID = 'AlcidesFonseca.liquid-java#getStarted'; export async function showWalkthrough(): Promise { await vscode.commands.executeCommand('workbench.action.openWalkthrough', WALKTHROUGH_ID); } + +export async function copyWalkthroughDependency(context: Pick, language: 'xml' | 'groovy'): Promise { + const uri = vscode.Uri.joinPath(context.extensionUri, 'media/walkthrough/annotations.md'); + const contents = Buffer.from(await vscode.workspace.fs.readFile(uri)).toString('utf8').replace(/\r\n/g, '\n'); + const snippet = contents.match(new RegExp('```' + language + '\\n([\\s\\S]*?)\\n```'))?.[1]; + if (!snippet) throw new Error('LiquidJava walkthrough dependency snippet is missing'); + await vscode.env.clipboard.writeText(snippet); + vscode.window.setStatusBarMessage('LiquidJava: ' + (language === 'xml' ? 'Maven' : 'Gradle') + ' dependency copied', 3000); +} diff --git a/client/src/test/walkthrough.test.ts b/client/src/test/walkthrough.test.ts index d98ca668..9270f5b0 100644 --- a/client/src/test/walkthrough.test.ts +++ b/client/src/test/walkthrough.test.ts @@ -1,7 +1,9 @@ import * as assert from 'node:assert/strict'; -import { readFile } from 'node:fs/promises'; +import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; import * as vscode from 'vscode'; -import { WALKTHROUGH_ID } from '../services/walkthrough'; +import { copyWalkthroughDependency, WALKTHROUGH_ID } from '../services/walkthrough'; import type { LiquidJavaTestApi } from '../types/test-api'; suite('LiquidJava walkthrough', () => { @@ -23,7 +25,8 @@ suite('LiquidJava walkthrough', () => { assert.ok(walkthrough); assert.equal(walkthrough.title, 'Get Started with LiquidJava'); assert.deepEqual(walkthrough.steps.map((step: { id: string }) => step.id), - ['setup', 'annotations', 'verification', 'commands', 'diagnostics', 'context', 'stateMachine', 'logs', 'tutorial']); + ['tutorial', 'annotations', 'verification', 'commands', 'diagnostics', 'context', 'stateMachine', 'logs']); + assert.equal(walkthrough.steps[0].title, 'Learn LiquidJava'); const commands = await vscode.commands.getCommands(true); assert.ok(commands.includes('liquidjava.showWalkthrough')); for (const step of walkthrough.steps) { @@ -35,22 +38,70 @@ suite('LiquidJava walkthrough', () => { 'walkthrough actions must work without an active Java editor'); } const description = (id: string) => walkthrough.steps.find((step: { id: string }) => step.id === id).description; - assert.ok(description('commands').includes('(command:workbench.action.showCommands)')); + assert.ok(description('commands').includes('(command:liquidjava.showCommands)')); assert.ok(description('logs').includes('(command:liquidjava.showLogs)')); assert.ok(description('tutorial').includes('(https://liquid-java.github.io/liquidjava-interactive-tutorial/)')); }); test('uses the README dependency snippets for Maven and Gradle setup', async () => { - const readme = await readFile(vscode.Uri.joinPath(installed.extensionUri, 'README.md').fsPath, 'utf8'); - const setup = await readFile(vscode.Uri.joinPath(installed.extensionUri, 'media/walkthrough/annotations.md').fsPath, 'utf8'); + const readme = (await readFile(vscode.Uri.joinPath(installed.extensionUri, 'README.md').fsPath, 'utf8')).replace(/\r\n/g, '\n'); + const setup = (await readFile(vscode.Uri.joinPath(installed.extensionUri, 'media/walkthrough/annotations.md').fsPath, 'utf8')).replace(/\r\n/g, '\n'); + const description = installed.packageJSON.contributes.walkthroughs[0].steps.find( + (step: { id: string }) => step.id === 'annotations').description as string; for (const language of ['xml', 'groovy']) { const snippet = readme.match(new RegExp('```' + language + '\\n[\\s\\S]*?```'))?.[0]; assert.ok(snippet, `README must include the ${language} dependency snippet`); assert.ok(setup.includes(snippet), `walkthrough ${language} dependency must match the README`); + for (const line of snippet.split('\n').slice(1, -1).filter(line => line.trim())) { + assert.ok(description.includes('``' + line + '``'), + 'dependency code must also appear in the step when VS Code hides its media pane'); + } } assert.ok(setup.includes('liquidjava.specification.Refinement')); }); + test('copies complete Maven and Gradle snippets from the walkthrough', async () => { + const originalClipboard = await vscode.env.clipboard.readText(); + try { + const readme = (await readFile(vscode.Uri.joinPath(installed.extensionUri, 'README.md').fsPath, 'utf8')).replace(/\r\n/g, '\n'); + await vscode.commands.executeCommand('workbench.action.closeAllEditors'); + await vscode.commands.executeCommand('liquidjava.showWalkthrough'); + assert.equal(vscode.window.activeTextEditor, undefined); + for (const [language, command] of [ + ['xml', 'liquidjava.copyMavenDependency'], + ['groovy', 'liquidjava.copyGradleDependency'], + ]) { + const snippet = readme.match(new RegExp('```' + language + '\\n([\\s\\S]*?)\\n```'))?.[1]; + assert.ok(snippet); + await vscode.commands.executeCommand(command); + assert.equal(await vscode.env.clipboard.readText(), snippet, + 'copy must preserve the complete README snippet and its newlines'); + } + } finally { + await vscode.env.clipboard.writeText(originalClipboard); + } + }); + + test('copies dependency snippets from Windows line endings', async () => { + const originalClipboard = await vscode.env.clipboard.readText(); + const directory = await mkdtemp(join(tmpdir(), 'liquidjava-walkthrough-')); + try { + const source = (await readFile(vscode.Uri.joinPath(installed.extensionUri, 'media/walkthrough/annotations.md').fsPath, 'utf8')).replace(/\r\n/g, '\n'); + const mediaDirectory = join(directory, 'media', 'walkthrough'); + await mkdir(mediaDirectory, { recursive: true }); + await writeFile(join(mediaDirectory, 'annotations.md'), source.replace(/\n/g, '\r\n')); + for (const language of ['xml', 'groovy'] as const) { + await copyWalkthroughDependency({ extensionUri: vscode.Uri.file(directory) }, language); + const expected = source.match(new RegExp('```' + language + '\\n([\\s\\S]*?)\\n```'))?.[1]; + assert.ok(expected); + assert.equal(await vscode.env.clipboard.readText(), expected); + } + } finally { + await vscode.env.clipboard.writeText(originalClipboard); + await rm(directory, { recursive: true, force: true }); + } + }); + test('reopens the native walkthrough repeatedly after dismissal', async () => { await vscode.commands.executeCommand('workbench.action.closeAllEditors'); await vscode.commands.executeCommand('liquidjava.showWalkthrough');