Skip to content

Commit 4688ad8

Browse files
Fix for markdown cells (#2739)
The latest update for the notebook cell handler broke cells in markdown. Also implements notebook scope handler with a one of scope handler the same way we do for collection item. We can have both language specific implementations as well as the ide notebook api so this is a more proper implementation. ## Checklist - [x] I have added [tests](https://www.cursorless.org/docs/contributing/test-case-recorder/) - [/] I have updated the [docs](https://github.com/cursorless-dev/cursorless/tree/main/docs) and [cheatsheet](https://github.com/cursorless-dev/cursorless/tree/main/cursorless-talon/src/cheatsheet) - [/] I have not broken the cheatsheet --------- Co-authored-by: Phil Cohen <phillip@phillip.io>
1 parent 876694f commit 4688ad8

File tree

5 files changed

+189
-107
lines changed

5 files changed

+189
-107
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
languageId: markdown
2+
command:
3+
version: 7
4+
spokenForm: change cell
5+
action:
6+
name: clearAndSetSelection
7+
target:
8+
type: primitive
9+
modifiers:
10+
- type: containingScope
11+
scopeType: {type: notebookCell}
12+
usePrePhraseSnapshot: false
13+
initialState:
14+
documentContents: |
15+
```
16+
code
17+
```
18+
selections:
19+
- anchor: {line: 1, character: 0}
20+
active: {line: 1, character: 0}
21+
marks: {}
22+
finalState:
23+
documentContents: |+
24+
25+
selections:
26+
- anchor: {line: 0, character: 0}
27+
active: {line: 0, character: 0}

packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/LineScopeHandler.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export class LineScopeHandler extends BaseScopeHandler {
1515
type: "paragraph",
1616
} as const;
1717
protected readonly isHierarchical = false;
18-
public readonly includeAdjacentInEvery: boolean = true;
18+
public readonly includeAdjacentInEvery = true;
1919

2020
constructor(_scopeType: ScopeType, _languageId: string) {
2121
super();
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import {
2+
Range,
3+
type Direction,
4+
type NotebookCell,
5+
type Position,
6+
type TextEditor,
7+
} from "@cursorless/common";
8+
import { ide } from "../../../singletons/ide.singleton";
9+
import { NotebookCellTarget } from "../../targets";
10+
import { BaseScopeHandler } from "./BaseScopeHandler";
11+
import type { TargetScope } from "./scope.types";
12+
import type { ScopeIteratorRequirements } from "./scopeHandler.types";
13+
14+
/**
15+
* This is the scope handler for the actual notebook API in the IDE.
16+
*/
17+
export class NotebookCellApiScopeHandler extends BaseScopeHandler {
18+
public readonly scopeType = { type: "notebookCell" } as const;
19+
public readonly iterationScopeType = { type: "document" } as const;
20+
protected isHierarchical = false;
21+
22+
constructor() {
23+
super();
24+
}
25+
26+
*generateScopeCandidates(
27+
editor: TextEditor,
28+
position: Position,
29+
direction: Direction,
30+
hints: ScopeIteratorRequirements,
31+
): Iterable<TargetScope> {
32+
const cells = getNotebookCells(editor, position, direction, hints);
33+
34+
for (const cell of cells) {
35+
yield createTargetScope(cell);
36+
}
37+
}
38+
}
39+
40+
function getNotebookCells(
41+
editor: TextEditor,
42+
position: Position,
43+
direction: Direction,
44+
hints: ScopeIteratorRequirements,
45+
) {
46+
const nb = getNotebook(editor);
47+
48+
if (nb == null) {
49+
return [];
50+
}
51+
52+
const { notebook, cell } = nb;
53+
54+
if (hints.containment === "required") {
55+
return [cell];
56+
}
57+
58+
if (
59+
hints.containment === "disallowed" ||
60+
hints.containment === "disallowedIfStrict"
61+
) {
62+
return direction === "forward"
63+
? notebook.cells.slice(cell.index + 1)
64+
: notebook.cells.slice(0, cell.index).reverse();
65+
}
66+
67+
// Every scope
68+
if (hints.distalPosition != null) {
69+
const searchRange = new Range(position, hints.distalPosition);
70+
if (searchRange.isRangeEqual(editor.document.range)) {
71+
return notebook.cells;
72+
}
73+
}
74+
75+
return direction === "forward"
76+
? notebook.cells.slice(cell.index)
77+
: notebook.cells.slice(0, cell.index + 1).reverse();
78+
}
79+
80+
function getNotebook(editor: TextEditor) {
81+
const uri = editor.document.uri.toString();
82+
for (const notebook of ide().visibleNotebookEditors) {
83+
for (const cell of notebook.cells) {
84+
if (cell.document.uri.toString() === uri) {
85+
return { notebook, cell };
86+
}
87+
}
88+
}
89+
return undefined;
90+
}
91+
92+
function createTargetScope(cell: NotebookCell): TargetScope {
93+
const editor = getEditor(cell);
94+
const contentRange = editor.document.range;
95+
return {
96+
editor,
97+
domain: contentRange,
98+
getTargets: (isReversed: boolean) => [
99+
new NotebookCellTarget({
100+
editor,
101+
isReversed,
102+
contentRange,
103+
}),
104+
],
105+
};
106+
}
107+
108+
function getEditor(cell: NotebookCell) {
109+
const uri = cell.document.uri.toString();
110+
for (const editor of ide().visibleTextEditors) {
111+
if (editor.document.uri.toString() === uri) {
112+
return editor;
113+
}
114+
}
115+
throw new Error("Editor not found notebook cell");
116+
}
Lines changed: 44 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -1,132 +1,70 @@
11
import {
2-
Range,
32
type Direction,
4-
type NotebookCell,
53
type Position,
64
type ScopeType,
75
type TextEditor,
86
} from "@cursorless/common";
97
import type { LanguageDefinitions } from "../../../languages/LanguageDefinitions";
10-
import { ide } from "../../../singletons/ide.singleton";
11-
import { NotebookCellTarget } from "../../targets";
8+
import { BaseScopeHandler } from "./BaseScopeHandler";
9+
import { NotebookCellApiScopeHandler } from "./NotebookCellApiScopeHandler";
10+
import { OneOfScopeHandler } from "./OneOfScopeHandler";
1211
import type { TargetScope } from "./scope.types";
1312
import type {
13+
ComplexScopeType,
1414
ScopeHandler,
1515
ScopeIteratorRequirements,
1616
} from "./scopeHandler.types";
17+
import type { ScopeHandlerFactory } from "./ScopeHandlerFactory";
1718

18-
export class NotebookCellScopeHandler implements ScopeHandler {
19+
export class NotebookCellScopeHandler extends BaseScopeHandler {
1920
public readonly scopeType = { type: "notebookCell" } as const;
20-
public readonly iterationScopeType = { type: "document" } as const;
21-
public readonly includeAdjacentInEvery = false;
21+
protected isHierarchical = false;
22+
private readonly scopeHandler: ScopeHandler;
2223

23-
constructor(
24-
private languageDefinitions: LanguageDefinitions,
25-
_scopeType: ScopeType,
26-
private languageId: string,
27-
) {}
28-
29-
*generateScopes(
30-
editor: TextEditor,
31-
position: Position,
32-
direction: Direction,
33-
hints: ScopeIteratorRequirements,
34-
): Iterable<TargetScope> {
35-
const scopeHandler = this.languageDefinitions
36-
.get(this.languageId)
37-
?.getScopeHandler(this.scopeType);
38-
39-
if (scopeHandler != null) {
40-
yield* scopeHandler.generateScopeCandidates(
41-
editor,
42-
position,
43-
direction,
44-
hints,
45-
);
46-
}
47-
48-
const cells = getNotebookCells(editor, position, direction, hints);
49-
50-
for (const cell of cells) {
51-
yield createTargetScope(cell);
52-
}
53-
}
54-
}
55-
56-
function getNotebookCells(
57-
editor: TextEditor,
58-
position: Position,
59-
direction: Direction,
60-
hints: ScopeIteratorRequirements,
61-
) {
62-
const nb = getNotebook(editor);
63-
64-
if (nb == null) {
65-
return [];
66-
}
67-
68-
const { notebook, cell } = nb;
69-
70-
if (hints.containment === "required") {
71-
return [cell];
24+
get iterationScopeType(): ScopeType | ComplexScopeType {
25+
return this.scopeHandler.iterationScopeType;
7226
}
7327

74-
if (
75-
hints.containment === "disallowed" ||
76-
hints.containment === "disallowedIfStrict"
28+
constructor(
29+
scopeHandlerFactory: ScopeHandlerFactory,
30+
languageDefinitions: LanguageDefinitions,
31+
_scopeType: ScopeType,
32+
languageId: string,
7733
) {
78-
return direction === "forward"
79-
? notebook.cells.slice(cell.index + 1)
80-
: notebook.cells.slice(0, cell.index).reverse();
81-
}
34+
super();
8235

83-
// Every scope
84-
if (hints.distalPosition != null) {
85-
const searchRange = new Range(position, hints.distalPosition);
86-
if (searchRange.isRangeEqual(editor.document.range)) {
87-
return notebook.cells;
88-
}
89-
}
36+
this.scopeHandler = (() => {
37+
const apiScopeHandler = new NotebookCellApiScopeHandler();
9038

91-
return direction === "forward"
92-
? notebook.cells.slice(cell.index)
93-
: notebook.cells.slice(0, cell.index + 1).reverse();
94-
}
39+
const languageScopeHandler = languageDefinitions
40+
.get(languageId)
41+
?.getScopeHandler(this.scopeType);
9542

96-
function getNotebook(editor: TextEditor) {
97-
const uri = editor.document.uri.toString();
98-
for (const notebook of ide().visibleNotebookEditors) {
99-
for (const cell of notebook.cells) {
100-
if (cell.document.uri.toString() === uri) {
101-
return { notebook, cell };
43+
if (languageScopeHandler == null) {
44+
return apiScopeHandler;
10245
}
103-
}
104-
}
105-
return undefined;
106-
}
10746

108-
function createTargetScope(cell: NotebookCell): TargetScope {
109-
const editor = getEditor(cell);
110-
const contentRange = editor.document.range;
111-
return {
112-
editor,
113-
domain: contentRange,
114-
getTargets: (isReversed: boolean) => [
115-
new NotebookCellTarget({
116-
editor,
117-
isReversed,
118-
contentRange,
119-
}),
120-
],
121-
};
122-
}
47+
return OneOfScopeHandler.createFromScopeHandlers(
48+
scopeHandlerFactory,
49+
{
50+
type: "oneOf",
51+
scopeTypes: [
52+
languageScopeHandler.scopeType,
53+
apiScopeHandler.scopeType,
54+
],
55+
},
56+
[languageScopeHandler, apiScopeHandler],
57+
languageId,
58+
);
59+
})();
60+
}
12361

124-
function getEditor(cell: NotebookCell) {
125-
const uri = cell.document.uri.toString();
126-
for (const editor of ide().visibleTextEditors) {
127-
if (editor.document.uri.toString() === uri) {
128-
return editor;
129-
}
62+
generateScopeCandidates(
63+
editor: TextEditor,
64+
position: Position,
65+
direction: Direction,
66+
hints: ScopeIteratorRequirements,
67+
): Iterable<TargetScope> {
68+
return this.scopeHandler.generateScopes(editor, position, direction, hints);
13069
}
131-
throw new Error("Editor not found notebook cell");
13270
}

packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/ScopeHandlerFactoryImpl.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ export class ScopeHandlerFactoryImpl implements ScopeHandlerFactory {
114114
);
115115
case "notebookCell":
116116
return new NotebookCellScopeHandler(
117+
this,
117118
this.languageDefinitions,
118119
scopeType,
119120
languageId,

0 commit comments

Comments
 (0)