Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .oxfmtrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@
},

"ignorePatterns": [
"/packages/app-web-docs/src/docs/contributing/private-scopes/*.mdx",
"/packages/app-web-docs/src/docs/user/actions/*.mdx",
"/packages/app-web-docs/src/docs/user/languages/*.mdx",
"/packages/app-web-docs/src/docs/user/modifiers/*.mdx",
"/packages/app-web-docs/src/docs/user/scopes/*.mdx",
"/packages/app-web-docs/src/docs/user/languages/*.mdx",
"/packages/app-web-docs/src/docs/user/tutorial/*.mdx",
"/packages/app-web-docs/src/docs/contributing/private-scopes/*.mdx",
"/packages/app-vscode/src/keyboard/grammar/generated/",
"/packages/lib-engine/src/customCommandGrammar/generated/",
"/packages/lib-engine/src/snippets/vendor/",
Expand Down
19 changes: 3 additions & 16 deletions packages/app-web-docs/src/docs/components/Code.css
Original file line number Diff line number Diff line change
Expand Up @@ -161,21 +161,8 @@
background-color: #e5c02c;
}

/* Code hat referenced */
/* Code mark referenced */

.code-hat-referenced::before {
animation: code-hat-referenced-pulse 2s ease-in-out infinite;
}

@keyframes code-hat-referenced-pulse {
50% {
background-color: var(--code-hat-referenced-color);
}
}

