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} 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 3721c82a07..fbc17884d0 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/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-engine/src/processTargets/ModifierStageFactoryImpl.ts b/packages/cursorless-engine/src/processTargets/ModifierStageFactoryImpl.ts index 17e040c88f..15a1f14c47 100644 --- a/packages/cursorless-engine/src/processTargets/ModifierStageFactoryImpl.ts +++ b/packages/cursorless-engine/src/processTargets/ModifierStageFactoryImpl.ts @@ -35,7 +35,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( @@ -135,8 +134,6 @@ export class ModifierStageFactoryImpl implements ModifierStageFactory { modifier: ContainingScopeModifier | EveryScopeModifier, ): ModifierStage { switch (modifier.scopeType.type) { - case "notebookCell": - return new NotebookCellStage(modifier); default: // Default to containing syntax scope using tree sitter return new LegacyContainingSyntaxScopeStage( 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..5ca3859fa1 --- /dev/null +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/NotebookCellScopeHandler.ts @@ -0,0 +1,132 @@ +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 cells = getNotebookCells(editor, position, direction, hints); + + for (const cell of cells) { + yield createTargetScope(cell); + } + } +} + +function getNotebookCells( + editor: TextEditor, + position: Position, + direction: Direction, + hints: ScopeIteratorRequirements, +) { + const nb = getNotebook(editor); + + 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; + } + } + + return direction === "forward" + ? notebook.cells.slice(cell.index) + : notebook.cells.slice(0, cell.index + 1).reverse(); +} + +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 { + const editor = getEditor(cell); + const contentRange = editor.document.range; + return { + editor, + domain: contentRange, + getTargets: (isReversed: boolean) => [ + new NotebookCellTarget({ + editor, + isReversed, + contentRange, + }), + ], + }; +} + +function getEditor(cell: NotebookCell) { + const uri = cell.document.uri.toString(); + for (const editor of ide().visibleTextEditors) { + if (editor.document.uri.toString() === uri) { + 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 c8501df9ca..2626555830 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/ScopeHandlerFactoryImpl.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/ScopeHandlerFactoryImpl.ts @@ -11,6 +11,7 @@ import { DocumentScopeHandler } from "./DocumentScopeHandler"; import { FallbackScopeHandler } from "./FallbackScopeHandler"; import { IdentifierScopeHandler } from "./IdentifierScopeHandler"; import { LineScopeHandler } from "./LineScopeHandler"; +import { NotebookCellScopeHandler } from "./NotebookCellScopeHandler"; import { OneOfScopeHandler } from "./OneOfScopeHandler"; import { ParagraphScopeHandler } from "./ParagraphScopeHandler"; import { @@ -111,6 +112,12 @@ export class ScopeHandlerFactoryImpl implements ScopeHandlerFactory { scopeType, languageId, ); + case "notebookCell": + return new NotebookCellScopeHandler( + this.languageDefinitions, + scopeType, + languageId, + ); case "custom": return scopeType.scopeHandler; case "oneOf": 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/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-everywhere-talon-core/src/ide/TalonJsIDE.ts b/packages/cursorless-everywhere-talon-core/src/ide/TalonJsIDE.ts index 48c3623ed6..2fb21e304c 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/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/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"); 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"); }