From c59b75ffe256785fe2f14948a1c0db9aad6d4f9f Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Sat, 23 Nov 2024 11:38:52 +0100 Subject: [PATCH 1/5] Implemented --- packages/common/src/ide/PassthroughIDEBase.ts | 7 +- packages/common/src/ide/fake/FakeIDE.ts | 6 +- packages/common/src/ide/types/ide.types.ts | 10 +- packages/common/src/index.ts | 6 +- packages/common/src/types/NotebookCell.ts | 32 +++++ packages/common/src/types/NotebookEditor.ts | 22 ++++ .../ModifierStageFactoryImpl.ts | 3 - .../createContinuousRangeTarget.ts | 4 + .../scopeHandlers/NotebookCellScopeHandler.ts | 123 ++++++++++++++++++ .../scopeHandlers/ScopeHandlerFactoryImpl.ts | 7 + .../scopeTypeStages/NotebookCellStage.ts | 25 ---- .../src/ide/TalonJsIDE.ts | 5 + .../src/ide/vscode/VscodeIDE.ts | 12 +- .../src/ide/vscode/VscodeIdeNotebook.ts | 51 ++++++++ .../ide/vscode/notebook/notebookCurrent.ts | 25 ---- .../neovim-common/src/ide/neovim/NeovimIDE.ts | 5 + 16 files changed, 282 insertions(+), 61 deletions(-) create mode 100644 packages/common/src/types/NotebookCell.ts create mode 100644 packages/common/src/types/NotebookEditor.ts create mode 100644 packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/NotebookCellScopeHandler.ts delete mode 100644 packages/cursorless-engine/src/processTargets/modifiers/scopeTypeStages/NotebookCellStage.ts create mode 100644 packages/cursorless-vscode/src/ide/vscode/VscodeIdeNotebook.ts delete mode 100644 packages/cursorless-vscode/src/ide/vscode/notebook/notebookCurrent.ts diff --git a/packages/common/src/ide/PassthroughIDEBase.ts b/packages/common/src/ide/PassthroughIDEBase.ts index dee928ed98..fc968e334e 100644 --- a/packages/common/src/ide/PassthroughIDEBase.ts +++ b/packages/common/src/ide/PassthroughIDEBase.ts @@ -1,4 +1,5 @@ import type { GeneralizedRange } from "../types/GeneralizedRange"; +import type { NotebookEditor } from "../types/NotebookEditor"; import type { TextDocument } from "../types/TextDocument"; import type { EditableTextEditor, TextEditor } from "../types/TextEditor"; import type { Capabilities } from "./types/Capabilities"; @@ -17,9 +18,9 @@ import type { RunMode, WorkspaceFolder, } from "./types/ide.types"; +import type { KeyValueStore } from "./types/KeyValueStore"; import type { Messages } from "./types/Messages"; import type { QuickPickOptions } from "./types/QuickPickOptions"; -import type { KeyValueStore } from "./types/KeyValueStore"; export default class PassthroughIDEBase implements IDE { configuration: Configuration; @@ -123,6 +124,10 @@ export default class PassthroughIDEBase implements IDE { return this.original.visibleTextEditors; } + public get visibleNotebookEditors(): NotebookEditor[] { + return this.original.visibleNotebookEditors; + } + public get cursorlessVersion(): string { return this.original.cursorlessVersion; } diff --git a/packages/common/src/ide/fake/FakeIDE.ts b/packages/common/src/ide/fake/FakeIDE.ts index aff28b88e7..9542d23f61 100644 --- a/packages/common/src/ide/fake/FakeIDE.ts +++ b/packages/common/src/ide/fake/FakeIDE.ts @@ -1,5 +1,5 @@ import { pull } from "lodash-es"; -import type { EditableTextEditor, TextEditor } from "../.."; +import type { EditableTextEditor, NotebookEditor, TextEditor } from "../.."; import type { GeneralizedRange } from "../../types/GeneralizedRange"; import type { TextDocument } from "../../types/TextDocument"; import type { TextDocumentChangeEvent } from "../types/Events"; @@ -82,6 +82,10 @@ export class FakeIDE implements IDE { throw Error("Not implemented"); } + get visibleNotebookEditors(): NotebookEditor[] { + throw Error("Not implemented"); + } + public getEditableTextEditor(_editor: TextEditor): EditableTextEditor { throw Error("Not implemented"); } diff --git a/packages/common/src/ide/types/ide.types.ts b/packages/common/src/ide/types/ide.types.ts index 44ad8562ac..354ebd6c14 100644 --- a/packages/common/src/ide/types/ide.types.ts +++ b/packages/common/src/ide/types/ide.types.ts @@ -1,10 +1,11 @@ +import type { URI } from "vscode-uri"; import type { EditableTextEditor, InputBoxOptions, + NotebookEditor, TextDocument, TextEditor, } from "../.."; -import type { URI } from "vscode-uri"; import type { GeneralizedRange } from "../../types/GeneralizedRange"; import type { Capabilities } from "./Capabilities"; import type { Clipboard } from "./Clipboard"; @@ -16,9 +17,9 @@ import type { TextEditorVisibleRangesChangeEvent, } from "./events.types"; import type { FlashDescriptor } from "./FlashDescriptor"; +import type { KeyValueStore } from "./KeyValueStore"; import type { Messages } from "./Messages"; import type { QuickPickOptions } from "./QuickPickOptions"; -import type { KeyValueStore } from "./KeyValueStore"; export type RunMode = "production" | "development" | "test"; export type HighlightId = string; @@ -79,6 +80,11 @@ export interface IDE { */ readonly visibleTextEditors: TextEditor[]; + /** + * The currently visible notebook editors or an empty array. + */ + readonly visibleNotebookEditors: NotebookEditor[]; + /** * The capabilities of the IDE */ diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index 3c54d20a40..bad0704156 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -14,19 +14,19 @@ export * from "./ide/types/Clipboard"; export * from "./ide/types/CommandHistoryStorage"; export * from "./ide/types/CommandId"; export * from "./ide/types/Configuration"; -export * from "./ide/types/events.types"; export * from "./ide/types/Events"; +export * from "./ide/types/events.types"; export * from "./ide/types/FileSystem.types"; export * from "./ide/types/FlashDescriptor"; export * from "./ide/types/Hats"; export * from "./ide/types/HatStability"; export * from "./ide/types/hatStyles.types"; export * from "./ide/types/ide.types"; +export * from "./ide/types/KeyValueStore"; export * from "./ide/types/Messages"; export * from "./ide/types/Paths"; export * from "./ide/types/QuickPickOptions"; export * from "./ide/types/RawTreeSitterQueryProvider"; -export * from "./ide/types/KeyValueStore"; export * from "./ide/types/TutorialContentProvider"; export * from "./ide/util/messages"; export * from "./scopeSupportFacets/languageScopeSupport"; @@ -66,6 +66,8 @@ export * from "./types/Edit"; export * from "./types/GeneralizedRange"; export * from "./types/HatTokenMap"; export * from "./types/InputBoxOptions"; +export * from "./types/NotebookCell"; +export * from "./types/NotebookEditor"; export * from "./types/Position"; export * from "./types/Range"; export * from "./types/RangeExpansionBehavior"; diff --git a/packages/common/src/types/NotebookCell.ts b/packages/common/src/types/NotebookCell.ts new file mode 100644 index 0000000000..1ad377b823 --- /dev/null +++ b/packages/common/src/types/NotebookCell.ts @@ -0,0 +1,32 @@ +import type { TextDocument } from "./TextDocument"; + +export interface NotebookCell { + /** + * The index of this cell in its containing notebook. The + * index is updated when a cell is moved within its notebook. The index is `-1` + * when the cell has been removed from its notebook. + */ + readonly index: number; + + /** + * The kind of this cell. + */ + readonly kind: NotebookCellKind; + + /** + * The {@link TextDocument} of this cell. + */ + readonly document: TextDocument; +} + +export enum NotebookCellKind { + /** + * A markup-cell is formatted source that is used for display. + */ + Markup = 1, + + /** + * A code-cell. + */ + Code = 2, +} diff --git a/packages/common/src/types/NotebookEditor.ts b/packages/common/src/types/NotebookEditor.ts new file mode 100644 index 0000000000..e08953b9c8 --- /dev/null +++ b/packages/common/src/types/NotebookEditor.ts @@ -0,0 +1,22 @@ +import type { URI } from "vscode-uri"; +import type { NotebookCell } from "./NotebookCell"; + +export interface NotebookEditor { + /** + * The associated uri for this document. + * + * *Note* that most documents use the `file`-scheme, which means they are files on disk. However, **not** all documents are + * saved on disk and therefore the `scheme` must be checked before trying to access the underlying file or siblings on disk. + */ + readonly uri: URI; + + /** + * The number of cells in the notebook. + */ + readonly cellCount: number; + + /** + * The cells of this notebook. + */ + readonly cells: NotebookCell[]; +} diff --git a/packages/cursorless-engine/src/processTargets/ModifierStageFactoryImpl.ts b/packages/cursorless-engine/src/processTargets/ModifierStageFactoryImpl.ts index 5f7b6ec009..65f63d3d04 100644 --- a/packages/cursorless-engine/src/processTargets/ModifierStageFactoryImpl.ts +++ b/packages/cursorless-engine/src/processTargets/ModifierStageFactoryImpl.ts @@ -36,7 +36,6 @@ import type { SimpleEveryScopeModifier, } from "./modifiers/scopeTypeStages/LegacyContainingSyntaxScopeStage"; import { LegacyContainingSyntaxScopeStage } from "./modifiers/scopeTypeStages/LegacyContainingSyntaxScopeStage"; -import { NotebookCellStage } from "./modifiers/scopeTypeStages/NotebookCellStage"; export class ModifierStageFactoryImpl implements ModifierStageFactory { constructor( @@ -129,8 +128,6 @@ export class ModifierStageFactoryImpl implements ModifierStageFactory { modifier: ContainingScopeModifier | EveryScopeModifier, ): ModifierStage { switch (modifier.scopeType.type) { - case "notebookCell": - return new NotebookCellStage(modifier); case "collectionItem": return new ItemStage(this.languageDefinitions, this, modifier); default: diff --git a/packages/cursorless-engine/src/processTargets/createContinuousRangeTarget.ts b/packages/cursorless-engine/src/processTargets/createContinuousRangeTarget.ts index 36b69c7dff..abb2daa7b9 100644 --- a/packages/cursorless-engine/src/processTargets/createContinuousRangeTarget.ts +++ b/packages/cursorless-engine/src/processTargets/createContinuousRangeTarget.ts @@ -33,6 +33,10 @@ export function createContinuousRangeTarget( includeStart: boolean, includeEnd: boolean, ): Target { + if (startTarget.editor !== endTarget.editor) { + throw Error("Continuous targets must be in the same editor"); + } + if (includeStart && includeEnd && isSameType(startTarget, endTarget)) { const richTarget = startTarget.maybeCreateRichRangeTarget( isReversed, diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/NotebookCellScopeHandler.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/NotebookCellScopeHandler.ts new file mode 100644 index 0000000000..cd3c2aec39 --- /dev/null +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/NotebookCellScopeHandler.ts @@ -0,0 +1,123 @@ +import { + Range, + type Direction, + type NotebookCell, + type Position, + type ScopeType, + type TextEditor, +} from "@cursorless/common"; +import type { LanguageDefinitions } from "../../../languages/LanguageDefinitions"; +import { ide } from "../../../singletons/ide.singleton"; +import { NotebookCellTarget } from "../../targets"; +import type { TargetScope } from "./scope.types"; +import type { + ScopeHandler, + ScopeIteratorRequirements, +} from "./scopeHandler.types"; + +export class NotebookCellScopeHandler implements ScopeHandler { + public readonly scopeType = { type: "notebookCell" } as const; + public readonly iterationScopeType = { type: "document" } as const; + public readonly includeAdjacentInEvery = false; + + constructor( + private languageDefinitions: LanguageDefinitions, + _scopeType: ScopeType, + private languageId: string, + ) {} + + *generateScopes( + editor: TextEditor, + position: Position, + direction: Direction, + hints: ScopeIteratorRequirements, + ): Iterable { + const scopeHandler = this.languageDefinitions + .get(this.languageId) + ?.getScopeHandler(this.scopeType); + + if (scopeHandler != null) { + yield* scopeHandler.generateScopeCandidates( + editor, + position, + direction, + hints, + ); + } + + const nb = getNotebook(editor); + + if (nb == null) { + return; + } + + const { notebook, cell } = nb; + + if (hints.containment === "required") { + yield createTargetScope(cell); + return; + } + + const cells = (() => { + if ( + hints.containment === "disallowed" || + hints.containment === "disallowedIfStrict" + ) { + return direction === "forward" + ? notebook.cells.slice(cell.index + 1) + : notebook.cells.slice(0, cell.index).reverse(); + } + // Every scope + if (hints.distalPosition != null) { + const searchRange = new Range(position, hints.distalPosition); + if (searchRange.isRangeEqual(editor.document.range)) { + return notebook.cells; + } + } + return direction === "forward" + ? notebook.cells.slice(cell.index) + : notebook.cells.slice(0, cell.index + 1).reverse(); + })(); + + for (const cell of cells) { + yield createTargetScope(cell); + } + } +} + +function createTargetScope(cell: NotebookCell): TargetScope { + const editor = getEditor(cell); + const contentRange = editor.document.range; + return { + editor, + domain: contentRange, + getTargets: (isReversed: boolean) => [ + new NotebookCellTarget({ + editor, + isReversed, + contentRange, + }), + ], + }; +} + +function getNotebook(editor: TextEditor) { + const uri = editor.document.uri.toString(); + for (const notebook of ide().visibleNotebookEditors) { + for (const cell of notebook.cells) { + if (cell.document.uri.toString() === uri) { + return { notebook, cell }; + } + } + } + return undefined; +} + +function getEditor(cell: NotebookCell) { + for (const editor of ide().visibleTextEditors) { + if (editor.document.uri.toString() === cell.document.uri.toString()) { + return editor; + } + } + throw new Error("Editor not found notebook cell"); +} diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/ScopeHandlerFactoryImpl.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/ScopeHandlerFactoryImpl.ts index 0d4b4056d7..af9064dd6e 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/ScopeHandlerFactoryImpl.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/ScopeHandlerFactoryImpl.ts @@ -8,6 +8,7 @@ import { CharacterScopeHandler } from "./CharacterScopeHandler"; import { DocumentScopeHandler } from "./DocumentScopeHandler"; import { IdentifierScopeHandler } from "./IdentifierScopeHandler"; import { LineScopeHandler } from "./LineScopeHandler"; +import { NotebookCellScopeHandler } from "./NotebookCellScopeHandler"; import { OneOfScopeHandler } from "./OneOfScopeHandler"; import { ParagraphScopeHandler } from "./ParagraphScopeHandler"; import { @@ -103,6 +104,12 @@ export class ScopeHandlerFactoryImpl implements ScopeHandlerFactory { scopeType, languageId, ); + case "notebookCell": + return new NotebookCellScopeHandler( + this.languageDefinitions, + scopeType, + languageId, + ); case "custom": return scopeType.scopeHandler; case "instance": diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeTypeStages/NotebookCellStage.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeTypeStages/NotebookCellStage.ts deleted file mode 100644 index 07d0966ba5..0000000000 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeTypeStages/NotebookCellStage.ts +++ /dev/null @@ -1,25 +0,0 @@ -import type { Target } from "../../../typings/target.types"; -import type { - ContainingScopeModifier, - EveryScopeModifier, -} from "@cursorless/common"; -import type { ModifierStage } from "../../PipelineStages.types"; -import { NotebookCellTarget } from "../../targets"; - -export class NotebookCellStage implements ModifierStage { - constructor(private modifier: ContainingScopeModifier | EveryScopeModifier) {} - - run(target: Target): NotebookCellTarget[] { - if (this.modifier.type === "everyScope") { - throw new Error(`Every ${this.modifier.type} not yet implemented`); - } - - return [ - new NotebookCellTarget({ - editor: target.editor, - isReversed: target.isReversed, - contentRange: target.contentRange, - }), - ]; - } -} diff --git a/packages/cursorless-everywhere-talon-core/src/ide/TalonJsIDE.ts b/packages/cursorless-everywhere-talon-core/src/ide/TalonJsIDE.ts index eac3f302c4..cb5dc345d1 100644 --- a/packages/cursorless-everywhere-talon-core/src/ide/TalonJsIDE.ts +++ b/packages/cursorless-everywhere-talon-core/src/ide/TalonJsIDE.ts @@ -10,6 +10,7 @@ import type { InputBoxOptions, Listener, Messages, + NotebookEditor, OpenUntitledTextDocumentOptions, QuickPickOptions, RunMode, @@ -80,6 +81,10 @@ export class TalonJsIDE implements IDE { return this.editors; } + get visibleNotebookEditors(): NotebookEditor[] { + return []; + } + getEditableTextEditor(editor: TextEditor): EditableTextEditor { if (editor instanceof TalonJsEditor) { return editor; diff --git a/packages/cursorless-vscode/src/ide/vscode/VscodeIDE.ts b/packages/cursorless-vscode/src/ide/vscode/VscodeIDE.ts index e288521ddd..65adc58d40 100644 --- a/packages/cursorless-vscode/src/ide/vscode/VscodeIDE.ts +++ b/packages/cursorless-vscode/src/ide/vscode/VscodeIDE.ts @@ -6,6 +6,7 @@ import type { HighlightId, IDE, InputBoxOptions, + NotebookEditor, OpenUntitledTextDocumentOptions, QuickPickOptions, RunMode, @@ -19,16 +20,17 @@ import { } from "@cursorless/vscode-common"; import { pull } from "lodash-es"; import { v4 as uuid } from "uuid"; -import * as vscode from "vscode"; import type { ExtensionContext, WorkspaceFolder } from "vscode"; +import * as vscode from "vscode"; import { window, workspace } from "vscode"; import { VscodeCapabilities } from "./VscodeCapabilities"; import VscodeClipboard from "./VscodeClipboard"; import VscodeConfiguration from "./VscodeConfiguration"; import { forwardEvent, vscodeOnDidChangeTextDocument } from "./VscodeEvents"; import VscodeFlashHandler from "./VscodeFlashHandler"; -import VscodeKeyValueStore from "./VscodeKeyValueStore"; import VscodeHighlights, { HighlightStyle } from "./VscodeHighlights"; +import { VscodeNotebookEditorImpl } from "./VscodeIdeNotebook"; +import VscodeKeyValueStore from "./VscodeKeyValueStore"; import VscodeMessages from "./VscodeMessages"; import { vscodeRunMode } from "./VscodeRunMode"; import { VscodeTextDocumentImpl } from "./VscodeTextDocumentImpl"; @@ -117,6 +119,12 @@ export class VscodeIDE implements IDE { return window.visibleTextEditors.map((e) => this.fromVscodeEditor(e)); } + get visibleNotebookEditors(): NotebookEditor[] { + return vscode.window.visibleNotebookEditors.map( + (editor) => new VscodeNotebookEditorImpl(editor), + ); + } + public getEditableTextEditor(editor: TextEditor): EditableTextEditor { return editor as EditableTextEditor; } diff --git a/packages/cursorless-vscode/src/ide/vscode/VscodeIdeNotebook.ts b/packages/cursorless-vscode/src/ide/vscode/VscodeIdeNotebook.ts new file mode 100644 index 0000000000..b1a11b1186 --- /dev/null +++ b/packages/cursorless-vscode/src/ide/vscode/VscodeIdeNotebook.ts @@ -0,0 +1,51 @@ +import type { + NotebookCell, + NotebookCellKind, + NotebookEditor, + TextDocument, +} from "@cursorless/common"; +import type * as vscode from "vscode"; +import type { URI } from "vscode-uri"; +import { VscodeTextDocumentImpl } from "./VscodeTextDocumentImpl"; + +export class VscodeNotebookEditorImpl implements NotebookEditor { + private notebook: vscode.NotebookDocument; + + constructor(editor: vscode.NotebookEditor) { + this.notebook = editor.notebook; + } + + get uri(): URI { + return this.notebook.uri; + } + + get cellCount(): number { + return this.notebook.cellCount; + } + + get cells(): NotebookCell[] { + return this.notebook + .getCells() + .map((cell) => new VscodeNotebookCellImpl(cell)); + } +} + +export class VscodeNotebookCellImpl implements NotebookCell { + private cell: vscode.NotebookCell; + + constructor(cell: vscode.NotebookCell) { + this.cell = cell; + } + + get index(): number { + return this.cell.index; + } + + get kind(): NotebookCellKind { + return this.cell.kind; + } + + get document(): TextDocument { + return new VscodeTextDocumentImpl(this.cell.document); + } +} diff --git a/packages/cursorless-vscode/src/ide/vscode/notebook/notebookCurrent.ts b/packages/cursorless-vscode/src/ide/vscode/notebook/notebookCurrent.ts deleted file mode 100644 index 7b17f50894..0000000000 --- a/packages/cursorless-vscode/src/ide/vscode/notebook/notebookCurrent.ts +++ /dev/null @@ -1,25 +0,0 @@ -import type { NotebookCell, TextDocument } from "vscode"; -import { window } from "vscode"; - -/** Gets the notebook containing a text document using >=1.68.0 VSCode notebook api **/ -export function getNotebookFromCellDocumentCurrent(document: TextDocument) { - // FIXME: All these type casts are necessary because we've pinned VSCode - // version type defs. Can remove them once we are using more recent type defs - const { notebookEditor } = - ((window as any).visibleNotebookEditors as any[]) - .flatMap((notebookEditor: any) => - ( - ( - notebookEditor.document ?? notebookEditor.notebook - ).getCells() as NotebookCell[] - ).map((cell) => ({ - notebookEditor, - cell, - })), - ) - .find( - ({ cell }) => cell.document.uri.toString() === document.uri.toString(), - ) ?? {}; - - return notebookEditor; -} diff --git a/packages/neovim-common/src/ide/neovim/NeovimIDE.ts b/packages/neovim-common/src/ide/neovim/NeovimIDE.ts index 6d77987079..8a45420cc5 100644 --- a/packages/neovim-common/src/ide/neovim/NeovimIDE.ts +++ b/packages/neovim-common/src/ide/neovim/NeovimIDE.ts @@ -2,6 +2,7 @@ import type { Disposable, EditableTextEditor, IDE, + NotebookEditor, OpenUntitledTextDocumentOptions, Range, RunMode, @@ -161,6 +162,10 @@ export class NeovimIDE implements IDE { // throw Error("visibleTextEditors Not implemented"); } + get visibleNotebookEditors(): NotebookEditor[] { + return []; + } + public getEditableTextEditor(editor: TextEditor): EditableTextEditor { return editor as EditableTextEditor; // throw Error("getEditableTextEditor Not implemented"); From cd1f0bec167d33937db1013ed06c5df3de88b04c Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Sat, 23 Nov 2024 11:43:14 +0100 Subject: [PATCH 2/5] Refactor --- .../scopeHandlers/NotebookCellScopeHandler.ts | 90 ++++++++++--------- 1 file changed, 49 insertions(+), 41 deletions(-) diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/NotebookCellScopeHandler.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/NotebookCellScopeHandler.ts index cd3c2aec39..31324bb9f6 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/NotebookCellScopeHandler.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/NotebookCellScopeHandler.ts @@ -45,44 +45,64 @@ export class NotebookCellScopeHandler implements ScopeHandler { ); } - const nb = getNotebook(editor); + const cells = getNotebookCells(editor, position, direction, hints); - if (nb == null) { - return; + for (const cell of cells) { + yield createTargetScope(cell); } + } +} - const { notebook, cell } = nb; +function getNotebookCells( + editor: TextEditor, + position: Position, + direction: Direction, + hints: ScopeIteratorRequirements, +) { + const nb = getNotebook(editor); - if (hints.containment === "required") { - yield createTargetScope(cell); - return; + if (nb == null) { + return []; + } + + const { notebook, cell } = nb; + + if (hints.containment === "required") { + return [cell]; + } + + if ( + hints.containment === "disallowed" || + hints.containment === "disallowedIfStrict" + ) { + return direction === "forward" + ? notebook.cells.slice(cell.index + 1) + : notebook.cells.slice(0, cell.index).reverse(); + } + + // Every scope + if (hints.distalPosition != null) { + const searchRange = new Range(position, hints.distalPosition); + if (searchRange.isRangeEqual(editor.document.range)) { + return notebook.cells; } + } - const cells = (() => { - if ( - hints.containment === "disallowed" || - hints.containment === "disallowedIfStrict" - ) { - return direction === "forward" - ? notebook.cells.slice(cell.index + 1) - : notebook.cells.slice(0, cell.index).reverse(); - } - // Every scope - if (hints.distalPosition != null) { - const searchRange = new Range(position, hints.distalPosition); - if (searchRange.isRangeEqual(editor.document.range)) { - return notebook.cells; - } - } - return direction === "forward" - ? notebook.cells.slice(cell.index) - : notebook.cells.slice(0, cell.index + 1).reverse(); - })(); + return direction === "forward" + ? notebook.cells.slice(cell.index) + : notebook.cells.slice(0, cell.index + 1).reverse(); +} - for (const cell of cells) { - yield createTargetScope(cell); +function getNotebook(editor: TextEditor) { + const uri = editor.document.uri.toString(); + for (const notebook of ide().visibleNotebookEditors) { + for (const cell of notebook.cells) { + if (cell.document.uri.toString() === uri) { + return { notebook, cell }; + } } } + return undefined; } function createTargetScope(cell: NotebookCell): TargetScope { @@ -101,18 +121,6 @@ function createTargetScope(cell: NotebookCell): TargetScope { }; } -function getNotebook(editor: TextEditor) { - const uri = editor.document.uri.toString(); - for (const notebook of ide().visibleNotebookEditors) { - for (const cell of notebook.cells) { - if (cell.document.uri.toString() === uri) { - return { notebook, cell }; - } - } - } - return undefined; -} - function getEditor(cell: NotebookCell) { for (const editor of ide().visibleTextEditors) { if (editor.document.uri.toString() === cell.document.uri.toString()) { From 56ad4c5cef3f8c8267a098a375cf9139cbbfb0ff Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Sat, 23 Nov 2024 11:44:01 +0100 Subject: [PATCH 3/5] More clean up --- .../modifiers/scopeHandlers/NotebookCellScopeHandler.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/NotebookCellScopeHandler.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/NotebookCellScopeHandler.ts index 31324bb9f6..5ca3859fa1 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/NotebookCellScopeHandler.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/NotebookCellScopeHandler.ts @@ -122,8 +122,9 @@ function createTargetScope(cell: NotebookCell): TargetScope { } function getEditor(cell: NotebookCell) { + const uri = cell.document.uri.toString(); for (const editor of ide().visibleTextEditors) { - if (editor.document.uri.toString() === cell.document.uri.toString()) { + if (editor.document.uri.toString() === uri) { return editor; } } From cdb3dd9a0e4e7f1468dbbdeb67bd8c6d6802f8a0 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Sun, 12 Jan 2025 11:14:19 +0100 Subject: [PATCH 4/5] Remove cell tests --- .../recorded/selectionTypes/drinkCell.yml | 30 -------------- .../recorded/selectionTypes/drinkCellEach.yml | 41 ------------------- .../recorded/selectionTypes/pourCell.yml | 29 ------------- .../recorded/selectionTypes/pourCellEach.yml | 41 ------------------- 4 files changed, 141 deletions(-) delete mode 100644 data/fixtures/recorded/selectionTypes/drinkCell.yml delete mode 100644 data/fixtures/recorded/selectionTypes/drinkCellEach.yml delete mode 100644 data/fixtures/recorded/selectionTypes/pourCell.yml delete mode 100644 data/fixtures/recorded/selectionTypes/pourCellEach.yml diff --git a/data/fixtures/recorded/selectionTypes/drinkCell.yml b/data/fixtures/recorded/selectionTypes/drinkCell.yml deleted file mode 100644 index 04a850b42c..0000000000 --- a/data/fixtures/recorded/selectionTypes/drinkCell.yml +++ /dev/null @@ -1,30 +0,0 @@ -languageId: python -postCommandSleepTimeMs: 400 -command: - version: 6 - spokenForm: drink cell - action: - name: editNewLineBefore - target: - type: primitive - modifiers: - - type: containingScope - scopeType: {type: notebookCell} - usePrePhraseSnapshot: false -initialState: - documentContents: |- - # %% - print("hello") - selections: - - anchor: {line: 1, character: 12} - active: {line: 1, character: 12} - marks: {} -finalState: - documentContents: |- - # %% - - # %% - print("hello") - selections: - - anchor: {line: 1, character: 0} - active: {line: 1, character: 0} diff --git a/data/fixtures/recorded/selectionTypes/drinkCellEach.yml b/data/fixtures/recorded/selectionTypes/drinkCellEach.yml deleted file mode 100644 index afb6ea2538..0000000000 --- a/data/fixtures/recorded/selectionTypes/drinkCellEach.yml +++ /dev/null @@ -1,41 +0,0 @@ -languageId: python -postEditorOpenSleepTimeMs: 250 -postCommandSleepTimeMs: 400 -command: - version: 6 - spokenForm: drink cell each - action: - name: editNewLineBefore - target: - type: primitive - modifiers: - - type: containingScope - scopeType: {type: notebookCell} - mark: {type: decoratedSymbol, symbolColor: default, character: e} - usePrePhraseSnapshot: false -initialState: - documentContents: |- - # %% - print("hello") - - # %% - print("hello") - selections: - - anchor: {line: 4, character: 12} - active: {line: 4, character: 12} - marks: - default.e: - start: {line: 1, character: 7} - end: {line: 1, character: 12} -finalState: - documentContents: |- - # %% - - # %% - print("hello") - - # %% - print("hello") - selections: - - anchor: {line: 1, character: 0} - active: {line: 1, character: 0} diff --git a/data/fixtures/recorded/selectionTypes/pourCell.yml b/data/fixtures/recorded/selectionTypes/pourCell.yml deleted file mode 100644 index 0da0c0c820..0000000000 --- a/data/fixtures/recorded/selectionTypes/pourCell.yml +++ /dev/null @@ -1,29 +0,0 @@ -languageId: python -postCommandSleepTimeMs: 400 -command: - version: 6 - spokenForm: pour cell - action: - name: editNewLineAfter - target: - type: primitive - modifiers: - - type: containingScope - scopeType: {type: notebookCell} - usePrePhraseSnapshot: false -initialState: - documentContents: |- - # %% - print("hello") - selections: - - anchor: {line: 1, character: 12} - active: {line: 1, character: 12} - marks: {} -finalState: - documentContents: | - # %% - print("hello") - # %% - selections: - - anchor: {line: 3, character: 0} - active: {line: 3, character: 0} diff --git a/data/fixtures/recorded/selectionTypes/pourCellEach.yml b/data/fixtures/recorded/selectionTypes/pourCellEach.yml deleted file mode 100644 index 64e8cd8fac..0000000000 --- a/data/fixtures/recorded/selectionTypes/pourCellEach.yml +++ /dev/null @@ -1,41 +0,0 @@ -languageId: python -postEditorOpenSleepTimeMs: 250 -postCommandSleepTimeMs: 400 -command: - version: 6 - spokenForm: pour cell each - action: - name: editNewLineAfter - target: - type: primitive - modifiers: - - type: containingScope - scopeType: {type: notebookCell} - mark: {type: decoratedSymbol, symbolColor: default, character: e} - usePrePhraseSnapshot: false -initialState: - documentContents: |- - # %% - print("hello") - - # %% - print("hello") - selections: - - anchor: {line: 4, character: 12} - active: {line: 4, character: 12} - marks: - default.e: - start: {line: 1, character: 7} - end: {line: 1, character: 12} -finalState: - documentContents: |- - # %% - print("hello") - - # %% - - # %% - print("hello") - selections: - - anchor: {line: 4, character: 0} - active: {line: 4, character: 0} From f8a8ed95c4b4cdc5d225386a1db415a79150534b Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Wed, 15 Jan 2025 07:38:46 +0100 Subject: [PATCH 5/5] remove jupiter command and selection hack --- packages/common/src/types/TextEditor.ts | 6 +-- .../actions/EditNew/runNotebookCellTargets.ts | 8 +--- .../src/ide/TalonJsEditor.ts | 2 +- .../src/ide/vscode/VscodeNotebooks.ts | 44 ------------------- .../src/ide/vscode/VscodeTextEditorImpl.ts | 14 ++---- .../src/ide/neovim/NeovimTextEditorImpl.ts | 4 +- 6 files changed, 8 insertions(+), 70 deletions(-) delete mode 100644 packages/cursorless-vscode/src/ide/vscode/VscodeNotebooks.ts diff --git a/packages/common/src/types/TextEditor.ts b/packages/common/src/types/TextEditor.ts index 9e7ed66521..db4599d926 100644 --- a/packages/common/src/types/TextEditor.ts +++ b/packages/common/src/types/TextEditor.ts @@ -110,12 +110,8 @@ export interface EditableTextEditor extends TextEditor { /** * Edit a new new notebook cell above. - * @return A promise that resolves to a function that must be applied to any - * selections that should be updated as result of this operation. This is a - * horrible hack to work around the fact that in vscode the promise resolves - * before the edits have actually been performed. */ - editNewNotebookCellAbove(): Promise<(selection: Selection) => Selection>; + editNewNotebookCellAbove(): Promise; /** * Edit a new new notebook cell below. diff --git a/packages/cursorless-engine/src/actions/EditNew/runNotebookCellTargets.ts b/packages/cursorless-engine/src/actions/EditNew/runNotebookCellTargets.ts index 4e3ab43929..6dcc65d51f 100644 --- a/packages/cursorless-engine/src/actions/EditNew/runNotebookCellTargets.ts +++ b/packages/cursorless-engine/src/actions/EditNew/runNotebookCellTargets.ts @@ -1,4 +1,3 @@ -import type { Selection } from "@cursorless/common"; import { ide } from "../../singletons/ide.singleton"; import type { Destination } from "../../typings/target.types"; import { createThatMark, ensureSingleTarget } from "../../util/targetUtils"; @@ -23,18 +22,13 @@ export async function runEditNewNotebookCellTargets( await actions.setSelection.run([destination.target]); - let modifyThatMark = (selection: Selection) => selection; if (isAbove) { - modifyThatMark = await editor.editNewNotebookCellAbove(); + await editor.editNewNotebookCellAbove(); } else { await editor.editNewNotebookCellBelow(); } const thatMark = createThatMark([destination.target.thatTarget]); - // Apply horrible hack to work around the fact that in vscode the promise - // resolves before the edits have actually been performed. - thatMark[0].selection = modifyThatMark(thatMark[0].selection); - return { thatSelections: thatMark }; } diff --git a/packages/cursorless-everywhere-talon-core/src/ide/TalonJsEditor.ts b/packages/cursorless-everywhere-talon-core/src/ide/TalonJsEditor.ts index 04ce668f82..39ed343551 100644 --- a/packages/cursorless-everywhere-talon-core/src/ide/TalonJsEditor.ts +++ b/packages/cursorless-everywhere-talon-core/src/ide/TalonJsEditor.ts @@ -149,7 +149,7 @@ export class TalonJsEditor implements EditableTextEditor { throw new Error("extractVariable not implemented."); } - editNewNotebookCellAbove(): Promise<(_selection: Selection) => Selection> { + editNewNotebookCellAbove(): Promise { throw new Error("editNewNotebookCellAbove not implemented."); } diff --git a/packages/cursorless-vscode/src/ide/vscode/VscodeNotebooks.ts b/packages/cursorless-vscode/src/ide/vscode/VscodeNotebooks.ts deleted file mode 100644 index e89a1da006..0000000000 --- a/packages/cursorless-vscode/src/ide/vscode/VscodeNotebooks.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { Selection } from "@cursorless/common"; -import * as vscode from "vscode"; -import { getNotebookFromCellDocument } from "./notebook/notebook"; -import type { VscodeTextEditorImpl } from "./VscodeTextEditorImpl"; - -export async function vscodeEditNewNotebookCellAbove( - editor: VscodeTextEditorImpl, -): Promise<(selection: Selection) => Selection> { - const isNotebook = isNotebookEditor(editor); - - const command = isNotebook - ? "notebook.cell.insertCodeCellAbove" - : "jupyter.insertCellAbove"; - - await vscode.commands.executeCommand(command); - - // This is a horrible hack to work around the fact that in vscode the promise - // resolves before the edits have actually been performed. This lambda will - // be applied to the selection of the that mark to pretend like the edit has - // been performed and moved the that mark down accordingly. - return isNotebook - ? (selection) => selection - : (selection) => - new Selection( - selection.anchor.translate(2, undefined), - selection.active.translate(2, undefined), - ); -} - -export async function vscodeEditNewNotebookCellBelow( - editor: VscodeTextEditorImpl, -): Promise { - const isNotebook = isNotebookEditor(editor); - - const command = isNotebook - ? "notebook.cell.insertCodeCellBelow" - : "jupyter.insertCellBelow"; - - await vscode.commands.executeCommand(command); -} - -function isNotebookEditor(editor: VscodeTextEditorImpl) { - return getNotebookFromCellDocument(editor.vscodeEditor.document) != null; -} diff --git a/packages/cursorless-vscode/src/ide/vscode/VscodeTextEditorImpl.ts b/packages/cursorless-vscode/src/ide/vscode/VscodeTextEditorImpl.ts index 68ca2ca10f..fc42b61f49 100644 --- a/packages/cursorless-vscode/src/ide/vscode/VscodeTextEditorImpl.ts +++ b/packages/cursorless-vscode/src/ide/vscode/VscodeTextEditorImpl.ts @@ -24,10 +24,6 @@ import vscodeFocusEditor from "./VscodeFocusEditor"; import { vscodeFold, vscodeUnfold } from "./VscodeFold"; import type { VscodeIDE } from "./VscodeIDE"; import { vscodeInsertSnippet } from "./VscodeInsertSnippets"; -import { - vscodeEditNewNotebookCellAbove, - vscodeEditNewNotebookCellBelow, -} from "./VscodeNotebooks"; import vscodeOpenLink from "./VscodeOpenLink"; import { vscodeRevealLine } from "./VscodeRevealLine"; import { VscodeTextDocumentImpl } from "./VscodeTextDocumentImpl"; @@ -137,14 +133,12 @@ export class VscodeTextEditorImpl implements EditableTextEditor { return vscodeFocusEditor(this); } - public editNewNotebookCellAbove(): Promise< - (selection: Selection) => Selection - > { - return vscodeEditNewNotebookCellAbove(this); + public async editNewNotebookCellAbove(): Promise { + await vscode.commands.executeCommand("notebook.cell.insertCodeCellAbove"); } - public editNewNotebookCellBelow(): Promise { - return vscodeEditNewNotebookCellBelow(this); + public async editNewNotebookCellBelow(): Promise { + await vscode.commands.executeCommand("notebook.cell.insertCodeCellBelow"); } public openLink( diff --git a/packages/neovim-common/src/ide/neovim/NeovimTextEditorImpl.ts b/packages/neovim-common/src/ide/neovim/NeovimTextEditorImpl.ts index c776cb5353..0e3db8dbaa 100644 --- a/packages/neovim-common/src/ide/neovim/NeovimTextEditorImpl.ts +++ b/packages/neovim-common/src/ide/neovim/NeovimTextEditorImpl.ts @@ -107,9 +107,7 @@ export class NeovimTextEditorImpl implements EditableTextEditor { // throw Error("focus Not implemented"); } - public editNewNotebookCellAbove(): Promise< - (selection: Selection) => Selection - > { + public editNewNotebookCellAbove(): Promise { throw Error("editNewNotebookCellAbove Not implemented"); }