@media (prefers-reduced-motion: reduce) {
.code-hat-referenced::before {
background-color: var(--code-hat-referenced-color);
animation: none;
}
.code-mark {
background-color: var(--code-mark-color);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { createContext, useContext, useMemo, useState } from "react";
import type { DecorationItem } from "shiki";
import type {
Position,
Range,
Selection,
TestCaseSnapshot,
} from "@cursorless/lib-common";
Expand Down Expand Up @@ -99,7 +100,7 @@ export function RecordedTestVisualizer({
// oxlint-disable-next-line react_perf/jsx-no-new-object-as-prop
const link = {
name: "GitHub",
url: `https://github.com/cursorless-dev/cursorless/blob/main/resources/fixtures/recorded/docs/${path}`,
url: `https://github.com/cursorless-dev/cursorless/blob/main/resources/fixtures/recorded/${path}`,
};

const renderHats =
Expand All @@ -109,7 +110,7 @@ export function RecordedTestVisualizer({
return (
<div className="row">
<div className="col">
Input
Before
<CodeState
renderWhitespace={renderWhitespace}
renderHats={renderHats}
Expand All @@ -119,7 +120,7 @@ export function RecordedTestVisualizer({
/>
</div>
<div className="col">
Output
After
<CodeState
renderWhitespace={renderWhitespace}
renderHats={renderHats}
Expand Down Expand Up @@ -204,16 +205,21 @@ function toDecorations(
plainObjectToRange(hatRange),
)
: [];
const markRanges = renderHats
? Object.values(state.marks ?? {}).map(plainObjectToRange)
: [];

// Shiki rejects intersecting decorations. A zero-width cursor at the end of
// a hat range intersects the hat decoration, so render both on one wrapper.
// We only merge at the end because code-cursor-after recreates that exact
// boundary without competing with the hat's ::before pseudo-element.
// a hat or mark range intersects that decoration, so render both on one
// wrapper. We only merge at the end because code-cursor-after recreates that
// exact boundary without competing with the hat's ::before pseudo-element.
const mergedCursorPositions = selections
.filter(
(selection) =>
selection.isEmpty &&
hatRanges.some(({ end }) => end.isEqual(selection.active)),
[...hatRanges, ...markRanges].some(({ end }) =>
end.isEqual(selection.active),
),
)
.map(({ active }) => active);

Expand All @@ -228,10 +234,46 @@ function toDecorations(
),
)
.map(toDecoration),
...toMarkDecorations(markRanges, hatRanges, mergedCursorPositions),
...toHatDecorations(state, renderHats, mergedCursorPositions),
];
}

function toMarkDecorations(
markRanges: readonly Range[],
hatRanges: readonly Range[],
mergedCursorPositions: readonly Position[],
): DecorationItem[] {
return markRanges
.filter(
(range, index) =>
markRanges.findIndex((otherRange) => otherRange.isRangeEqual(range)) ===
index,
)
.map((range) => {
const className = ["code-mark"];

// Prefer placing the cursor on the smaller hat wrapper when both the hat
// and its containing mark end at the cursor position.
if (
mergedCursorPositions.some((position) => position.isEqual(range.end)) &&
!hatRanges.some(({ end }) => end.isEqual(range.end))
) {
className.push("code-cursor-after");
}

return {
start: range.start,
end: range.end,
alwaysWrap: true,
properties: {
className,
style: `--code-mark-color: ${highlightColors.content.background};`,
},
};
});
}

function toHatDecorations(
state: TestCaseSnapshot,
renderHats: boolean,
Expand All @@ -241,23 +283,12 @@ function toHatDecorations(
return [];
}

const markRanges = Object.values(state.marks ?? {}).map(plainObjectToRange);

return state.hatTokenMap.map(({ hatStyle, hatRange }) => {
const range = plainObjectToRange(hatRange);
const properties: DecorationItem["properties"] = {
className: ["code-hat", `code-hat-${hatStyle}`],
};

const isReferenced = markRanges.some((markRange) =>
markRange.contains(range),
);

if (isReferenced) {
properties.className?.push("code-hat-referenced");
properties.style = `--code-hat-referenced-color: ${highlightColors.content.background};`;
}

if (mergedCursorPositions.some((position) => position.isEqual(range.end))) {
// The hat uses ::before and the cursor uses ::after, allowing both
// visuals to share this wrapper without overlapping Shiki decorations.
Expand Down
2 changes: 1 addition & 1 deletion packages/app-web-docs/src/docs/user/how-to.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
sidebar_group: Getting started
sidebar_position: 4
sidebar_position: 3
---

# How-to guides
Expand Down
4 changes: 2 additions & 2 deletions packages/app-web-docs/src/docs/user/learning-resources.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
sidebar_group: Getting started
sidebar_position: 3
sidebar_position: 4
---

# Learning resources
Expand All @@ -10,7 +10,7 @@ These resources can help you learn Cursorless, from your first commands through
## Start here

- [Cursorless tutorial videos](https://www.youtube.com/watch?v=5mAzHGM2M0k&list=PLXv2sppxeoQZz49evjy4T0QJRIgc_JPqs) - Learn the basic concepts and commands.
- [Interactive tutorial](./sidebar.md#tutorial) - Learn Cursorless inside your editor using the Cursorless sidebar.
- [Cursorless tutorial](./tutorial/README.mdx) - Learn in your browser or follow the same lessons interactively inside VS Code.

## Learn from examples

Expand Down
26 changes: 1 addition & 25 deletions packages/app-web-docs/src/docs/user/sidebar.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,28 +28,4 @@ To identify the scope for a piece of code:

## Tutorial

The sidebar includes interactive tutorials that teach Cursorless using guided exercises in a practice document:

- **Introduction** covers selecting tokens and ranges, using multiple targets, working with lines, deleting text, and positioning the cursor.
- **Basic coding** covers structural scopes and actions such as clone, swap, pour, bring, and dedent.

Each step shows a command to say and usually advances automatically when you complete it. The tutorial uses your custom spoken forms and saves your progress so that you can continue where you left off.

### Start a tutorial

With VS Code focused, say `"cursorless tutorial"` to start or continue the Introduction tutorial.

To choose a tutorial, say `"tutorial list"`, then click its name or say `"tutorial <number>"`. For example, say `"tutorial two"` to start or continue Basic coding.

You can also say `"bar cursorless"` and choose a tutorial from the Tutorial section of the sidebar.

### Navigate the tutorial

You can use the arrow buttons in the sidebar or these commands:

- `"tutorial next"` - Move to the next step.
- `"tutorial previous"` or `"tutorial last"` - Move to the previous step.
- `"tutorial restart"` - Return to the first step of the current tutorial.
- `"tutorial list"` or `"tutorial close"` - Return to the tutorial list.

The tutorial opens a practice document and expects it to match the current exercise. If you edit the document or move away from it, say `"tutorial resume"` to restore the current step and continue.
The sidebar includes interactive tutorials with guided exercises in a practice document. See the [tutorial](./tutorial/README.mdx#use-the-interactive-tutorial) for the available lessons and instructions for using them interactively.
83 changes: 83 additions & 0 deletions packages/app-web-docs/src/docs/user/tutorial/1-basics.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { RecordedTestVisualizer, RecordedTestVisualizerOptions, RecordedTestVisualizerProvider } from "@site/src/docs/components/RecordedTestVisualizer";

# 1. Introduction

<RecordedTestVisualizerProvider>

<RecordedTestVisualizerOptions />

## Step 1

Say `"take cap"`
Comment thread
AndreasArvidsson marked this conversation as resolved.

<RecordedTestVisualizer fixtureName="tutorial/tutorial-1-basics/takeCap" />

## Step 2

Well done! 🙌 You just used the code word for 'c', "cap", to refer to the word with a gray hat over the 'c'.

When a hat is not gray, we say its color: say `"take blue sun"`

<RecordedTestVisualizer fixtureName="tutorial/tutorial-1-basics/takeBlueSun" />

## Step 3

Selecting a single token is great, but often we need something bigger.

Say `"take harp past drum"` to select a range.

<RecordedTestVisualizer fixtureName="tutorial/tutorial-1-basics/takeHarpPastDrum" />

## Step 4

Despite its name, one of the most powerful aspects of Cursorless is the ability to use more than one cursor.

Let's try that: `"take near and sun"`

<RecordedTestVisualizer fixtureName="tutorial/tutorial-1-basics/takeNearAndSun" />

## Step 5

But let's show that Cursorless can live up to its name: we can say `"chuck trap"` to delete a word without ever moving our cursor.

<RecordedTestVisualizer fixtureName="tutorial/tutorial-1-basics/chuckTrap" />

## Step 6

Tokens are great, but they're just one way to think of a document.

Let's try working with lines: `"chuck line odd"`

<RecordedTestVisualizer fixtureName="tutorial/tutorial-1-basics/chuckLineOdd" />

## Step 7

We can also use "line" to refer to the line containing our cursor: `"take line"`

<RecordedTestVisualizer fixtureName="tutorial/tutorial-1-basics/takeLine" />

## Step 8

You now know how to select and delete; let's give you a couple more actions to play with: say "pre" to place the cursor before a target, as in `"pre urge"`

<RecordedTestVisualizer fixtureName="tutorial/tutorial-1-basics/preUrge" />

## Step 9

Say "post" to place the cursor after a target: `"post air"`

<RecordedTestVisualizer fixtureName="tutorial/tutorial-1-basics/postAir" />

## Step 10

Say "change" to delete a word and move your cursor to where it used to be: `"change sit"`

<RecordedTestVisualizer fixtureName="tutorial/tutorial-1-basics/changeSit" />

## Step 11

And that wraps up unit 1 of the Cursorless tutorial! Next time, we'll write some code 🙌.

Feel free to keep playing with this document, then say `"tutorial next"` to continue.

</RecordedTestVisualizerProvider>
73 changes: 73 additions & 0 deletions packages/app-web-docs/src/docs/user/tutorial/2-basic-coding.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { RecordedTestVisualizer, RecordedTestVisualizerOptions, RecordedTestVisualizerProvider } from "@site/src/docs/components/RecordedTestVisualizer";

# 2. Basic coding

<RecordedTestVisualizerProvider>

<RecordedTestVisualizerOptions />

## Step 1

When editing code, we often think in terms of statements, functions, etc. Let's clone a statement: `"clone state sit"`
Comment thread
AndreasArvidsson marked this conversation as resolved.

<RecordedTestVisualizer fixtureName="tutorial/tutorial-2-basic-coding/cloneStateInk" />

## Step 2

"state" is one of many scopes supported by Cursorless. To see all available scopes, have a look at the Scopes section below, and use the "visualize" command to see them live: `"visualize funk"`
Comment thread
AndreasArvidsson marked this conversation as resolved.

## Step 3

Say `"visualize nothing"` to hide the visualization.

## Step 4

Cursorless tries its best to keep your commands short.

In the following command, we just say "string" once, but Cursorless infers that both targets are strings: `"swap string air with whale"`
Comment thread
AndreasArvidsson marked this conversation as resolved.

<RecordedTestVisualizer fixtureName="tutorial/tutorial-2-basic-coding/swapStringAirWithWhale" />

## Step 5

Great. Let's learn a new action. The "pour" action lets you start editing a new line below any line on your screen: `"pour urge"`
Comment thread
AndreasArvidsson marked this conversation as resolved.

<RecordedTestVisualizer fixtureName="tutorial/tutorial-2-basic-coding/pourUrge" />

## Step 6

Now let's try applying a Cursorless action to the current line: `"dedent this"`
Comment thread
AndreasArvidsson marked this conversation as resolved.

<RecordedTestVisualizer fixtureName="tutorial/tutorial-2-basic-coding/dedentThis" />

## Step 7

Code reuse is a fact of life as a programmer. Cursorless makes this easy with the "bring" command: `"bring state urge"`
Comment thread
AndreasArvidsson marked this conversation as resolved.

<RecordedTestVisualizer fixtureName="tutorial/tutorial-2-basic-coding/bringStateUrge" />

## Step 8

"bring" also works with two targets just like "swap": `"bring blue cap to value red"`

<RecordedTestVisualizer fixtureName="tutorial/tutorial-2-basic-coding/bringBlueCapToValueRisk" />

## Step 9

Cursorless tries its best to use its knowledge of programming languages to leave you with syntactically valid code.

Note how it cleans up the comma here: `"chuck arg blue vest"`

<RecordedTestVisualizer fixtureName="tutorial/tutorial-2-basic-coding/chuckArgueBlueVest" />

## Step 10

We introduced a lot of different scopes today. If you're anything like us, you've already forgotten them all.

The important thing to remember is that you can always say `"cursorless help"` to see a list.

## Step 11

As always, feel free to stick around and play with this file to practice what you've just learned. Happy coding 😊. Say `"tutorial next"` to get back home.

</RecordedTestVisualizerProvider>
Loading
Loading