From 9ce18f87c1c9f4c8717150f51b8169ac3382082a Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Thu, 17 Nov 2022 08:42:12 +0100 Subject: [PATCH 01/43] remove dependency upon vscode.DecorationRangeBehavior --- src/actions/BringMoveSwap.ts | 9 ++---- src/actions/EditNew/runEditTargets.ts | 3 +- src/actions/InsertCopy.ts | 3 +- src/actions/InsertSnippet.ts | 8 ++--- src/actions/Paste.ts | 4 +-- src/actions/Wrap.ts | 31 +++---------------- src/core/updateSelections/updateSelections.ts | 18 +++++------ src/libs/common/index.ts | 1 + .../common/types/DecorationRangeBehavior.ts | 23 ++++++++++++++ 9 files changed, 44 insertions(+), 56 deletions(-) create mode 100644 src/libs/common/types/DecorationRangeBehavior.ts diff --git a/src/actions/BringMoveSwap.ts b/src/actions/BringMoveSwap.ts index b3d8badcc0..0846d05caa 100644 --- a/src/actions/BringMoveSwap.ts +++ b/src/actions/BringMoveSwap.ts @@ -1,6 +1,5 @@ import { Selection, TextEditor } from "@cursorless/common"; import { flatten } from "lodash"; -import { DecorationRangeBehavior } from "vscode"; import { getSelectionInfo, performEditsAndUpdateFullSelectionInfos, @@ -169,16 +168,12 @@ class BringMoveSwap implements Action { getSelectionInfo( editor.document, range.toSelection(originalTarget.isReversed), - DecorationRangeBehavior.OpenOpen, + "OpenOpen", ), ); const cursorSelectionInfos = editor.selections.map((selection) => - getSelectionInfo( - editor.document, - selection, - DecorationRangeBehavior.ClosedClosed, - ), + getSelectionInfo(editor.document, selection, "ClosedClosed"), ); const editableEditor = ide().getEditableTextEditor(editor); diff --git a/src/actions/EditNew/runEditTargets.ts b/src/actions/EditNew/runEditTargets.ts index 5b97ea6153..a67b6225bf 100644 --- a/src/actions/EditNew/runEditTargets.ts +++ b/src/actions/EditNew/runEditTargets.ts @@ -1,6 +1,5 @@ import { Selection, EditableTextEditor } from "@cursorless/common"; import { zip } from "lodash"; -import { DecorationRangeBehavior } from "vscode"; import { performEditsAndUpdateSelectionsWithBehavior } from "../../core/updateSelections/updateSelections"; import { Graph } from "../../typings/Types"; import { EditTarget, State } from "./EditNew.types"; @@ -63,7 +62,7 @@ export async function runEditTargets( const editSelections = { selections: edits.map((edit) => edit.range.toSelection(false)), - rangeBehavior: DecorationRangeBehavior.OpenOpen, + rangeBehavior: "OpenOpen", }; const [ diff --git a/src/actions/InsertCopy.ts b/src/actions/InsertCopy.ts index 81be1f11c4..80577c7232 100644 --- a/src/actions/InsertCopy.ts +++ b/src/actions/InsertCopy.ts @@ -1,6 +1,5 @@ import { Selection, TextEditor } from "@cursorless/common"; import { flatten, zip } from "lodash"; -import { DecorationRangeBehavior } from "vscode"; import { performEditsAndUpdateSelectionsWithBehavior } from "../core/updateSelections/updateSelections"; import ide from "../libs/cursorless-engine/singletons/ide.singleton"; import { containingLineIfUntypedStage } from "../processTargets/modifiers/commonContainingScopeIfUntypedStages"; @@ -55,7 +54,7 @@ class InsertCopy implements Action { selections: edits.map( ({ range }) => new Selection(range.start, range.end), ), - rangeBehavior: DecorationRangeBehavior.OpenOpen, + rangeBehavior: "OpenOpen", }; const editableEditor = ide().getEditableTextEditor(editor); diff --git a/src/actions/InsertSnippet.ts b/src/actions/InsertSnippet.ts index 3261adcbde..a5ed9679d3 100644 --- a/src/actions/InsertSnippet.ts +++ b/src/actions/InsertSnippet.ts @@ -1,4 +1,4 @@ -import { commands, DecorationRangeBehavior } from "vscode"; +import { commands } from "vscode"; import textFormatters from "../core/textFormatters"; import { callFunctionAndUpdateSelectionInfos, @@ -76,11 +76,7 @@ export default class InsertSnippet implements Action { await this.graph.actions.editNew.run([targets]); const targetSelectionInfos = editor.selections.map((selection) => - getSelectionInfo( - editor.document, - selection, - DecorationRangeBehavior.OpenOpen, - ), + getSelectionInfo(editor.document, selection, "OpenOpen"), ); // NB: We used the command "editor.action.insertSnippet" instead of calling editor.insertSnippet diff --git a/src/actions/Paste.ts b/src/actions/Paste.ts index 822b1576f2..0e98d38dd5 100644 --- a/src/actions/Paste.ts +++ b/src/actions/Paste.ts @@ -1,4 +1,4 @@ -import { commands, DecorationRangeBehavior } from "vscode"; +import { commands } from "vscode"; import { callFunctionAndUpdateSelections, callFunctionAndUpdateSelectionsWithBehavior, @@ -42,7 +42,7 @@ export class Paste { }, { selections: targetEditor.selections, - rangeBehavior: DecorationRangeBehavior.OpenOpen, + rangeBehavior: "OpenOpen", }, ], ); diff --git a/src/actions/Wrap.ts b/src/actions/Wrap.ts index f702a07cea..a87c6f795f 100644 --- a/src/actions/Wrap.ts +++ b/src/actions/Wrap.ts @@ -1,5 +1,4 @@ import { Selection } from "@cursorless/common"; -import { DecorationRangeBehavior } from "vscode"; import { getSelectionInfo, performEditsAndUpdateFullSelectionInfos, @@ -49,42 +48,22 @@ export default class Wrap implements Action { const delimiterSelectionInfos: FullSelectionInfo[] = boundaries.flatMap( ({ start, end }) => { return [ - getSelectionInfo( - document, - start, - DecorationRangeBehavior.OpenClosed, - ), - getSelectionInfo( - document, - end, - DecorationRangeBehavior.ClosedOpen, - ), + getSelectionInfo(document, start, "OpenClosed"), + getSelectionInfo(document, end, "ClosedOpen"), ]; }, ); const cursorSelectionInfos = editor.selections.map((selection) => - getSelectionInfo( - document, - selection, - DecorationRangeBehavior.ClosedClosed, - ), + getSelectionInfo(document, selection, "ClosedClosed"), ); const sourceMarkSelectionInfos = targets.map((target) => - getSelectionInfo( - document, - target.contentSelection, - DecorationRangeBehavior.ClosedClosed, - ), + getSelectionInfo(document, target.contentSelection, "ClosedClosed"), ); const thatMarkSelectionInfos = targets.map((target) => - getSelectionInfo( - document, - target.contentSelection, - DecorationRangeBehavior.OpenOpen, - ), + getSelectionInfo(document, target.contentSelection, "OpenOpen"), ); const editableEditor = ide().getEditableTextEditor(editor); diff --git a/src/core/updateSelections/updateSelections.ts b/src/core/updateSelections/updateSelections.ts index 77d879a319..7121f98eb7 100644 --- a/src/core/updateSelections/updateSelections.ts +++ b/src/core/updateSelections/updateSelections.ts @@ -1,11 +1,11 @@ import { + DecorationRangeBehavior, EditableTextEditor, Range, Selection, TextDocument, } from "@cursorless/common"; import { flatten } from "lodash"; -import { DecorationRangeBehavior } from "vscode"; import { Edit } from "../../typings/Types"; import { FullSelectionInfo, @@ -53,15 +53,13 @@ function getSelectionInfoInternal( expansionBehavior: { start: { type: - rangeBehavior === DecorationRangeBehavior.ClosedClosed || - rangeBehavior === DecorationRangeBehavior.ClosedOpen + rangeBehavior === "ClosedClosed" || rangeBehavior === "ClosedOpen" ? "closed" : "open", }, end: { type: - rangeBehavior === DecorationRangeBehavior.ClosedClosed || - rangeBehavior === DecorationRangeBehavior.OpenClosed + rangeBehavior === "ClosedClosed" || rangeBehavior === "OpenClosed" ? "closed" : "open", }, @@ -85,7 +83,7 @@ function getSelectionInfoInternal( function selectionsToSelectionInfos( document: TextDocument, selectionMatrix: (readonly Selection[])[], - rangeBehavior: DecorationRangeBehavior = DecorationRangeBehavior.ClosedClosed, + rangeBehavior: DecorationRangeBehavior = "ClosedClosed", ): FullSelectionInfo[][] { return selectionMatrix.map((selections) => selections.map((selection) => @@ -97,7 +95,7 @@ function selectionsToSelectionInfos( function rangesToSelectionInfos( document: TextDocument, rangeMatrix: (readonly Range[])[], - rangeBehavior: DecorationRangeBehavior = DecorationRangeBehavior.ClosedClosed, + rangeBehavior: DecorationRangeBehavior = "ClosedClosed", ): FullSelectionInfo[][] { return rangeMatrix.map((ranges) => ranges.map((range) => @@ -230,8 +228,7 @@ export function callFunctionAndUpdateSelectionsWithBehavior( getSelectionInfo( document, selection, - selectionsWithBehavior.rangeBehavior ?? - DecorationRangeBehavior.ClosedClosed, + selectionsWithBehavior.rangeBehavior ?? "ClosedClosed", ), ), ), @@ -291,8 +288,7 @@ export function performEditsAndUpdateSelectionsWithBehavior( getSelectionInfo( editor.document, selection, - selectionsWithBehavior.rangeBehavior ?? - DecorationRangeBehavior.ClosedClosed, + selectionsWithBehavior.rangeBehavior ?? "ClosedClosed", ), ), ), diff --git a/src/libs/common/index.ts b/src/libs/common/index.ts index 47ad83280b..d387073d57 100644 --- a/src/libs/common/index.ts +++ b/src/libs/common/index.ts @@ -17,6 +17,7 @@ export { walkFilesSync } from "./util/walkSync"; export { Listener, Notifier } from "./util/Notifier"; export { TokenHatSplittingMode } from "./ide/types/Configuration"; export * from "./ide/types/ide.types"; +export * from "./types/DecorationRangeBehavior"; export * from "./types/Position"; export * from "./types/Range"; export * from "./types/Selection"; diff --git a/src/libs/common/types/DecorationRangeBehavior.ts b/src/libs/common/types/DecorationRangeBehavior.ts new file mode 100644 index 0000000000..0bacf32481 --- /dev/null +++ b/src/libs/common/types/DecorationRangeBehavior.ts @@ -0,0 +1,23 @@ +/** + * Describes the behavior of decorations when typing/editing at their edges. + */ +export type DecorationRangeBehavior = + /** + * The decoration's range will widen when edits occur at the start or end. + */ + | "OpenOpen" + + /** + * The decoration's range will not widen when edits occur at the start of end. + */ + | "ClosedClosed" + + /** + * The decoration's range will widen when edits occur at the start, but not at the end. + */ + | "OpenClosed" + + /** + * The decoration's range will widen when edits occur at the end, but not at the start. + */ + | "ClosedOpen"; From 03445b5e4b146292422d79d7809b9c80cdf76aee Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Thu, 17 Nov 2022 09:04:45 +0100 Subject: [PATCH 02/43] updated scroll --- src/actions/Scroll.ts | 15 +++++---------- src/ide/vscode/VscodeRevealLine.ts | 19 +++++++++++++++++++ src/ide/vscode/VscodeTextEditorImpl.ts | 6 ++++++ src/libs/common/index.ts | 1 + src/libs/common/types/RevealLineAt.ts | 15 +++++++++++++++ src/libs/common/types/TextEditor.ts | 9 +++++++++ 6 files changed, 55 insertions(+), 10 deletions(-) create mode 100644 src/ide/vscode/VscodeRevealLine.ts create mode 100644 src/libs/common/types/RevealLineAt.ts diff --git a/src/actions/Scroll.ts b/src/actions/Scroll.ts index d5cbd7cadb..688f489c1d 100644 --- a/src/actions/Scroll.ts +++ b/src/actions/Scroll.ts @@ -1,4 +1,4 @@ -import { commands } from "vscode"; +import type { RevealLineAt } from "@cursorless/common"; import ide from "../libs/cursorless-engine/singletons/ide.singleton"; import { Target } from "../typings/target.types"; import { Graph } from "../typings/Types"; @@ -6,7 +6,7 @@ import { groupBy } from "../util/itertools"; import { Action, ActionReturnValue } from "./actions.types"; class Scroll implements Action { - constructor(private graph: Graph, private at: string) { + constructor(private graph: Graph, private at: RevealLineAt) { this.run = this.run.bind(this); } @@ -20,14 +20,9 @@ class Scroll implements Action { const originalEditor = ide().activeEditableTextEditor; for (const lineWithEditor of lines) { - // For reveal line to the work we have to have the correct editor focused - if (!lineWithEditor.editor.isActive) { - await ide().getEditableTextEditor(lineWithEditor.editor).focus(); - } - await commands.executeCommand("revealLine", { - lineNumber: lineWithEditor.lineNumber, - at: this.at, - }); + await ide() + .getEditableTextEditor(lineWithEditor.editor) + .revealLine(lineWithEditor.lineNumber, this.at); } // If necessary focus back original editor diff --git a/src/ide/vscode/VscodeRevealLine.ts b/src/ide/vscode/VscodeRevealLine.ts new file mode 100644 index 0000000000..b5b00fc053 --- /dev/null +++ b/src/ide/vscode/VscodeRevealLine.ts @@ -0,0 +1,19 @@ +import * as vscode from "vscode"; +import { RevealLineAt } from "@cursorless/common"; +import { VscodeTextEditorImpl } from "./VscodeTextEditorImpl"; + +export async function vscodeRevealLine( + editor: VscodeTextEditorImpl, + lineNumber: number, + at: RevealLineAt, +): Promise { + // For reveal line to the work we have to have the correct editor focused + if (!editor.isActive) { + await editor.focus(); + } + + await vscode.commands.executeCommand("revealLine", { + lineNumber, + at, + }); +} diff --git a/src/ide/vscode/VscodeTextEditorImpl.ts b/src/ide/vscode/VscodeTextEditorImpl.ts index d569e26f6d..349789a626 100644 --- a/src/ide/vscode/VscodeTextEditorImpl.ts +++ b/src/ide/vscode/VscodeTextEditorImpl.ts @@ -1,6 +1,7 @@ import type { Position, Range, + RevealLineAt, Selection, TextDocument, TextEditor, @@ -20,6 +21,7 @@ import vscodeEdit from "./VscodeEdit"; import vscodeFocusEditor from "./VscodeFocusEditor"; import VscodeIDE from "./VscodeIDE"; import vscodeOpenLink from "./VscodeOpenLink"; +import { vscodeRevealLine } from "./VscodeRevealLine"; import { VscodeTextDocumentImpl } from "./VscodeTextDocumentImpl"; export class VscodeTextEditorImpl implements TextEditor { @@ -69,6 +71,10 @@ export class VscodeTextEditorImpl implements TextEditor { this.editor.revealRange(toVscodeRange(range)); } + public revealLine(lineNumber: number, at: RevealLineAt): Promise { + return vscodeRevealLine(this, lineNumber, at); + } + public setDecorations( decorationType: TextEditorDecorationType, ranges: readonly Range[], diff --git a/src/libs/common/index.ts b/src/libs/common/index.ts index d387073d57..03d89f5c82 100644 --- a/src/libs/common/index.ts +++ b/src/libs/common/index.ts @@ -20,6 +20,7 @@ export * from "./ide/types/ide.types"; export * from "./types/DecorationRangeBehavior"; export * from "./types/Position"; export * from "./types/Range"; +export * from "./types/RevealLineAt"; export * from "./types/Selection"; export * from "./types/TextDocument"; export * from "./types/TextEditor"; diff --git a/src/libs/common/types/RevealLineAt.ts b/src/libs/common/types/RevealLineAt.ts new file mode 100644 index 0000000000..40abf8a3a5 --- /dev/null +++ b/src/libs/common/types/RevealLineAt.ts @@ -0,0 +1,15 @@ +export type RevealLineAt = + /** + * Reveal line at top of viewport + */ + | "top" + + /** + * Reveal line at center of viewport + */ + | "center" + + /** + * Reveal line at bottom of viewport + */ + | "bottom"; diff --git a/src/libs/common/types/TextEditor.ts b/src/libs/common/types/TextEditor.ts index 79219d9b8a..940b082ca6 100644 --- a/src/libs/common/types/TextEditor.ts +++ b/src/libs/common/types/TextEditor.ts @@ -1,6 +1,7 @@ import type { Position, Range, + RevealLineAt, Selection, TextDocument, TextEditorDecorationType, @@ -65,6 +66,14 @@ export interface EditableTextEditor extends TextEditor { */ revealRange(range: Range): void; + /** + * Scroll to reveal the given line. + * + * @param lineNumber A line number. + * @param at Were to reveal the line at: top|center|bottom. + */ + revealLine(lineNumber: number, at: RevealLineAt): Promise; + /** * Focus the editor. */ From 126053083807d40cde106603aeced5fa4505cb35 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Thu, 17 Nov 2022 09:17:10 +0100 Subject: [PATCH 03/43] make sure editable actions return promise --- src/actions/InsertCopy.ts | 2 +- src/ide/vscode/VscodeEdit.ts | 6 +++--- src/ide/vscode/VscodeTextEditorImpl.ts | 8 ++++---- src/libs/common/types/TextEditor.ts | 6 +++--- src/util/setSelectionsAndFocusEditor.ts | 2 +- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/actions/InsertCopy.ts b/src/actions/InsertCopy.ts index 80577c7232..d91b50f92a 100644 --- a/src/actions/InsertCopy.ts +++ b/src/actions/InsertCopy.ts @@ -75,7 +75,7 @@ class InsertCopy implements Action { ); setSelectionsWithoutFocusingEditor(editableEditor, updatedEditorSelections); - editableEditor.revealRange(editor.selections[0]); + await editableEditor.revealRange(editor.selections[0]); return { sourceMark: createThatMark(targets, insertionRanges), diff --git a/src/ide/vscode/VscodeEdit.ts b/src/ide/vscode/VscodeEdit.ts index c80184301b..b738394255 100644 --- a/src/ide/vscode/VscodeEdit.ts +++ b/src/ide/vscode/VscodeEdit.ts @@ -7,12 +7,12 @@ import { } from "@cursorless/vscode-common"; import type * as vscode from "vscode"; -export default function vscodeEdit( +export default async function vscodeEdit( editor: vscode.TextEditor, callback: (editBuilder: TextEditorEdit) => void, options?: { undoStopBefore: boolean; undoStopAfter: boolean }, -): Thenable { - return editor.edit((editBuilder) => { +): Promise { + return await editor.edit((editBuilder) => { callback({ replace: (location, value) => { editBuilder.replace(toVscodePositionOrRange(location), value); diff --git a/src/ide/vscode/VscodeTextEditorImpl.ts b/src/ide/vscode/VscodeTextEditorImpl.ts index 349789a626..7d7d826c07 100644 --- a/src/ide/vscode/VscodeTextEditorImpl.ts +++ b/src/ide/vscode/VscodeTextEditorImpl.ts @@ -67,7 +67,7 @@ export class VscodeTextEditorImpl implements TextEditor { return this.id === other.id; } - public revealRange(range: Range): void { + public async revealRange(range: Range): Promise { this.editor.revealRange(toVscodeRange(range)); } @@ -75,17 +75,17 @@ export class VscodeTextEditorImpl implements TextEditor { return vscodeRevealLine(this, lineNumber, at); } - public setDecorations( + public async setDecorations( decorationType: TextEditorDecorationType, ranges: readonly Range[], - ): void { + ): Promise { this.editor.setDecorations(decorationType, ranges.map(toVscodeRange)); } public edit( callback: (editBuilder: TextEditorEdit) => void, options?: { undoStopBefore: boolean; undoStopAfter: boolean }, - ): Thenable { + ): Promise { return vscodeEdit(this.editor, callback, options); } diff --git a/src/libs/common/types/TextEditor.ts b/src/libs/common/types/TextEditor.ts index 940b082ca6..6de22293c8 100644 --- a/src/libs/common/types/TextEditor.ts +++ b/src/libs/common/types/TextEditor.ts @@ -64,7 +64,7 @@ export interface EditableTextEditor extends TextEditor { * * @param range A range. */ - revealRange(range: Range): void; + revealRange(range: Range): Promise; /** * Scroll to reveal the given line. @@ -91,7 +91,7 @@ export interface EditableTextEditor extends TextEditor { setDecorations( decorationType: TextEditorDecorationType, ranges: readonly Range[], - ): void; + ): Promise; /** * Perform an edit on the document associated with this text editor. @@ -107,7 +107,7 @@ export interface EditableTextEditor extends TextEditor { edit( callback: (editBuilder: TextEditorEdit) => void, options?: { undoStopBefore: boolean; undoStopAfter: boolean }, - ): Thenable; + ): Promise; /** * Open link at location. diff --git a/src/util/setSelectionsAndFocusEditor.ts b/src/util/setSelectionsAndFocusEditor.ts index bf1dbb457e..2b8b318a22 100644 --- a/src/util/setSelectionsAndFocusEditor.ts +++ b/src/util/setSelectionsAndFocusEditor.ts @@ -10,7 +10,7 @@ export async function setSelectionsAndFocusEditor( setSelectionsWithoutFocusingEditor(editor, selections); if (revealRange) { - editor.revealRange(editor.selections[0]); + await editor.revealRange(editor.selections[0]); } // NB: We focus the editor after setting the selection because otherwise you see From 12b040bf8c026234516bae6ab3c31f2010c307c9 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Thu, 17 Nov 2022 09:45:05 +0100 Subject: [PATCH 04/43] update paste action --- src/actions/Paste.ts | 12 +++++------- src/ide/vscode/VscodeTextEditorImpl.ts | 4 ++++ src/libs/common/types/TextEditor.ts | 5 +++++ 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/actions/Paste.ts b/src/actions/Paste.ts index 0e98d38dd5..dacd5ade51 100644 --- a/src/actions/Paste.ts +++ b/src/actions/Paste.ts @@ -1,4 +1,3 @@ -import { commands } from "vscode"; import { callFunctionAndUpdateSelections, callFunctionAndUpdateSelectionsWithBehavior, @@ -14,7 +13,9 @@ export class Paste { constructor(private graph: Graph) {} async run([targets]: [Target[]]): Promise { - const targetEditor = ensureSingleEditor(targets); + const targetEditor = ide().getEditableTextEditor( + ensureSingleEditor(targets), + ); const originalEditor = ide().activeEditableTextEditor; // First call editNew in order to insert delimiters if necessary and leave @@ -34,7 +35,7 @@ export class Paste { const [updatedCursorSelections, updatedTargetSelections] = await callFunctionAndUpdateSelectionsWithBehavior( this.graph.rangeUpdater, - () => commands.executeCommand("editor.action.clipboardPasteAction"), + () => targetEditor.clipboardPaste(), targetEditor.document, [ { @@ -50,10 +51,7 @@ export class Paste { // Reset cursors on the editor where the edits took place. // NB: We don't focus the editor here because we want to focus the original // editor, not the one where the edits took place - setSelectionsWithoutFocusingEditor( - ide().getEditableTextEditor(targetEditor), - updatedCursorSelections, - ); + setSelectionsWithoutFocusingEditor(targetEditor, updatedCursorSelections); // If necessary focus back original editor if (originalEditor != null && !originalEditor.isActive) { diff --git a/src/ide/vscode/VscodeTextEditorImpl.ts b/src/ide/vscode/VscodeTextEditorImpl.ts index 7d7d826c07..42cd1e34de 100644 --- a/src/ide/vscode/VscodeTextEditorImpl.ts +++ b/src/ide/vscode/VscodeTextEditorImpl.ts @@ -96,4 +96,8 @@ export class VscodeTextEditorImpl implements TextEditor { public openLink(location: Position | Range): Promise { return vscodeOpenLink(this.editor, toVscodePositionOrRange(location)); } + + public async clipboardPaste(): Promise { + await vscode.commands.executeCommand("editor.action.clipboardPasteAction"); + } } diff --git a/src/libs/common/types/TextEditor.ts b/src/libs/common/types/TextEditor.ts index 6de22293c8..8d34eafd88 100644 --- a/src/libs/common/types/TextEditor.ts +++ b/src/libs/common/types/TextEditor.ts @@ -115,4 +115,9 @@ export interface EditableTextEditor extends TextEditor { * @return True if a link was opened */ openLink(location: Position | Range): Promise; + + /** + * Paste clipboard content + */ + clipboardPaste(): Promise; } From 373db09042329c7255631020312e0be5537c470a Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Thu, 17 Nov 2022 10:09:13 +0100 Subject: [PATCH 05/43] updated fold action --- src/actions/Fold.ts | 34 ++++++++++---------------- src/ide/vscode/VscodeFold.ts | 29 ++++++++++++++++++++++ src/ide/vscode/VscodeTextEditorImpl.ts | 9 +++++++ src/libs/common/types/TextEditor.ts | 12 +++++++++ 4 files changed, 63 insertions(+), 21 deletions(-) create mode 100644 src/ide/vscode/VscodeFold.ts diff --git a/src/actions/Fold.ts b/src/actions/Fold.ts index e5ac7c93c1..957e0e1b20 100644 --- a/src/actions/Fold.ts +++ b/src/actions/Fold.ts @@ -1,4 +1,3 @@ -import { commands } from "vscode"; import ide from "../libs/cursorless-engine/singletons/ide.singleton"; import { Target } from "../typings/target.types"; import { Graph } from "../typings/Types"; @@ -6,17 +5,12 @@ import { createThatMark, ensureSingleEditor } from "../util/targetUtils"; import { Action, ActionReturnValue } from "./actions.types"; class FoldAction implements Action { - constructor(private command: string) { + constructor(private isFold: boolean) { this.run = this.run.bind(this); } async run([targets]: [Target[], Target[]]): Promise { - const originalEditor = ide().activeEditableTextEditor; - const editor = ensureSingleEditor(targets); - - if (originalEditor !== editor) { - await ide().getEditableTextEditor(editor).focus(); - } + const editor = ide().getEditableTextEditor(ensureSingleEditor(targets)); const singleLineTargets = targets.filter( (target) => target.contentRange.isSingleLine, @@ -24,6 +18,7 @@ class FoldAction implements Action { const multiLineTargets = targets.filter( (target) => !target.contentRange.isSingleLine, ); + // Don't mix multi and single line targets. // This is probably the result of an "every" command // and folding the single line targets will fold the parent as well @@ -31,17 +26,14 @@ class FoldAction implements Action { ? multiLineTargets : singleLineTargets; - await commands.executeCommand(this.command, { - levels: 1, - direction: "down", - selectionLines: selectedTargets.map( - (target) => target.contentRange.start.line, - ), - }); - - // If necessary focus back original editor - if (originalEditor != null && originalEditor !== editor) { - await originalEditor.focus(); + const selectionLines = selectedTargets.map( + (target) => target.contentRange.start.line, + ); + + if (this.isFold) { + await editor.fold(selectionLines); + } else { + await editor.unfold(selectionLines); } return { @@ -52,12 +44,12 @@ class FoldAction implements Action { export class Fold extends FoldAction { constructor(_graph: Graph) { - super("editor.fold"); + super(true); } } export class Unfold extends FoldAction { constructor(_graph: Graph) { - super("editor.unfold"); + super(false); } } diff --git a/src/ide/vscode/VscodeFold.ts b/src/ide/vscode/VscodeFold.ts new file mode 100644 index 0000000000..64b6cdc3aa --- /dev/null +++ b/src/ide/vscode/VscodeFold.ts @@ -0,0 +1,29 @@ +import * as vscode from "vscode"; +import VscodeIDE from "./VscodeIDE"; +import { VscodeTextEditorImpl } from "./VscodeTextEditorImpl"; + +export async function vscodeFold( + ide: VscodeIDE, + editor: VscodeTextEditorImpl, + lineNumbers: number[], + isFold: boolean, +): Promise { + const command = isFold ? "editor.fold" : "editor.unfold"; + const originalEditor = ide.activeEditableTextEditor; + + // Necessary to focus editor for fold command to work + if (originalEditor !== editor) { + await editor.focus(); + } + + await vscode.commands.executeCommand(command, { + levels: 1, + direction: "down", + selectionLines: lineNumbers, + }); + + // If necessary focus back original editor + if (originalEditor != null && originalEditor !== editor) { + await originalEditor.focus(); + } +} diff --git a/src/ide/vscode/VscodeTextEditorImpl.ts b/src/ide/vscode/VscodeTextEditorImpl.ts index 42cd1e34de..88d96d07c4 100644 --- a/src/ide/vscode/VscodeTextEditorImpl.ts +++ b/src/ide/vscode/VscodeTextEditorImpl.ts @@ -19,6 +19,7 @@ import { import * as vscode from "vscode"; import vscodeEdit from "./VscodeEdit"; import vscodeFocusEditor from "./VscodeFocusEditor"; +import { vscodeFold } from "./VscodeFold"; import VscodeIDE from "./VscodeIDE"; import vscodeOpenLink from "./VscodeOpenLink"; import { vscodeRevealLine } from "./VscodeRevealLine"; @@ -100,4 +101,12 @@ export class VscodeTextEditorImpl implements TextEditor { public async clipboardPaste(): Promise { await vscode.commands.executeCommand("editor.action.clipboardPasteAction"); } + + public async fold(lineNumbers: number[]): Promise { + return vscodeFold(this.ide, this, lineNumbers, true); + } + + public async unfold(lineNumbers: number[]): Promise { + return vscodeFold(this.ide, this, lineNumbers, false); + } } diff --git a/src/libs/common/types/TextEditor.ts b/src/libs/common/types/TextEditor.ts index 8d34eafd88..f6f22c37e2 100644 --- a/src/libs/common/types/TextEditor.ts +++ b/src/libs/common/types/TextEditor.ts @@ -120,4 +120,16 @@ export interface EditableTextEditor extends TextEditor { * Paste clipboard content */ clipboardPaste(): Promise; + + /** + * Fold lines + * @param lineNumbers Lines to fold + */ + fold(lineNumbers: number[]): Promise; + + /** + * Unfold lines + * @param lineNumbers Lines to Unfold + */ + unfold(lineNumbers: number[]): Promise; } From e6580b063e56dbe542ba5184cf3b2a5397dd3126 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Thu, 17 Nov 2022 10:19:55 +0100 Subject: [PATCH 06/43] Updated find in workspace action --- src/actions/Actions.ts | 4 ++-- src/actions/Find.ts | 8 +++----- src/ide/vscode/VscodeIDE.ts | 8 +++++++- src/libs/common/ide/fake/FakeIDE.ts | 9 ++++++++- src/libs/common/ide/spy/SpyIDE.ts | 6 +++++- src/libs/common/ide/types/ide.types.ts | 6 ++++++ 6 files changed, 31 insertions(+), 10 deletions(-) diff --git a/src/actions/Actions.ts b/src/actions/Actions.ts index c748ecbeca..6c37f00a93 100644 --- a/src/actions/Actions.ts +++ b/src/actions/Actions.ts @@ -20,7 +20,7 @@ import { Copy, Cut } from "./CutCopy"; import Deselect from "./Deselect"; import { EditNew, EditNewAfter, EditNewBefore } from "./EditNew"; import ExecuteCommand from "./ExecuteCommand"; -import { FindInFiles } from "./Find"; +import { FindInWorkspace } from "./Find"; import { Fold, Unfold } from "./Fold"; import FollowLink from "./FollowLink"; import GenerateSnippet from "./GenerateSnippet"; @@ -64,7 +64,7 @@ class Actions implements ActionRecord { editNewLineBefore = new EditNewBefore(this.graph); executeCommand = new ExecuteCommand(this.graph); extractVariable = new ExtractVariable(this.graph); - findInWorkspace = new FindInFiles(this.graph); + findInWorkspace = new FindInWorkspace(this.graph); foldRegion = new Fold(this.graph); followLink = new FollowLink(this.graph); generateSnippet = new GenerateSnippet(this.graph); diff --git a/src/actions/Find.ts b/src/actions/Find.ts index 9526504416..26440bb331 100644 --- a/src/actions/Find.ts +++ b/src/actions/Find.ts @@ -1,10 +1,10 @@ -import { commands } from "vscode"; +import ide from "../libs/cursorless-engine/singletons/ide.singleton"; import { Target } from "../typings/target.types"; import { Graph } from "../typings/Types"; import { ensureSingleTarget } from "../util/targetUtils"; import { Action, ActionReturnValue } from "./actions.types"; -export class FindInFiles implements Action { +export class FindInWorkspace implements Action { constructor(private graph: Graph) { this.run = this.run.bind(this); } @@ -17,9 +17,7 @@ export class FindInFiles implements Action { thatTargets, } = await this.graph.actions.getText.run([targets]); - await commands.executeCommand("workbench.action.findInFiles", { - query, - }); + await ide().findInWorkspace(query); return { thatTargets }; } diff --git a/src/ide/vscode/VscodeIDE.ts b/src/ide/vscode/VscodeIDE.ts index e9ac7a668d..46613cdeac 100644 --- a/src/ide/vscode/VscodeIDE.ts +++ b/src/ide/vscode/VscodeIDE.ts @@ -1,6 +1,6 @@ import type { EditableTextEditor, TextEditor } from "@cursorless/common"; import { pull } from "lodash"; -import type * as vscode from "vscode"; +import * as vscode from "vscode"; import { ExtensionContext, window, workspace, WorkspaceFolder } from "vscode"; import type { TextDocumentChangeEvent } from "../../libs/common/ide/types/Events"; import type { @@ -66,6 +66,12 @@ export default class VscodeIDE implements IDE { return editor as EditableTextEditor; } + public async findInWorkspace(query: string): Promise { + await vscode.commands.executeCommand("workbench.action.findInFiles", { + query, + }); + } + public onDidChangeTextDocument( listener: (event: TextDocumentChangeEvent) => void, ): Disposable { diff --git a/src/libs/common/ide/fake/FakeIDE.ts b/src/libs/common/ide/fake/FakeIDE.ts index 7af41a817c..17b7517332 100644 --- a/src/libs/common/ide/fake/FakeIDE.ts +++ b/src/libs/common/ide/fake/FakeIDE.ts @@ -55,6 +55,13 @@ export default class FakeIDE implements IDE { return this.original?.visibleTextEditors ?? []; } + public findInWorkspace(query: string): Promise { + if (this.original == null) { + throw Error("Original ide is missing"); + } + return this.original.findInWorkspace(query); + } + public getEditableTextEditor(editor: TextEditor): EditableTextEditor { if (this.original == null) { throw Error("Original ide is missing"); @@ -62,7 +69,7 @@ export default class FakeIDE implements IDE { return this.original.getEditableTextEditor(editor); } - onDidChangeTextDocument( + public onDidChangeTextDocument( listener: (event: TextDocumentChangeEvent) => void, ): Disposable { if (this.original == null) { diff --git a/src/libs/common/ide/spy/SpyIDE.ts b/src/libs/common/ide/spy/SpyIDE.ts index d1b96977f3..1fa39364b9 100644 --- a/src/libs/common/ide/spy/SpyIDE.ts +++ b/src/libs/common/ide/spy/SpyIDE.ts @@ -57,7 +57,11 @@ export default class SpyIDE implements IDE { return this.original.getEditableTextEditor(editor); } - onDidChangeTextDocument( + public findInWorkspace(query: string): Promise { + return this.original.findInWorkspace(query); + } + + public onDidChangeTextDocument( listener: (event: TextDocumentChangeEvent) => void, ): Disposable { return this.original.onDidChangeTextDocument(listener); diff --git a/src/libs/common/ide/types/ide.types.ts b/src/libs/common/ide/types/ide.types.ts index 8ed9ff02b2..46069fc6ce 100644 --- a/src/libs/common/ide/types/ide.types.ts +++ b/src/libs/common/ide/types/ide.types.ts @@ -69,6 +69,12 @@ export interface IDE { onDidChangeTextDocument( listener: (event: TextDocumentChangeEvent) => void, ): Disposable; + + /** + * Find occurrences of query string in all files in the workspace + * @param query The string query to search for + */ + findInWorkspace(query: string): Promise; } export interface WorkspaceFolder { From 476f926632ca5698bda28b497fe4678c437fbfd6 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Thu, 17 Nov 2022 10:36:42 +0100 Subject: [PATCH 07/43] Updated insert snippet action --- src/actions/InsertSnippet.ts | 9 +++------ src/actions/Paste.ts | 20 +++++++++----------- src/ide/vscode/VscodeTextEditorImpl.ts | 6 ++++++ src/libs/common/types/TextEditor.ts | 6 ++++++ 4 files changed, 24 insertions(+), 17 deletions(-) diff --git a/src/actions/InsertSnippet.ts b/src/actions/InsertSnippet.ts index a5ed9679d3..12d06bb077 100644 --- a/src/actions/InsertSnippet.ts +++ b/src/actions/InsertSnippet.ts @@ -1,9 +1,9 @@ -import { commands } from "vscode"; import textFormatters from "../core/textFormatters"; import { callFunctionAndUpdateSelectionInfos, getSelectionInfo, } from "../core/updateSelections/updateSelections"; +import ide from "../libs/cursorless-engine/singletons/ide.singleton"; import ModifyIfUntypedStage from "../processTargets/modifiers/ModifyIfUntypedStage"; import { Snippet, SnippetDefinition } from "../typings/snippet"; import { Target } from "../typings/target.types"; @@ -55,7 +55,7 @@ export default class InsertSnippet implements Action { ): Promise { const snippet = this.graph.snippets.getSnippetStrict(snippetName); - const editor = ensureSingleEditor(targets); + const editor = ide().getEditableTextEditor(ensureSingleEditor(targets)); const definition = findMatchingSnippetDefinitionStrict( targets, @@ -83,10 +83,7 @@ export default class InsertSnippet implements Action { // because the latter doesn't support special variables like CLIPBOARD const [updatedTargetSelections] = await callFunctionAndUpdateSelectionInfos( this.graph.rangeUpdater, - () => - commands.executeCommand("editor.action.insertSnippet", { - snippet: snippetString, - }), + () => editor.insertSnippet(snippetString), editor.document, [targetSelectionInfos], ); diff --git a/src/actions/Paste.ts b/src/actions/Paste.ts index dacd5ade51..d7338b1e10 100644 --- a/src/actions/Paste.ts +++ b/src/actions/Paste.ts @@ -13,9 +13,7 @@ export class Paste { constructor(private graph: Graph) {} async run([targets]: [Target[]]): Promise { - const targetEditor = ide().getEditableTextEditor( - ensureSingleEditor(targets), - ); + const editor = ide().getEditableTextEditor(ensureSingleEditor(targets)); const originalEditor = ide().activeEditableTextEditor; // First call editNew in order to insert delimiters if necessary and leave @@ -26,8 +24,8 @@ export class Paste { async () => { await this.graph.actions.editNew.run([targets]); }, - targetEditor.document, - [targetEditor.selections], + editor.document, + [editor.selections], ); // Then use VSCode paste command, using open ranges at the place where we @@ -35,14 +33,14 @@ export class Paste { const [updatedCursorSelections, updatedTargetSelections] = await callFunctionAndUpdateSelectionsWithBehavior( this.graph.rangeUpdater, - () => targetEditor.clipboardPaste(), - targetEditor.document, + () => editor.clipboardPaste(), + editor.document, [ { selections: originalCursorSelections, }, { - selections: targetEditor.selections, + selections: editor.selections, rangeBehavior: "OpenOpen", }, ], @@ -51,7 +49,7 @@ export class Paste { // Reset cursors on the editor where the edits took place. // NB: We don't focus the editor here because we want to focus the original // editor, not the one where the edits took place - setSelectionsWithoutFocusingEditor(targetEditor, updatedCursorSelections); + setSelectionsWithoutFocusingEditor(editor, updatedCursorSelections); // If necessary focus back original editor if (originalEditor != null && !originalEditor.isActive) { @@ -63,7 +61,7 @@ export class Paste { this.graph.editStyles.displayPendingEditDecorationsForRanges( updatedTargetSelections.map((selection) => ({ - editor: targetEditor, + editor: editor, range: selection, })), this.graph.editStyles.justAdded, @@ -72,7 +70,7 @@ export class Paste { return { thatSelections: updatedTargetSelections.map((selection) => ({ - editor: targetEditor, + editor: editor, selection, })), }; diff --git a/src/ide/vscode/VscodeTextEditorImpl.ts b/src/ide/vscode/VscodeTextEditorImpl.ts index 88d96d07c4..10689e0e6d 100644 --- a/src/ide/vscode/VscodeTextEditorImpl.ts +++ b/src/ide/vscode/VscodeTextEditorImpl.ts @@ -109,4 +109,10 @@ export class VscodeTextEditorImpl implements TextEditor { public async unfold(lineNumbers: number[]): Promise { return vscodeFold(this.ide, this, lineNumbers, false); } + + public async insertSnippet(snippet: string): Promise { + await vscode.commands.executeCommand("editor.action.insertSnippet", { + snippet, + }); + } } diff --git a/src/libs/common/types/TextEditor.ts b/src/libs/common/types/TextEditor.ts index f6f22c37e2..f71dc110e7 100644 --- a/src/libs/common/types/TextEditor.ts +++ b/src/libs/common/types/TextEditor.ts @@ -132,4 +132,10 @@ export interface EditableTextEditor extends TextEditor { * @param lineNumbers Lines to Unfold */ unfold(lineNumbers: number[]): Promise; + + /** + * Insert snippet + * @param snippet A snippet string + */ + insertSnippet(snippet: string): Promise; } From 6b1ddb0dc9b5b0c7099763d468a4121183c7b305 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Thu, 17 Nov 2022 10:39:16 +0100 Subject: [PATCH 08/43] Updated wrap with snippet action --- src/actions/WrapWithSnippet.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/actions/WrapWithSnippet.ts b/src/actions/WrapWithSnippet.ts index 6b06454170..e75993328a 100644 --- a/src/actions/WrapWithSnippet.ts +++ b/src/actions/WrapWithSnippet.ts @@ -1,5 +1,5 @@ -import { commands } from "vscode"; import { callFunctionAndUpdateSelections } from "../core/updateSelections/updateSelections"; +import ide from "../libs/cursorless-engine/singletons/ide.singleton"; import ModifyIfUntypedStage from "../processTargets/modifiers/ModifyIfUntypedStage"; import { Target } from "../typings/target.types"; import { Graph } from "../typings/Types"; @@ -53,7 +53,7 @@ export default class WrapWithSnippet implements Action { const snippet = this.graph.snippets.getSnippetStrict(snippetName); - const editor = ensureSingleEditor(targets); + const editor = ide().getEditableTextEditor(ensureSingleEditor(targets)); const definition = findMatchingSnippetDefinitionStrict( targets, @@ -79,10 +79,7 @@ export default class WrapWithSnippet implements Action { // because the latter doesn't support special variables like CLIPBOARD const [updatedTargetSelections] = await callFunctionAndUpdateSelections( this.graph.rangeUpdater, - () => - commands.executeCommand("editor.action.insertSnippet", { - snippet: snippetString, - }), + () => editor.insertSnippet(snippetString), editor.document, [targetSelections], ); From 405165064f72c2281eecc3a18c2318dbcef67f7a Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Thu, 17 Nov 2022 12:09:11 +0100 Subject: [PATCH 09/43] Updated the toggle break ponts action --- src/actions/ToggleBreakpoint.ts | 46 +++++++----------------- src/ide/vscode/VscodeTextEditorImpl.ts | 5 +++ src/ide/vscode/VscodeToggleBreakpoint.ts | 37 +++++++++++++++++++ src/libs/common/types/TextEditor.ts | 6 ++++ 4 files changed, 60 insertions(+), 34 deletions(-) create mode 100644 src/ide/vscode/VscodeToggleBreakpoint.ts diff --git a/src/actions/ToggleBreakpoint.ts b/src/actions/ToggleBreakpoint.ts index 58d66725fd..e089e7f1c1 100644 --- a/src/actions/ToggleBreakpoint.ts +++ b/src/actions/ToggleBreakpoint.ts @@ -1,20 +1,11 @@ -import { toVscodeRange } from "@cursorless/vscode-common"; -import * as vscode from "vscode"; -import { URI } from "vscode-uri"; +import { Range } from "@cursorless/common"; +import ide from "../libs/cursorless-engine/singletons/ide.singleton"; import { containingLineIfUntypedStage } from "../processTargets/modifiers/commonContainingScopeIfUntypedStages"; import { Target } from "../typings/target.types"; import { Graph } from "../typings/Types"; +import { runOnTargetsForEachEditor } from "../util/targetUtils"; import { Action, ActionReturnValue } from "./actions.types"; -function getBreakpoints(uri: URI, range: vscode.Range) { - return vscode.debug.breakpoints.filter( - (breakpoint) => - breakpoint instanceof vscode.SourceBreakpoint && - breakpoint.location.uri.toString() === uri.toString() && - breakpoint.location.range.intersection(range) != null, - ); -} - export default class ToggleBreakpoint implements Action { getFinalStages = () => [containingLineIfUntypedStage]; @@ -30,31 +21,18 @@ export default class ToggleBreakpoint implements Action { this.graph.editStyles.referenced, ); - const toAdd: vscode.Breakpoint[] = []; - const toRemove: vscode.Breakpoint[] = []; - - targets.forEach((target) => { - let range = toVscodeRange(target.contentRange); - // The action preference give us line content but line breakpoints are registered on character 0 - if (target.isLine) { - range = range.with(range.start.with(undefined, 0), undefined); - } - const uri = target.editor.document.uri; - const existing = getBreakpoints(uri, range); - if (existing.length > 0) { - toRemove.push(...existing); - } else { + await runOnTargetsForEachEditor(targets, async (editor, targets) => { + const ranges = targets.map((target) => { + const range = target.contentRange; + // The action preference give us line content but line breakpoints are registered on character 0 if (target.isLine) { - range = range.with(undefined, range.end.with(undefined, 0)); + return new Range(range.start.line, 0, range.end.line, 0); } - toAdd.push( - new vscode.SourceBreakpoint(new vscode.Location(uri, range)), - ); - } - }); + return range; + }); - vscode.debug.addBreakpoints(toAdd); - vscode.debug.removeBreakpoints(toRemove); + await ide().getEditableTextEditor(editor).toggleBreakpoint(ranges); + }); return { thatTargets: targets, diff --git a/src/ide/vscode/VscodeTextEditorImpl.ts b/src/ide/vscode/VscodeTextEditorImpl.ts index 10689e0e6d..54ac8f134c 100644 --- a/src/ide/vscode/VscodeTextEditorImpl.ts +++ b/src/ide/vscode/VscodeTextEditorImpl.ts @@ -24,6 +24,7 @@ import VscodeIDE from "./VscodeIDE"; import vscodeOpenLink from "./VscodeOpenLink"; import { vscodeRevealLine } from "./VscodeRevealLine"; import { VscodeTextDocumentImpl } from "./VscodeTextDocumentImpl"; +import { vscodeToggleBreakpoint } from "./VscodeToggleBreakpoint"; export class VscodeTextEditorImpl implements TextEditor { readonly document: TextDocument; @@ -115,4 +116,8 @@ export class VscodeTextEditorImpl implements TextEditor { snippet, }); } + + public async toggleBreakpoint(ranges: Range[]): Promise { + return vscodeToggleBreakpoint(this, ranges); + } } diff --git a/src/ide/vscode/VscodeToggleBreakpoint.ts b/src/ide/vscode/VscodeToggleBreakpoint.ts new file mode 100644 index 0000000000..86748528b9 --- /dev/null +++ b/src/ide/vscode/VscodeToggleBreakpoint.ts @@ -0,0 +1,37 @@ +import { Range } from "@cursorless/common"; +import { toVscodeRange } from "@cursorless/vscode-common"; +import * as vscode from "vscode"; +import { VscodeTextEditorImpl } from "./VscodeTextEditorImpl"; + +export async function vscodeToggleBreakpoint( + editor: VscodeTextEditorImpl, + ranges: Range[], +): Promise { + const uri = editor.document.uri; + const toAdd: vscode.Breakpoint[] = []; + const toRemove: vscode.Breakpoint[] = []; + + ranges.forEach((range) => { + const vscodeRange = toVscodeRange(range); + const existing = getBreakpoints(uri, vscodeRange); + if (existing.length > 0) { + toRemove.push(...existing); + } else { + toAdd.push( + new vscode.SourceBreakpoint(new vscode.Location(uri, vscodeRange)), + ); + } + }); + + vscode.debug.addBreakpoints(toAdd); + vscode.debug.removeBreakpoints(toRemove); +} + +function getBreakpoints(uri: vscode.Uri, range: vscode.Range) { + return vscode.debug.breakpoints.filter( + (breakpoint) => + breakpoint instanceof vscode.SourceBreakpoint && + breakpoint.location.uri.toString() === uri.toString() && + breakpoint.location.range.intersection(range) != null, + ); +} diff --git a/src/libs/common/types/TextEditor.ts b/src/libs/common/types/TextEditor.ts index f71dc110e7..0702916c2a 100644 --- a/src/libs/common/types/TextEditor.ts +++ b/src/libs/common/types/TextEditor.ts @@ -138,4 +138,10 @@ export interface EditableTextEditor extends TextEditor { * @param snippet A snippet string */ insertSnippet(snippet: string): Promise; + + /** + * Toggle breakpoint at ranges + * @param ranges A list of ranges + */ + toggleBreakpoint(ranges: Range[]): Promise; } From baab8ccbd35ca19e27748602cfb4c89b073a7ec8 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Thu, 17 Nov 2022 17:54:44 +0100 Subject: [PATCH 10/43] Use editor to insert snippet --- src/actions/GenerateSnippet/GenerateSnippet.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/actions/GenerateSnippet/GenerateSnippet.ts b/src/actions/GenerateSnippet/GenerateSnippet.ts index f7a9e70dbc..01d16d16f8 100644 --- a/src/actions/GenerateSnippet/GenerateSnippet.ts +++ b/src/actions/GenerateSnippet/GenerateSnippet.ts @@ -1,5 +1,5 @@ import { Range } from "@cursorless/common"; -import { commands, window } from "vscode"; +import { window } from "vscode"; import ide from "../../libs/cursorless-engine/singletons/ide.singleton"; import { Offsets } from "../../processTargets/modifiers/surroundingPair/types"; import isTesting from "../../testUtil/isTesting"; @@ -211,11 +211,11 @@ export default class GenerateSnippet implements Action { JSON.stringify(snippet, null, 2), ); + const editableEditor = ide().getEditableTextEditor(editor); + if (isTesting()) { // If we're testing, we just overwrite the current document - ide().getEditableTextEditor(editor).selections = [ - editor.document.range.toSelection(false), - ]; + editableEditor.selections = [editor.document.range.toSelection(false)]; } else { // Otherwise, we create and open a new document for the snippet in the // user snippets dir @@ -223,9 +223,7 @@ export default class GenerateSnippet implements Action { } // Insert the meta-snippet - await commands.executeCommand("editor.action.insertSnippet", { - snippet: snippetText, - }); + await editableEditor.insertSnippet(snippetText); return { thatSelections: targets.map(({ editor, contentSelection }) => ({ From 60321bcd838150e12d19718d688c007517fcd4aa Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Thu, 17 Nov 2022 18:13:22 +0100 Subject: [PATCH 11/43] Added open new text document to ide --- src/actions/GenerateSnippet/openNewSnippetFile.ts | 11 +++++------ src/ide/vscode/VscodeIDE.ts | 5 +++++ src/libs/common/ide/fake/FakeIDE.ts | 7 +++++++ src/libs/common/ide/spy/SpyIDE.ts | 4 ++++ src/libs/common/ide/types/ide.types.ts | 8 ++++++++ 5 files changed, 29 insertions(+), 6 deletions(-) diff --git a/src/actions/GenerateSnippet/openNewSnippetFile.ts b/src/actions/GenerateSnippet/openNewSnippetFile.ts index 7c1aba08dd..0965804967 100644 --- a/src/actions/GenerateSnippet/openNewSnippetFile.ts +++ b/src/actions/GenerateSnippet/openNewSnippetFile.ts @@ -1,6 +1,6 @@ import { open } from "fs/promises"; import { join } from "path"; -import { window, workspace } from "vscode"; +import ide from "../../libs/cursorless-engine/singletons/ide.singleton"; /** * Creates a new empty file in the users snippet directory and opens an editor @@ -8,9 +8,9 @@ import { window, workspace } from "vscode"; * @param snippetName The name of the snippet */ export async function openNewSnippetFile(snippetName: string) { - const userSnippetsDir = workspace - .getConfiguration("cursorless.experimental") - .get("snippetsDir"); + const userSnippetsDir = ide().configuration.getOwnConfiguration( + "experimental.snippetsDir", + ); if (!userSnippetsDir) { throw new Error("User snippets dir not configured."); @@ -18,8 +18,7 @@ export async function openNewSnippetFile(snippetName: string) { const path = join(userSnippetsDir, `${snippetName}.cursorless-snippets`); await touch(path); - const snippetDoc = await workspace.openTextDocument(path); - await window.showTextDocument(snippetDoc); + await ide().openTextDocument(path); } async function touch(path: string) { diff --git a/src/ide/vscode/VscodeIDE.ts b/src/ide/vscode/VscodeIDE.ts index 46613cdeac..946f74eca1 100644 --- a/src/ide/vscode/VscodeIDE.ts +++ b/src/ide/vscode/VscodeIDE.ts @@ -72,6 +72,11 @@ export default class VscodeIDE implements IDE { }); } + public async openTextDocument(path: string): Promise { + const textDocument = await workspace.openTextDocument(path); + await window.showTextDocument(textDocument); + } + public onDidChangeTextDocument( listener: (event: TextDocumentChangeEvent) => void, ): Disposable { diff --git a/src/libs/common/ide/fake/FakeIDE.ts b/src/libs/common/ide/fake/FakeIDE.ts index 17b7517332..abc101ff1f 100644 --- a/src/libs/common/ide/fake/FakeIDE.ts +++ b/src/libs/common/ide/fake/FakeIDE.ts @@ -78,6 +78,13 @@ export default class FakeIDE implements IDE { return this.original.onDidChangeTextDocument(listener); } + public async openTextDocument(path: string): Promise { + if (this.original == null) { + throw Error("Original ide is missing"); + } + return this.original.openTextDocument(path); + } + disposeOnExit(...disposables: Disposable[]): () => void { this.disposables.push(...disposables); diff --git a/src/libs/common/ide/spy/SpyIDE.ts b/src/libs/common/ide/spy/SpyIDE.ts index 1fa39364b9..218ef7448e 100644 --- a/src/libs/common/ide/spy/SpyIDE.ts +++ b/src/libs/common/ide/spy/SpyIDE.ts @@ -61,6 +61,10 @@ export default class SpyIDE implements IDE { return this.original.findInWorkspace(query); } + public async openTextDocument(path: string): Promise { + return this.original.openTextDocument(path); + } + public onDidChangeTextDocument( listener: (event: TextDocumentChangeEvent) => void, ): Disposable { diff --git a/src/libs/common/ide/types/ide.types.ts b/src/libs/common/ide/types/ide.types.ts index 46069fc6ce..8e0c2245d6 100644 --- a/src/libs/common/ide/types/ide.types.ts +++ b/src/libs/common/ide/types/ide.types.ts @@ -75,6 +75,14 @@ export interface IDE { * @param query The string query to search for */ findInWorkspace(query: string): Promise; + + /** + * Opens a document. + * + * @see {@link openTextDocument} + * @param path A path to a file on disk. + */ + openTextDocument(path: string): Promise; } export interface WorkspaceFolder { From 749290a40b2f9f7924e8069e10b816dfbe383f51 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Thu, 17 Nov 2022 18:40:38 +0100 Subject: [PATCH 12/43] Added show input box on ide --- .../GenerateSnippet/GenerateSnippet.ts | 3 +-- src/ide/vscode/VscodeIDE.ts | 12 ++++++++- src/libs/common/ide/fake/FakeIDE.ts | 27 ++++++++++++++----- src/libs/common/ide/spy/SpyIDE.ts | 12 ++++++++- src/libs/common/ide/types/ide.types.ts | 18 ++++++++++++- src/libs/common/index.ts | 1 + src/libs/common/types/InputBoxOptions.ts | 24 +++++++++++++++++ 7 files changed, 85 insertions(+), 12 deletions(-) create mode 100644 src/libs/common/types/InputBoxOptions.ts diff --git a/src/actions/GenerateSnippet/GenerateSnippet.ts b/src/actions/GenerateSnippet/GenerateSnippet.ts index 01d16d16f8..023874a220 100644 --- a/src/actions/GenerateSnippet/GenerateSnippet.ts +++ b/src/actions/GenerateSnippet/GenerateSnippet.ts @@ -1,5 +1,4 @@ import { Range } from "@cursorless/common"; -import { window } from "vscode"; import ide from "../../libs/cursorless-engine/singletons/ide.singleton"; import { Offsets } from "../../processTargets/modifiers/surroundingPair/types"; import isTesting from "../../testUtil/isTesting"; @@ -69,7 +68,7 @@ export default class GenerateSnippet implements Action { ); if (snippetName == null) { - snippetName = await window.showInputBox({ + snippetName = await ide().showInputBox({ prompt: "Name of snippet", placeHolder: "helloWorld", }); diff --git a/src/ide/vscode/VscodeIDE.ts b/src/ide/vscode/VscodeIDE.ts index 946f74eca1..dfe63a149f 100644 --- a/src/ide/vscode/VscodeIDE.ts +++ b/src/ide/vscode/VscodeIDE.ts @@ -1,4 +1,8 @@ -import type { EditableTextEditor, TextEditor } from "@cursorless/common"; +import type { + EditableTextEditor, + InputBoxOptions, + TextEditor, +} from "@cursorless/common"; import { pull } from "lodash"; import * as vscode from "vscode"; import { ExtensionContext, window, workspace, WorkspaceFolder } from "vscode"; @@ -77,6 +81,12 @@ export default class VscodeIDE implements IDE { await window.showTextDocument(textDocument); } + public async showInputBox( + options?: InputBoxOptions, + ): Promise { + return await vscode.window.showInputBox(options); + } + public onDidChangeTextDocument( listener: (event: TextDocumentChangeEvent) => void, ): Disposable { diff --git a/src/libs/common/ide/fake/FakeIDE.ts b/src/libs/common/ide/fake/FakeIDE.ts index abc101ff1f..04396da114 100644 --- a/src/libs/common/ide/fake/FakeIDE.ts +++ b/src/libs/common/ide/fake/FakeIDE.ts @@ -1,4 +1,8 @@ -import type { EditableTextEditor, TextEditor } from "@cursorless/common"; +import type { + EditableTextEditor, + InputBoxOptions, + TextEditor, +} from "@cursorless/common"; import { pull } from "lodash"; import type { TextDocumentChangeEvent } from "../types/Events"; import type { @@ -69,20 +73,29 @@ export default class FakeIDE implements IDE { return this.original.getEditableTextEditor(editor); } - public onDidChangeTextDocument( - listener: (event: TextDocumentChangeEvent) => void, - ): Disposable { + public async openTextDocument(path: string): Promise { if (this.original == null) { throw Error("Original ide is missing"); } - return this.original.onDidChangeTextDocument(listener); + return this.original.openTextDocument(path); } - public async openTextDocument(path: string): Promise { + public async showInputBox( + options?: InputBoxOptions, + ): Promise { if (this.original == null) { throw Error("Original ide is missing"); } - return this.original.openTextDocument(path); + return this.original.showInputBox(options); + } + + public onDidChangeTextDocument( + listener: (event: TextDocumentChangeEvent) => void, + ): Disposable { + if (this.original == null) { + throw Error("Original ide is missing"); + } + return this.original.onDidChangeTextDocument(listener); } disposeOnExit(...disposables: Disposable[]): () => void { diff --git a/src/libs/common/ide/spy/SpyIDE.ts b/src/libs/common/ide/spy/SpyIDE.ts index 218ef7448e..67cacae00e 100644 --- a/src/libs/common/ide/spy/SpyIDE.ts +++ b/src/libs/common/ide/spy/SpyIDE.ts @@ -1,4 +1,8 @@ -import { EditableTextEditor, TextEditor } from "@cursorless/common"; +import type { + EditableTextEditor, + InputBoxOptions, + TextEditor, +} from "@cursorless/common"; import { pickBy, values } from "lodash"; import type { Clipboard } from "../types/Clipboard"; import type { Configuration } from "../types/Configuration"; @@ -65,6 +69,12 @@ export default class SpyIDE implements IDE { return this.original.openTextDocument(path); } + public async showInputBox( + options?: InputBoxOptions, + ): Promise { + return this.original.showInputBox(options); + } + public onDidChangeTextDocument( listener: (event: TextDocumentChangeEvent) => void, ): Disposable { diff --git a/src/libs/common/ide/types/ide.types.ts b/src/libs/common/ide/types/ide.types.ts index 8e0c2245d6..3f470bb247 100644 --- a/src/libs/common/ide/types/ide.types.ts +++ b/src/libs/common/ide/types/ide.types.ts @@ -1,4 +1,8 @@ -import type { EditableTextEditor, TextEditor } from "@cursorless/common"; +import type { + EditableTextEditor, + InputBoxOptions, + TextEditor, +} from "@cursorless/common"; import { URI } from "vscode-uri"; import { Clipboard } from "./Clipboard"; import { Configuration } from "./Configuration"; @@ -83,6 +87,18 @@ export interface IDE { * @param path A path to a file on disk. */ openTextDocument(path: string): Promise; + + /** + * Opens an input box to ask the user for input. + * + * The returned value will be `undefined` if the input box was canceled (e.g. pressing ESC). Otherwise the + * returned value will be the string typed by the user or an empty string if the user did not type + * anything but dismissed the input box with OK. + * + * @param options Configures the behavior of the input box. + * @return A promise that resolves to a string the user provided or to `undefined` in case of dismissal. + */ + showInputBox(options?: InputBoxOptions): Promise; } export interface WorkspaceFolder { diff --git a/src/libs/common/index.ts b/src/libs/common/index.ts index 03d89f5c82..6aadb95dcd 100644 --- a/src/libs/common/index.ts +++ b/src/libs/common/index.ts @@ -18,6 +18,7 @@ export { Listener, Notifier } from "./util/Notifier"; export { TokenHatSplittingMode } from "./ide/types/Configuration"; export * from "./ide/types/ide.types"; export * from "./types/DecorationRangeBehavior"; +export * from "./types/InputBoxOptions"; export * from "./types/Position"; export * from "./types/Range"; export * from "./types/RevealLineAt"; diff --git a/src/libs/common/types/InputBoxOptions.ts b/src/libs/common/types/InputBoxOptions.ts new file mode 100644 index 0000000000..783b732290 --- /dev/null +++ b/src/libs/common/types/InputBoxOptions.ts @@ -0,0 +1,24 @@ +/** + * Options to configure the behavior of the input box UI. + */ +export interface InputBoxOptions { + /** + * An optional string that represents the title of the input box. + */ + title?: string; + + /** + * The value to prefill in the input box. + */ + value?: string; + + /** + * The text to display underneath the input box. + */ + prompt?: string; + + /** + * An optional string to show as placeholder in the input box to guide the user what to type. + */ + placeHolder?: string; +} From cbb1512fc0302d92adb0dcaa34da4116efffecb8 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Fri, 18 Nov 2022 09:47:54 +0100 Subject: [PATCH 13/43] Added capabilities --- src/libs/common/ide/fake/FakeIDE.ts | 8 ++++++++ src/libs/common/ide/spy/SpyIDE.ts | 7 ++++++- src/libs/common/ide/types/Capabilities.ts | 13 +++++++++++++ src/libs/common/ide/types/ide.types.ts | 6 ++++++ 4 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 src/libs/common/ide/types/Capabilities.ts diff --git a/src/libs/common/ide/fake/FakeIDE.ts b/src/libs/common/ide/fake/FakeIDE.ts index 04396da114..eeac82a9cc 100644 --- a/src/libs/common/ide/fake/FakeIDE.ts +++ b/src/libs/common/ide/fake/FakeIDE.ts @@ -1,4 +1,5 @@ import type { + Capabilities, EditableTextEditor, InputBoxOptions, TextEditor, @@ -59,6 +60,13 @@ export default class FakeIDE implements IDE { return this.original?.visibleTextEditors ?? []; } + get capabilities(): Capabilities { + if (this.original == null) { + throw Error("Original ide is missing"); + } + return this.original.capabilities; + } + public findInWorkspace(query: string): Promise { if (this.original == null) { throw Error("Original ide is missing"); diff --git a/src/libs/common/ide/spy/SpyIDE.ts b/src/libs/common/ide/spy/SpyIDE.ts index 67cacae00e..6e54aba85d 100644 --- a/src/libs/common/ide/spy/SpyIDE.ts +++ b/src/libs/common/ide/spy/SpyIDE.ts @@ -1,4 +1,5 @@ import type { + Capabilities, EditableTextEditor, InputBoxOptions, TextEditor, @@ -6,7 +7,7 @@ import type { import { pickBy, values } from "lodash"; import type { Clipboard } from "../types/Clipboard"; import type { Configuration } from "../types/Configuration"; -import type { TextDocumentChangeEvent } from "../types/Events"; +import { TextDocumentChangeEvent } from "../types/Events"; import type { Disposable, IDE, @@ -57,6 +58,10 @@ export default class SpyIDE implements IDE { return this.original.workspaceFolders; } + public get capabilities(): Capabilities { + return this.original.capabilities; + } + public getEditableTextEditor(editor: TextEditor): EditableTextEditor { return this.original.getEditableTextEditor(editor); } diff --git a/src/libs/common/ide/types/Capabilities.ts b/src/libs/common/ide/types/Capabilities.ts new file mode 100644 index 0000000000..a05ae3210d --- /dev/null +++ b/src/libs/common/ide/types/Capabilities.ts @@ -0,0 +1,13 @@ +export interface Capabilities { + commands: CapabilitiesCommands; +} + +export type CapabilitiesCommands = Partial< + Record +>; + +export type CapabilityCommandId = "toggleLineComment"; + +export interface CapabilitiesCommand { + acceptsLocation: boolean; +} diff --git a/src/libs/common/ide/types/ide.types.ts b/src/libs/common/ide/types/ide.types.ts index 3f470bb247..472b3a67d9 100644 --- a/src/libs/common/ide/types/ide.types.ts +++ b/src/libs/common/ide/types/ide.types.ts @@ -4,6 +4,7 @@ import type { TextEditor, } from "@cursorless/common"; import { URI } from "vscode-uri"; +import { Capabilities } from "./Capabilities"; import { Clipboard } from "./Clipboard"; import { Configuration } from "./Configuration"; import { TextDocumentChangeEvent } from "./Events"; @@ -59,6 +60,11 @@ export interface IDE { */ readonly visibleTextEditors: TextEditor[]; + /** + * The capabilities of the IDE + */ + readonly capabilities: Capabilities; + /** * Get an editable version of the text editor. * @param editor A editable text editor From 16fdf774b3678b1cafc66a3c207c13022f345a16 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Fri, 18 Nov 2022 09:48:10 +0100 Subject: [PATCH 14/43] Added vscode capabilities --- src/ide/vscode/VscodeIDE.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/ide/vscode/VscodeIDE.ts b/src/ide/vscode/VscodeIDE.ts index dfe63a149f..2adebd2bf6 100644 --- a/src/ide/vscode/VscodeIDE.ts +++ b/src/ide/vscode/VscodeIDE.ts @@ -1,4 +1,5 @@ import type { + Capabilities, EditableTextEditor, InputBoxOptions, TextEditor, @@ -28,6 +29,12 @@ export default class VscodeIDE implements IDE { clipboard: VscodeClipboard; private editorMap; + capabilities: Capabilities = { + commands: { + toggleLineComment: { acceptsLocation: false }, + }, + }; + constructor(private extensionContext: ExtensionContext) { this.configuration = new VscodeConfiguration(this); this.globalState = new VscodeGlobalState(extensionContext); From 9be062feaf954a40993722608ce038ceee563c12 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Fri, 18 Nov 2022 10:45:22 +0100 Subject: [PATCH 15/43] Started updating makeshift actions --- src/actions/CallbackAction.ts | 132 ++++++++++++++++++++++ src/actions/MakeshiftActions.ts | 59 +++++++++- src/ide/vscode/VscodeTextEditorImpl.ts | 4 + src/libs/common/ide/types/Capabilities.ts | 6 +- src/libs/common/ide/types/CommandId.ts | 1 + src/libs/common/index.ts | 2 + src/libs/common/types/TextEditor.ts | 10 +- 7 files changed, 206 insertions(+), 8 deletions(-) create mode 100644 src/actions/CallbackAction.ts create mode 100644 src/libs/common/ide/types/CommandId.ts diff --git a/src/actions/CallbackAction.ts b/src/actions/CallbackAction.ts new file mode 100644 index 0000000000..439b716868 --- /dev/null +++ b/src/actions/CallbackAction.ts @@ -0,0 +1,132 @@ +import { EditableTextEditor, Range, TextEditor } from "@cursorless/common"; +import { flatten } from "lodash"; +import { selectionToThatTarget } from "../core/commandRunner/selectionToThatTarget"; +import { callFunctionAndUpdateSelections } from "../core/updateSelections/updateSelections"; +import ide from "../libs/cursorless-engine/singletons/ide.singleton"; +import { Target } from "../typings/target.types"; +import { Graph } from "../typings/Types"; +import { + setSelectionsAndFocusEditor, + setSelectionsWithoutFocusingEditor, +} from "../util/setSelectionsAndFocusEditor"; +import { + ensureSingleEditor, + ensureSingleTarget, + runOnTargetsForEachEditor, +} from "../util/targetUtils"; +import { Action, ActionReturnValue } from "./actions.types"; + +interface Options { + callback: (editor: EditableTextEditor, ranges: Range[]) => Promise; + setSelection: boolean; + restoreSelection: boolean; + ensureSingleEditor: boolean; + ensureSingleTarget: boolean; + showDecorations: boolean; +} + +export default class CallbackAction implements Action { + constructor(private graph: Graph) { + this.run = this.run.bind(this); + } + + async run( + [targets]: [Target[]], + options: Options, + ): Promise { + if (options.showDecorations) { + await this.graph.editStyles.displayPendingEditDecorations( + targets, + this.graph.editStyles.referenced, + ); + } + + if (options.ensureSingleEditor) { + ensureSingleEditor(targets); + } + + if (options.ensureSingleTarget) { + ensureSingleTarget(targets); + } + + const originalEditor = ide().activeEditableTextEditor; + + const thatTargets = flatten( + await runOnTargetsForEachEditor(targets, (editor, targets) => { + return this.runForEditor(options, editor, targets); + }), + ); + + // If necessary focus back original editor + if ( + options.restoreSelection && + originalEditor != null && + !originalEditor.isActive + ) { + // NB: We just do one editor focus at the end, instead of using + // setSelectionsAndFocusEditor because the command might operate on + // multiple editors, so we just do one focus at the end. + await originalEditor.focus(); + } + + return { thatTargets }; + } + + private async runForEditor( + options: Options, + editor: TextEditor, + targets: Target[], + ): Promise { + return flatten( + await runOnTargetsForEachEditor(targets, async (editor, targets) => { + const editableEditor = ide().getEditableTextEditor(editor); + const originalSelections = editor.selections; + const originalEditorVersion = editor.document.version; + const targetSelections = targets.map( + (target) => target.contentSelection, + ); + + // For this callback/command to the work we have to have the correct editor focused + if (options.setSelection) { + await setSelectionsAndFocusEditor( + editableEditor, + targetSelections, + false, + ); + } + + const [updatedOriginalSelections, updatedTargetSelections] = + await callFunctionAndUpdateSelections( + this.graph.rangeUpdater, + () => options.callback(editableEditor, targetSelections), + editor.document, + [originalSelections, targetSelections], + ); + + // Reset original selections + if (options.restoreSelection) { + // NB: We don't focus the editor here because we'll do that at the + // very end. This code can run on multiple editors in the course of + // one command, so we want to avoid focusing the editor multiple + // times. + setSelectionsWithoutFocusingEditor( + editableEditor, + updatedOriginalSelections, + ); + } + + // If the document hasn't changed then we just return the original targets + // so that we preserve their rich types, but if it has changed then we + // just downgrade them to untyped targets + return editor.document.version === originalEditorVersion + ? targets + : updatedTargetSelections.map((selection) => + selectionToThatTarget({ + editor, + selection, + }), + ); + }), + ); + } +} diff --git a/src/actions/MakeshiftActions.ts b/src/actions/MakeshiftActions.ts index f831eb4ab3..1810bcf398 100644 --- a/src/actions/MakeshiftActions.ts +++ b/src/actions/MakeshiftActions.ts @@ -1,5 +1,14 @@ -import sleep from "../libs/common/util/sleep"; +import { + CommandId, + EditableTextEditor, + Range, + sleep, +} from "@cursorless/common"; +import ide from "../libs/cursorless-engine/singletons/ide.singleton"; import { Target } from "../typings/target.types"; +import { Graph } from "../typings/Types"; +import { ActionReturnValue } from "./actions.types"; +import CallbackAction from "./CallbackAction"; import CommandAction from "./CommandAction"; abstract class MakeshiftAction extends CommandAction { @@ -81,6 +90,50 @@ export class OutdentLines extends MakeshiftAction { command = "editor.action.outdentLines"; } -export class CommentLines extends MakeshiftAction { - command = "editor.action.commentLine"; +abstract class MakeshiftAction2 extends CallbackAction { + abstract command: CommandId; + ensureSingleEditor: boolean = false; + ensureSingleTarget: boolean = false; + restoreSelection: boolean = true; + showDecorations: boolean = true; + + constructor(graph: Graph) { + super(graph); + this.run = this.run.bind(this); + this.callback = this.callback.bind(this); + } + + async run(targets: [Target[]]): Promise { + const capabilities = ide().capabilities.commands[this.command]; + + if (capabilities == null) { + throw Error(`Missing command capabilities for '${this.command}'`); + } + + return super.run(targets, { + callback: this.callback, + setSelection: !capabilities.acceptsLocation, + ensureSingleEditor: this.ensureSingleEditor, + ensureSingleTarget: this.ensureSingleTarget, + restoreSelection: this.restoreSelection, + showDecorations: this.showDecorations, + }); + } + + private async callback( + editor: EditableTextEditor, + ranges: Range[], + ): Promise { + switch (this.command) { + case "toggleLineComment": + return editor.toggleLineComment(ranges); + default: + throw Error(`Unknown command '${this.command}'`); + } + } +} + +export class CommentLines extends MakeshiftAction2 { + command: CommandId = "toggleLineComment"; + ensureSingleEditor = true; } diff --git a/src/ide/vscode/VscodeTextEditorImpl.ts b/src/ide/vscode/VscodeTextEditorImpl.ts index 54ac8f134c..aedf4d79bb 100644 --- a/src/ide/vscode/VscodeTextEditorImpl.ts +++ b/src/ide/vscode/VscodeTextEditorImpl.ts @@ -120,4 +120,8 @@ export class VscodeTextEditorImpl implements TextEditor { public async toggleBreakpoint(ranges: Range[]): Promise { return vscodeToggleBreakpoint(this, ranges); } + + public async toggleLineComment(): Promise { + await vscode.commands.executeCommand("editor.action.commentLine"); + } } diff --git a/src/libs/common/ide/types/Capabilities.ts b/src/libs/common/ide/types/Capabilities.ts index a05ae3210d..0aeff7772a 100644 --- a/src/libs/common/ide/types/Capabilities.ts +++ b/src/libs/common/ide/types/Capabilities.ts @@ -1,13 +1,13 @@ +import { CommandId } from "./CommandId"; + export interface Capabilities { commands: CapabilitiesCommands; } export type CapabilitiesCommands = Partial< - Record + Record >; -export type CapabilityCommandId = "toggleLineComment"; - export interface CapabilitiesCommand { acceptsLocation: boolean; } diff --git a/src/libs/common/ide/types/CommandId.ts b/src/libs/common/ide/types/CommandId.ts new file mode 100644 index 0000000000..fdadf7d3a0 --- /dev/null +++ b/src/libs/common/ide/types/CommandId.ts @@ -0,0 +1 @@ +export type CommandId = "toggleLineComment"; diff --git a/src/libs/common/index.ts b/src/libs/common/index.ts index 6aadb95dcd..0dc5f91944 100644 --- a/src/libs/common/index.ts +++ b/src/libs/common/index.ts @@ -17,6 +17,8 @@ export { walkFilesSync } from "./util/walkSync"; export { Listener, Notifier } from "./util/Notifier"; export { TokenHatSplittingMode } from "./ide/types/Configuration"; export * from "./ide/types/ide.types"; +export * from "./ide/types/Capabilities"; +export * from "./ide/types/CommandId"; export * from "./types/DecorationRangeBehavior"; export * from "./types/InputBoxOptions"; export * from "./types/Position"; diff --git a/src/libs/common/types/TextEditor.ts b/src/libs/common/types/TextEditor.ts index 0702916c2a..baeb08f1fa 100644 --- a/src/libs/common/types/TextEditor.ts +++ b/src/libs/common/types/TextEditor.ts @@ -129,7 +129,7 @@ export interface EditableTextEditor extends TextEditor { /** * Unfold lines - * @param lineNumbers Lines to Unfold + * @param lineNumbers Lines to unfold */ unfold(lineNumbers: number[]): Promise; @@ -140,8 +140,14 @@ export interface EditableTextEditor extends TextEditor { insertSnippet(snippet: string): Promise; /** - * Toggle breakpoint at ranges + * Toggle breakpoints * @param ranges A list of ranges */ toggleBreakpoint(ranges: Range[]): Promise; + + /** + * Toggle line comments + * @param ranges A list of ranges + */ + toggleLineComment(ranges: Range[]): Promise; } From ab1f5b615f6d1dafbf3dd42d1e85c000cf16ab48 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Fri, 18 Nov 2022 15:13:23 +0100 Subject: [PATCH 16/43] Updated all makeshift actions --- src/actions/Actions.ts | 12 +- src/actions/CallbackAction.ts | 115 ++++++------- src/actions/MakeshiftActions.ts | 188 +++++++++++----------- src/ide/vscode/VscodeCapabilities.ts | 31 ++++ src/ide/vscode/VscodeIDE.ts | 21 ++- src/ide/vscode/VscodeTextEditorImpl.ts | 60 ++++++- src/libs/common/ide/types/Capabilities.ts | 2 +- src/libs/common/ide/types/CommandId.ts | 13 +- src/libs/common/ide/types/ide.types.ts | 8 +- src/libs/common/types/TextEditor.ts | 76 ++++++++- src/util/targetUtils.ts | 12 ++ 11 files changed, 354 insertions(+), 184 deletions(-) create mode 100644 src/ide/vscode/VscodeCapabilities.ts diff --git a/src/actions/Actions.ts b/src/actions/Actions.ts index 6c37f00a93..22e1de32d8 100644 --- a/src/actions/Actions.ts +++ b/src/actions/Actions.ts @@ -1,10 +1,10 @@ import { Graph } from "../typings/Types"; import { ActionRecord } from "./actions.types"; import { - CommentLines, + ToggleLineComment, ExtractVariable, - IndentLines, - OutdentLines, + IndentLine, + OutdentLine, Rename, RevealDefinition, RevealTypeDefinition, @@ -70,7 +70,7 @@ class Actions implements ActionRecord { generateSnippet = new GenerateSnippet(this.graph); getText = new GetText(this.graph); highlight = new Highlight(this.graph); - indentLine = new IndentLines(this.graph); + indentLine = new IndentLine(this.graph); insertCopyAfter = new InsertCopyAfter(this.graph); insertCopyBefore = new InsertCopyBefore(this.graph); insertEmptyLineAfter = new InsertEmptyLineAfter(this.graph); @@ -78,7 +78,7 @@ class Actions implements ActionRecord { insertEmptyLinesAround = new InsertEmptyLinesAround(this.graph); insertSnippet = new InsertSnippet(this.graph); moveToTarget = new Move(this.graph); - outdentLine = new OutdentLines(this.graph); + outdentLine = new OutdentLine(this.graph); pasteFromClipboard = new Paste(this.graph); randomizeTargets = new Random(this.graph); remove = new Remove(this.graph); @@ -102,7 +102,7 @@ class Actions implements ActionRecord { sortTargets = new Sort(this.graph); swapTargets = new Swap(this.graph); toggleLineBreakpoint = new ToggleBreakpoint(this.graph); - toggleLineComment = new CommentLines(this.graph); + toggleLineComment = new ToggleLineComment(this.graph); unfoldRegion = new Unfold(this.graph); wrapWithPairedDelimiter = new Wrap(this.graph); wrapWithSnippet = new WrapWithSnippet(this.graph); diff --git a/src/actions/CallbackAction.ts b/src/actions/CallbackAction.ts index 439b716868..93274d023f 100644 --- a/src/actions/CallbackAction.ts +++ b/src/actions/CallbackAction.ts @@ -1,4 +1,4 @@ -import { EditableTextEditor, Range, TextEditor } from "@cursorless/common"; +import { EditableTextEditor, TextEditor } from "@cursorless/common"; import { flatten } from "lodash"; import { selectionToThatTarget } from "../core/commandRunner/selectionToThatTarget"; import { callFunctionAndUpdateSelections } from "../core/updateSelections/updateSelections"; @@ -13,11 +13,12 @@ import { ensureSingleEditor, ensureSingleTarget, runOnTargetsForEachEditor, + runOnTargetsForEachEditorSequentially, } from "../util/targetUtils"; import { Action, ActionReturnValue } from "./actions.types"; interface Options { - callback: (editor: EditableTextEditor, ranges: Range[]) => Promise; + callback: (editor: EditableTextEditor, targets: Target[]) => Promise; setSelection: boolean; restoreSelection: boolean; ensureSingleEditor: boolean; @@ -51,14 +52,21 @@ export default class CallbackAction implements Action { const originalEditor = ide().activeEditableTextEditor; + // If we are relying on selections we have to wait for one editor to finish + // before moving the selection to the next + const runOnTargets = options.setSelection + ? runOnTargetsForEachEditorSequentially + : runOnTargetsForEachEditor; + const thatTargets = flatten( - await runOnTargetsForEachEditor(targets, (editor, targets) => { - return this.runForEditor(options, editor, targets); - }), + await runOnTargets(targets, (editor, targets) => + this.runForEditor(options, editor, targets), + ), ); // If necessary focus back original editor if ( + options.setSelection && options.restoreSelection && originalEditor != null && !originalEditor.isActive @@ -77,56 +85,53 @@ export default class CallbackAction implements Action { editor: TextEditor, targets: Target[], ): Promise { - return flatten( - await runOnTargetsForEachEditor(targets, async (editor, targets) => { - const editableEditor = ide().getEditableTextEditor(editor); - const originalSelections = editor.selections; - const originalEditorVersion = editor.document.version; - const targetSelections = targets.map( - (target) => target.contentSelection, - ); + console.log("runForEditor start"); + const editableEditor = ide().getEditableTextEditor(editor); + const originalSelections = editor.selections; + const originalEditorVersion = editor.document.version; + const targetSelections = targets.map((target) => target.contentSelection); - // For this callback/command to the work we have to have the correct editor focused - if (options.setSelection) { - await setSelectionsAndFocusEditor( - editableEditor, - targetSelections, - false, - ); - } - - const [updatedOriginalSelections, updatedTargetSelections] = - await callFunctionAndUpdateSelections( - this.graph.rangeUpdater, - () => options.callback(editableEditor, targetSelections), - editor.document, - [originalSelections, targetSelections], - ); - - // Reset original selections - if (options.restoreSelection) { - // NB: We don't focus the editor here because we'll do that at the - // very end. This code can run on multiple editors in the course of - // one command, so we want to avoid focusing the editor multiple - // times. - setSelectionsWithoutFocusingEditor( - editableEditor, - updatedOriginalSelections, - ); - } - - // If the document hasn't changed then we just return the original targets - // so that we preserve their rich types, but if it has changed then we - // just downgrade them to untyped targets - return editor.document.version === originalEditorVersion - ? targets - : updatedTargetSelections.map((selection) => - selectionToThatTarget({ - editor, - selection, - }), - ); - }), - ); + // For this callback/command to the work we have to have the correct editor focused + if (options.setSelection) { + await setSelectionsAndFocusEditor( + editableEditor, + targetSelections, + false, + ); + } + + const [updatedOriginalSelections, updatedTargetSelections] = + await callFunctionAndUpdateSelections( + this.graph.rangeUpdater, + () => options.callback(editableEditor, targets), + editor.document, + [originalSelections, targetSelections], + ); + + // Reset original selections + if (options.setSelection && options.restoreSelection) { + // NB: We don't focus the editor here because we'll do that at the + // very end. This code can run on multiple editors in the course of + // one command, so we want to avoid focusing the editor multiple + // times. + setSelectionsWithoutFocusingEditor( + editableEditor, + updatedOriginalSelections, + ); + } + + console.log("runForEditor return"); + + // If the document hasn't changed then we just return the original targets + // so that we preserve their rich types, but if it has changed then we + // just downgrade them to untyped targets + return editor.document.version === originalEditorVersion + ? targets + : updatedTargetSelections.map((selection) => + selectionToThatTarget({ + editor, + selection, + }), + ); } } diff --git a/src/actions/MakeshiftActions.ts b/src/actions/MakeshiftActions.ts index 1810bcf398..b98f70b169 100644 --- a/src/actions/MakeshiftActions.ts +++ b/src/actions/MakeshiftActions.ts @@ -1,96 +1,11 @@ -import { - CommandId, - EditableTextEditor, - Range, - sleep, -} from "@cursorless/common"; +import { CommandId, EditableTextEditor } from "@cursorless/common"; import ide from "../libs/cursorless-engine/singletons/ide.singleton"; import { Target } from "../typings/target.types"; import { Graph } from "../typings/Types"; import { ActionReturnValue } from "./actions.types"; import CallbackAction from "./CallbackAction"; -import CommandAction from "./CommandAction"; - -abstract class MakeshiftAction extends CommandAction { - abstract command: string; - restoreSelection?: boolean; - commandArg?: object; - ensureSingleTarget?: boolean; - postCommandSleepMs?: number; - - async run(targets: [Target[]]) { - const returnValue = await super.run(targets, { - command: this.command, - commandArgs: this.commandArg ? [this.commandArg] : [], - ensureSingleTarget: this.ensureSingleTarget ?? false, - restoreSelection: this.restoreSelection ?? true, - }); - if (this.postCommandSleepMs) { - await sleep(this.postCommandSleepMs); - } - return returnValue; - } -} - -export class RevealDefinition extends MakeshiftAction { - command = "editor.action.revealDefinition"; - ensureSingleTarget = true; - restoreSelection = false; -} - -export class RevealTypeDefinition extends MakeshiftAction { - command = "editor.action.goToTypeDefinition"; - ensureSingleTarget = true; - restoreSelection = false; -} - -export class ShowHover extends MakeshiftAction { - command = "editor.action.showHover"; - ensureSingleTarget = true; - restoreSelection = false; -} - -export class ShowDebugHover extends MakeshiftAction { - command = "editor.debug.action.showDebugHover"; - ensureSingleTarget = true; - restoreSelection = false; -} -export class ShowQuickFix extends MakeshiftAction { - command = "editor.action.quickFix"; - ensureSingleTarget = true; - postCommandSleepMs = 100; -} - -export class ShowReferences extends MakeshiftAction { - command = "references-view.find"; - ensureSingleTarget = true; -} - -export class Rename extends MakeshiftAction { - command = "editor.action.rename"; - ensureSingleTarget = true; -} - -export class ExtractVariable extends MakeshiftAction { - command = "editor.action.codeAction"; - commandArg = { - kind: "refactor.extract.constant", - preferred: true, - }; - ensureSingleTarget = true; - restoreSelection = false; -} - -export class IndentLines extends MakeshiftAction { - command = "editor.action.indentLines"; -} - -export class OutdentLines extends MakeshiftAction { - command = "editor.action.outdentLines"; -} - -abstract class MakeshiftAction2 extends CallbackAction { +abstract class MakeshiftAction extends CallbackAction { abstract command: CommandId; ensureSingleEditor: boolean = false; ensureSingleTarget: boolean = false; @@ -104,11 +19,7 @@ abstract class MakeshiftAction2 extends CallbackAction { } async run(targets: [Target[]]): Promise { - const capabilities = ide().capabilities.commands[this.command]; - - if (capabilities == null) { - throw Error(`Missing command capabilities for '${this.command}'`); - } + const capabilities = ide().capabilities.getCommand(this.command); return super.run(targets, { callback: this.callback, @@ -120,20 +31,101 @@ abstract class MakeshiftAction2 extends CallbackAction { }); } - private async callback( + private callback( editor: EditableTextEditor, - ranges: Range[], + targets: Target[], ): Promise { + const ranges = targets.map((t) => t.contentRange); + + // Multi target actions switch (this.command) { case "toggleLineComment": return editor.toggleLineComment(ranges); - default: - throw Error(`Unknown command '${this.command}'`); + case "indentLine": + return editor.indentLines(ranges); + case "outdentLine": + return editor.outdentLines(ranges); + } + + const range = ranges[0]; + + // Single target actions + switch (this.command) { + case "rename": + return editor.rename(range); + case "showReferences": + return editor.showReferences(range); + case "quickFix": + return editor.quickFix(range); + case "revealDefinition": + return editor.revealDefinition(range); + case "revealTypeDefinition": + return editor.revealTypeDefinition(range); + case "showHover": + return editor.showHover(range); + case "showDebugHover": + return editor.showDebugHover(range); + case "extractVariable": + return editor.extractVariable(range); } + + throw Error(`Unknown command '${this.command}'`); } } -export class CommentLines extends MakeshiftAction2 { +export class ToggleLineComment extends MakeshiftAction { command: CommandId = "toggleLineComment"; - ensureSingleEditor = true; +} + +export class IndentLine extends MakeshiftAction { + command: CommandId = "indentLine"; +} + +export class OutdentLine extends MakeshiftAction { + command: CommandId = "outdentLine"; +} + +export class Rename extends MakeshiftAction { + command: CommandId = "rename"; + ensureSingleTarget = true; +} + +export class ShowReferences extends MakeshiftAction { + command: CommandId = "showReferences"; + ensureSingleTarget = true; +} + +export class ShowQuickFix extends MakeshiftAction { + command: CommandId = "quickFix"; + ensureSingleTarget = true; +} + +export class RevealDefinition extends MakeshiftAction { + command: CommandId = "revealDefinition"; + ensureSingleTarget = true; + restoreSelection = false; +} + +export class RevealTypeDefinition extends MakeshiftAction { + command: CommandId = "revealTypeDefinition"; + ensureSingleTarget = true; + restoreSelection = false; +} + +export class ShowHover extends MakeshiftAction { + command: CommandId = "showHover"; + ensureSingleTarget = true; + restoreSelection = false; +} + +export class ShowDebugHover extends MakeshiftAction { + command: CommandId = "showDebugHover"; + ensureSingleTarget = true; + restoreSelection = false; +} + +export class ExtractVariable extends MakeshiftAction { + command: CommandId = "extractVariable"; + ensureSingleTarget = true; + restoreSelection = false; } diff --git a/src/ide/vscode/VscodeCapabilities.ts b/src/ide/vscode/VscodeCapabilities.ts new file mode 100644 index 0000000000..43a66d54ba --- /dev/null +++ b/src/ide/vscode/VscodeCapabilities.ts @@ -0,0 +1,31 @@ +import { CommandId } from "@cursorless/common"; +import { + Capabilities, + CapabilitiesCommand, + CapabilitiesCommands, +} from "../../libs/common/ide/types/Capabilities"; + +const capabilitiesCommands: CapabilitiesCommands = { + toggleLineComment: { acceptsLocation: false }, + indentLine: { acceptsLocation: false }, + outdentLine: { acceptsLocation: false }, + rename: { acceptsLocation: false }, + quickFix: { acceptsLocation: false }, + revealDefinition: { acceptsLocation: false }, + revealTypeDefinition: { acceptsLocation: false }, + showHover: { acceptsLocation: false }, + showDebugHover: { acceptsLocation: false }, + extractVariable: { acceptsLocation: false }, +}; + +export class VscodeCapabilities implements Capabilities { + public getCommand(commandId: CommandId): CapabilitiesCommand { + const capabilities = capabilitiesCommands[commandId]; + + if (capabilities == null) { + throw Error(`Missing command capabilities for '${commandId}'`); + } + + return capabilities; + } +} diff --git a/src/ide/vscode/VscodeIDE.ts b/src/ide/vscode/VscodeIDE.ts index 2adebd2bf6..89b08052d3 100644 --- a/src/ide/vscode/VscodeIDE.ts +++ b/src/ide/vscode/VscodeIDE.ts @@ -5,6 +5,7 @@ import type { TextEditor, } from "@cursorless/common"; import { pull } from "lodash"; +import { v4 as uuid } from "uuid"; import * as vscode from "vscode"; import { ExtensionContext, window, workspace, WorkspaceFolder } from "vscode"; import type { TextDocumentChangeEvent } from "../../libs/common/ide/types/Events"; @@ -13,33 +14,29 @@ import type { IDE, RunMode, } from "../../libs/common/ide/types/ide.types"; +import { VscodeCapabilities } from "./VscodeCapabilities"; import VscodeClipboard from "./VscodeClipboard"; import VscodeConfiguration from "./VscodeConfiguration"; -import { VscodeTextEditorImpl } from "./VscodeTextEditorImpl"; import { vscodeOnDidChangeTextDocument } from "./VscodeEvents"; import VscodeGlobalState from "./VscodeGlobalState"; import VscodeMessages from "./VscodeMessages"; import { vscodeRunMode } from "./VscodeRunMode"; -import { v4 as uuid } from "uuid"; +import { VscodeTextEditorImpl } from "./VscodeTextEditorImpl"; export default class VscodeIDE implements IDE { - configuration: VscodeConfiguration; - globalState: VscodeGlobalState; - messages: VscodeMessages; - clipboard: VscodeClipboard; + readonly configuration: VscodeConfiguration; + readonly globalState: VscodeGlobalState; + readonly messages: VscodeMessages; + readonly clipboard: VscodeClipboard; + readonly capabilities: Capabilities; private editorMap; - capabilities: Capabilities = { - commands: { - toggleLineComment: { acceptsLocation: false }, - }, - }; - constructor(private extensionContext: ExtensionContext) { this.configuration = new VscodeConfiguration(this); this.globalState = new VscodeGlobalState(extensionContext); this.messages = new VscodeMessages(); this.clipboard = new VscodeClipboard(); + this.capabilities = new VscodeCapabilities(); this.editorMap = new WeakMap(); } diff --git a/src/ide/vscode/VscodeTextEditorImpl.ts b/src/ide/vscode/VscodeTextEditorImpl.ts index aedf4d79bb..4f8a83aeca 100644 --- a/src/ide/vscode/VscodeTextEditorImpl.ts +++ b/src/ide/vscode/VscodeTextEditorImpl.ts @@ -1,8 +1,9 @@ -import type { +import { Position, Range, RevealLineAt, Selection, + sleep, TextDocument, TextEditor, TextEditorDecorationType, @@ -95,6 +96,13 @@ export class VscodeTextEditorImpl implements TextEditor { return vscodeFocusEditor(this.ide, this); } + public async executeCommand( + command: string, + ...rest: any[] + ): Promise { + return await vscode.commands.executeCommand(command, ...rest); + } + public openLink(location: Position | Range): Promise { return vscodeOpenLink(this.editor, toVscodePositionOrRange(location)); } @@ -103,11 +111,11 @@ export class VscodeTextEditorImpl implements TextEditor { await vscode.commands.executeCommand("editor.action.clipboardPasteAction"); } - public async fold(lineNumbers: number[]): Promise { + public fold(lineNumbers: number[]): Promise { return vscodeFold(this.ide, this, lineNumbers, true); } - public async unfold(lineNumbers: number[]): Promise { + public unfold(lineNumbers: number[]): Promise { return vscodeFold(this.ide, this, lineNumbers, false); } @@ -117,11 +125,55 @@ export class VscodeTextEditorImpl implements TextEditor { }); } - public async toggleBreakpoint(ranges: Range[]): Promise { + public toggleBreakpoint(ranges: Range[]): Promise { return vscodeToggleBreakpoint(this, ranges); } public async toggleLineComment(): Promise { await vscode.commands.executeCommand("editor.action.commentLine"); } + + public async indentLines(_ranges: Range[]): Promise { + await vscode.commands.executeCommand("editor.action.indentLines"); + } + + public async outdentLines(_ranges: Range[]): Promise { + await vscode.commands.executeCommand("editor.action.outdentLines"); + } + + public async rename(_range: Range): Promise { + await vscode.commands.executeCommand("editor.action.rename"); + } + + public async showReferences(_range: Range): Promise { + await vscode.commands.executeCommand("references-view.find"); + } + + public async quickFix(_range: Range): Promise { + await vscode.commands.executeCommand("editor.action.quickFix"); + await sleep(100); + } + + public async revealDefinition(_range: Range): Promise { + await vscode.commands.executeCommand("editor.action.revealDefinition"); + } + + public async revealTypeDefinition(_range: Range): Promise { + await vscode.commands.executeCommand("editor.action.goToTypeDefinition"); + } + + public async showHover(_range: Range): Promise { + await vscode.commands.executeCommand("editor.action.showHover"); + } + + public async showDebugHover(_range: Range): Promise { + await vscode.commands.executeCommand("editor.debug.action.showDebugHover"); + } + + public async extractVariable(_range: Range): Promise { + await vscode.commands.executeCommand("editor.action.codeAction", { + kind: "refactor.extract.constant", + preferred: true, + }); + } } diff --git a/src/libs/common/ide/types/Capabilities.ts b/src/libs/common/ide/types/Capabilities.ts index 0aeff7772a..7b61c766d7 100644 --- a/src/libs/common/ide/types/Capabilities.ts +++ b/src/libs/common/ide/types/Capabilities.ts @@ -1,7 +1,7 @@ import { CommandId } from "./CommandId"; export interface Capabilities { - commands: CapabilitiesCommands; + readonly getCommand: (commandId: CommandId) => CapabilitiesCommand; } export type CapabilitiesCommands = Partial< diff --git a/src/libs/common/ide/types/CommandId.ts b/src/libs/common/ide/types/CommandId.ts index fdadf7d3a0..812a44abc4 100644 --- a/src/libs/common/ide/types/CommandId.ts +++ b/src/libs/common/ide/types/CommandId.ts @@ -1 +1,12 @@ -export type CommandId = "toggleLineComment"; +export type CommandId = + | "toggleLineComment" + | "indentLine" + | "outdentLine" + | "rename" + | "showReferences" + | "quickFix" + | "revealDefinition" + | "revealTypeDefinition" + | "showHover" + | "showDebugHover" + | "extractVariable"; diff --git a/src/libs/common/ide/types/ide.types.ts b/src/libs/common/ide/types/ide.types.ts index 472b3a67d9..61569d703e 100644 --- a/src/libs/common/ide/types/ide.types.ts +++ b/src/libs/common/ide/types/ide.types.ts @@ -14,10 +14,10 @@ import { State } from "./State"; export type RunMode = "production" | "development" | "test"; export interface IDE { - configuration: Configuration; - messages: Messages; - globalState: State; - clipboard: Clipboard; + readonly configuration: Configuration; + readonly messages: Messages; + readonly globalState: State; + readonly clipboard: Clipboard; /** * Register disposables to be disposed of on IDE exit. diff --git a/src/libs/common/types/TextEditor.ts b/src/libs/common/types/TextEditor.ts index baeb08f1fa..8038274fde 100644 --- a/src/libs/common/types/TextEditor.ts +++ b/src/libs/common/types/TextEditor.ts @@ -62,7 +62,7 @@ export interface EditableTextEditor extends TextEditor { /** * Scroll to reveal the given range. * - * @param range A range. + * @param range A {@link Range range}. */ revealRange(range: Range): Promise; @@ -109,6 +109,16 @@ export interface EditableTextEditor extends TextEditor { options?: { undoStopBefore: boolean; undoStopAfter: boolean }, ): Promise; + /** + * Executes the command denoted by the given command identifier. + * + * @param command Identifier of the command to execute. + * @param rest Parameters passed to the command function. + * @return A promise that resolves to the returned value of the given command. `undefined` when + * the command handler function doesn't return anything. + */ + executeCommand(command: string, ...rest: any[]): Promise; + /** * Open link at location. * @param location Position or range @@ -141,13 +151,73 @@ export interface EditableTextEditor extends TextEditor { /** * Toggle breakpoints - * @param ranges A list of ranges + * @param ranges A list of {@link Range ranges} */ toggleBreakpoint(ranges: Range[]): Promise; /** * Toggle line comments - * @param ranges A list of ranges + * @param ranges A list of {@link Range ranges} */ toggleLineComment(ranges: Range[]): Promise; + + /** + * Indent lines + * @param ranges A list of {@link Range ranges} + */ + indentLines(ranges: Range[]): Promise; + + /** + * Outdent lines + * @param ranges A list of {@link Range ranges} + */ + outdentLines(ranges: Range[]): Promise; + + /** + * Rename + * @param range A {@link Range range} + */ + rename(range: Range): Promise; + + /** + * Show references + * @param range A {@link Range range} + */ + showReferences(range: Range): Promise; + + /** + * Show quick fixed dialogue + * @param range A {@link Range range} + */ + quickFix(range: Range): Promise; + + /** + * Reveal definition + * @param range A {@link Range range} + */ + revealDefinition(range: Range): Promise; + + /** + * Reveal type definition + * @param range A {@link Range range} + */ + revealTypeDefinition(range: Range): Promise; + + /** + * Show hover + * @param range A {@link Range range} + */ + showHover(range: Range): Promise; + + /** + * Show debug hover + * @param range A {@link Range range} + */ + showDebugHover(range: Range): Promise; + + /** + * Extract variable + * @param range A {@link Range range} + */ + extractVariable(range: Range): Promise; } diff --git a/src/util/targetUtils.ts b/src/util/targetUtils.ts index 496a5ad6a7..cc3dd71a55 100644 --- a/src/util/targetUtils.ts +++ b/src/util/targetUtils.ts @@ -45,6 +45,18 @@ export async function runOnTargetsForEachEditor( return runForEachEditor(targets, (target) => target.editor, func); } +export async function runOnTargetsForEachEditorSequentially( + targets: Target[], + func: (editor: TextEditor, targets: Target[]) => Promise, +): Promise { + const editorGroups = groupForEachEditor(targets, (target) => target.editor); + const result: T[] = []; + for (const [editor, targets] of editorGroups) { + result.push(await func(editor, targets)); + } + return result; +} + export function groupTargetsForEachEditor(targets: Target[]) { return groupForEachEditor(targets, (target) => target.editor); } From 1f9014c4a5c4d29d499842bd90060a13b41aec7f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 18 Nov 2022 14:15:34 +0000 Subject: [PATCH 17/43] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/libs/common/ide/types/ide.types.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libs/common/ide/types/ide.types.ts b/src/libs/common/ide/types/ide.types.ts index 61569d703e..44f625c084 100644 --- a/src/libs/common/ide/types/ide.types.ts +++ b/src/libs/common/ide/types/ide.types.ts @@ -15,9 +15,9 @@ export type RunMode = "production" | "development" | "test"; export interface IDE { readonly configuration: Configuration; - readonly messages: Messages; - readonly globalState: State; - readonly clipboard: Clipboard; + readonly messages: Messages; + readonly globalState: State; + readonly clipboard: Clipboard; /** * Register disposables to be disposed of on IDE exit. From 212ced4c752583ae7e446c8e75af0b4dffb14752 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Fri, 18 Nov 2022 15:49:26 +0100 Subject: [PATCH 18/43] Updated execute command action --- src/actions/CallbackAction.ts | 4 ++-- src/actions/ExecuteCommand.ts | 39 ++++++++++++++++++++++++++++------- 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/src/actions/CallbackAction.ts b/src/actions/CallbackAction.ts index 93274d023f..ebcb526fcb 100644 --- a/src/actions/CallbackAction.ts +++ b/src/actions/CallbackAction.ts @@ -19,10 +19,10 @@ import { Action, ActionReturnValue } from "./actions.types"; interface Options { callback: (editor: EditableTextEditor, targets: Target[]) => Promise; - setSelection: boolean; - restoreSelection: boolean; ensureSingleEditor: boolean; ensureSingleTarget: boolean; + setSelection: boolean; + restoreSelection: boolean; showDecorations: boolean; } diff --git a/src/actions/ExecuteCommand.ts b/src/actions/ExecuteCommand.ts index ab25c6b24f..faa44705a6 100644 --- a/src/actions/ExecuteCommand.ts +++ b/src/actions/ExecuteCommand.ts @@ -1,24 +1,47 @@ import { Target } from "../typings/target.types"; import { Graph } from "../typings/Types"; import { Action, ActionReturnValue } from "./actions.types"; -import CommandAction, { CommandOptions } from "./CommandAction"; +import CallbackAction from "./CallbackAction"; + +interface Options { + commandArgs?: any[]; + ensureSingleEditor?: boolean; + ensureSingleTarget?: boolean; + restoreSelection?: boolean; + showDecorations?: boolean; +} /** - * This is just a wrapper for {@link CommandAction} that allows the commands string without an options object. - * Should only be used by the API. Internally go directly to {@link CommandAction} + * This is just a wrapper for {@link CallbackAction} that allows the commands string without an options object. + * Should only be used by the API. Internally go directly to {@link CallbackAction} */ export default class ExecuteCommand implements Action { - private commandAction: CommandAction; - + private callbackAction: CallbackAction; constructor(graph: Graph) { - this.commandAction = new CommandAction(graph); + this.callbackAction = new CallbackAction(graph); + this.run = this.run.bind(this); } async run( targets: [Target[]], command: string, - args: CommandOptions = {}, + { + commandArgs, + ensureSingleEditor, + ensureSingleTarget, + restoreSelection, + showDecorations, + }: Options = {}, ): Promise { - return this.commandAction.run(targets, { ...args, command }); + const args = commandArgs ?? []; + + return this.callbackAction.run(targets, { + callback: (editor) => editor.executeCommand(command, ...args), + setSelection: true, + ensureSingleEditor: ensureSingleEditor ?? false, + ensureSingleTarget: ensureSingleTarget ?? true, + restoreSelection: restoreSelection ?? true, + showDecorations: showDecorations ?? true, + }); } } From baf1aa313bf538a46e1a9b7e310a096b45616883 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Fri, 18 Nov 2022 17:10:49 +0100 Subject: [PATCH 19/43] Made all editor action locations optional --- src/actions/Actions.ts | 11 +- src/actions/CallbackAction.ts | 12 +- src/actions/{CutCopy.ts => ClipboardCut.ts} | 13 +- src/actions/{Paste.ts => ClipboardPaste.ts} | 4 +- src/actions/CommandAction.ts | 150 -------------------- src/actions/ExecuteCommand.ts | 8 +- src/actions/MakeshiftActions.ts | 114 +++++++++------ src/actions/WrapWithSnippet.ts | 4 +- src/ide/vscode/VscodeCapabilities.ts | 1 + src/ide/vscode/VscodeFold.ts | 7 +- src/ide/vscode/VscodeInsertSnippets.ts | 17 +++ src/ide/vscode/VscodeOpenLink.ts | 14 +- src/ide/vscode/VscodeTextEditorImpl.ts | 58 ++++---- src/ide/vscode/VscodeToggleBreakpoint.ts | 5 +- src/libs/common/ide/types/CommandId.ts | 1 + src/libs/common/types/TextEditor.ts | 54 ++++--- 16 files changed, 190 insertions(+), 283 deletions(-) rename src/actions/{CutCopy.ts => ClipboardCut.ts} (83%) rename src/actions/{Paste.ts => ClipboardPaste.ts} (96%) delete mode 100644 src/actions/CommandAction.ts create mode 100644 src/ide/vscode/VscodeInsertSnippets.ts diff --git a/src/actions/Actions.ts b/src/actions/Actions.ts index 22e1de32d8..cd0a803e1b 100644 --- a/src/actions/Actions.ts +++ b/src/actions/Actions.ts @@ -12,11 +12,12 @@ import { ShowHover, ShowQuickFix, ShowReferences, + ClipboardCopy, } from "./MakeshiftActions"; import { Bring, Move, Swap } from "./BringMoveSwap"; import Call from "./Call"; import Clear from "./Clear"; -import { Copy, Cut } from "./CutCopy"; +import { ClipboardCut } from "./ClipboardCut"; import Deselect from "./Deselect"; import { EditNew, EditNewAfter, EditNewBefore } from "./EditNew"; import ExecuteCommand from "./ExecuteCommand"; @@ -36,7 +37,7 @@ import { InsertEmptyLinesAround, } from "./InsertEmptyLines"; import InsertSnippet from "./InsertSnippet"; -import { Paste } from "./Paste"; +import { ClipboardPaste } from "./ClipboardPaste"; import Remove from "./Remove"; import Replace from "./Replace"; import Rewrap from "./Rewrap"; @@ -56,8 +57,8 @@ class Actions implements ActionRecord { callAsFunction = new Call(this.graph); clearAndSetSelection = new Clear(this.graph); - copyToClipboard = new Copy(this.graph); - cutToClipboard = new Cut(this.graph); + copyToClipboard = new ClipboardCopy(this.graph); + cutToClipboard = new ClipboardCut(this.graph); deselect = new Deselect(this.graph); editNew = new EditNew(this.graph); editNewLineAfter = new EditNewAfter(this.graph); @@ -79,7 +80,7 @@ class Actions implements ActionRecord { insertSnippet = new InsertSnippet(this.graph); moveToTarget = new Move(this.graph); outdentLine = new OutdentLine(this.graph); - pasteFromClipboard = new Paste(this.graph); + pasteFromClipboard = new ClipboardPaste(this.graph); randomizeTargets = new Random(this.graph); remove = new Remove(this.graph); rename = new Rename(this.graph); diff --git a/src/actions/CallbackAction.ts b/src/actions/CallbackAction.ts index ebcb526fcb..b8772bac10 100644 --- a/src/actions/CallbackAction.ts +++ b/src/actions/CallbackAction.ts @@ -1,5 +1,6 @@ import { EditableTextEditor, TextEditor } from "@cursorless/common"; import { flatten } from "lodash"; +import { Options } from "semver"; import { selectionToThatTarget } from "../core/commandRunner/selectionToThatTarget"; import { callFunctionAndUpdateSelections } from "../core/updateSelections/updateSelections"; import ide from "../libs/cursorless-engine/singletons/ide.singleton"; @@ -17,7 +18,7 @@ import { } from "../util/targetUtils"; import { Action, ActionReturnValue } from "./actions.types"; -interface Options { +interface CallbackOptions extends Options { callback: (editor: EditableTextEditor, targets: Target[]) => Promise; ensureSingleEditor: boolean; ensureSingleTarget: boolean; @@ -26,14 +27,14 @@ interface Options { showDecorations: boolean; } -export default class CallbackAction implements Action { +export class CallbackAction implements Action { constructor(private graph: Graph) { this.run = this.run.bind(this); } async run( [targets]: [Target[]], - options: Options, + options: CallbackOptions, ): Promise { if (options.showDecorations) { await this.graph.editStyles.displayPendingEditDecorations( @@ -81,11 +82,10 @@ export default class CallbackAction implements Action { } private async runForEditor( - options: Options, + options: CallbackOptions, editor: TextEditor, targets: Target[], ): Promise { - console.log("runForEditor start"); const editableEditor = ide().getEditableTextEditor(editor); const originalSelections = editor.selections; const originalEditorVersion = editor.document.version; @@ -120,8 +120,6 @@ export default class CallbackAction implements Action { ); } - console.log("runForEditor return"); - // If the document hasn't changed then we just return the original targets // so that we preserve their rich types, but if it has changed then we // just downgrade them to untyped targets diff --git a/src/actions/CutCopy.ts b/src/actions/ClipboardCut.ts similarity index 83% rename from src/actions/CutCopy.ts rename to src/actions/ClipboardCut.ts index 654e5be05c..afed109d7c 100644 --- a/src/actions/CutCopy.ts +++ b/src/actions/ClipboardCut.ts @@ -3,9 +3,8 @@ import { Target } from "../typings/target.types"; import { Graph } from "../typings/Types"; import { getOutsideOverflow } from "../util/targetUtils"; import { Action, ActionReturnValue } from "./actions.types"; -import CommandAction from "./CommandAction"; -export class Cut implements Action { +export class ClipboardCut implements Action { constructor(private graph: Graph) { this.run = this.run.bind(this); } @@ -50,13 +49,3 @@ export class Cut implements Action { return { thatSelections: thatMark }; } } - -export class Copy extends CommandAction { - constructor(graph: Graph) { - super(graph, { - command: "editor.action.clipboardCopyAction", - ensureSingleEditor: true, - showDecorations: true, - }); - } -} diff --git a/src/actions/Paste.ts b/src/actions/ClipboardPaste.ts similarity index 96% rename from src/actions/Paste.ts rename to src/actions/ClipboardPaste.ts index d7338b1e10..cb18ee9759 100644 --- a/src/actions/Paste.ts +++ b/src/actions/ClipboardPaste.ts @@ -9,7 +9,7 @@ import { setSelectionsWithoutFocusingEditor } from "../util/setSelectionsAndFocu import { ensureSingleEditor } from "../util/targetUtils"; import { ActionReturnValue } from "./actions.types"; -export class Paste { +export class ClipboardPaste { constructor(private graph: Graph) {} async run([targets]: [Target[]]): Promise { @@ -17,7 +17,7 @@ export class Paste { const originalEditor = ide().activeEditableTextEditor; // First call editNew in order to insert delimiters if necessary and leave - // the cursor in the right position. Note that this action will focus the + // the cursor in the right position. Note that this action will focus the // editor containing the targets const [originalCursorSelections] = await callFunctionAndUpdateSelections( this.graph.rangeUpdater, diff --git a/src/actions/CommandAction.ts b/src/actions/CommandAction.ts deleted file mode 100644 index 6c49576df1..0000000000 --- a/src/actions/CommandAction.ts +++ /dev/null @@ -1,150 +0,0 @@ -import { flatten } from "lodash"; -import { commands } from "vscode"; -import { selectionToThatTarget } from "../core/commandRunner/selectionToThatTarget"; -import { callFunctionAndUpdateSelections } from "../core/updateSelections/updateSelections"; -import ide from "../libs/cursorless-engine/singletons/ide.singleton"; -import { Target } from "../typings/target.types"; -import { Graph } from "../typings/Types"; -import { - setSelectionsAndFocusEditor, - setSelectionsWithoutFocusingEditor, -} from "../util/setSelectionsAndFocusEditor"; -import { - ensureSingleEditor, - ensureSingleTarget, - runOnTargetsForEachEditor, -} from "../util/targetUtils"; -import { Action, ActionReturnValue } from "./actions.types"; - -export interface CommandOptions { - command?: string; - commandArgs?: any[]; - restoreSelection?: boolean; - ensureSingleEditor?: boolean; - ensureSingleTarget?: boolean; - showDecorations?: boolean; -} - -const defaultOptions: CommandOptions = { - commandArgs: [], - restoreSelection: true, - ensureSingleEditor: false, - ensureSingleTarget: false, - showDecorations: false, -}; - -export default class CommandAction implements Action { - constructor(private graph: Graph, private options: CommandOptions = {}) { - this.run = this.run.bind(this); - } - - private async runCommandAndUpdateSelections( - targets: Target[], - options: Required, - ): Promise { - return flatten( - await runOnTargetsForEachEditor(targets, async (editor, targets) => { - const originalSelections = editor.selections; - const originalEditorVersion = editor.document.version; - - const targetSelections = targets.map( - (target) => target.contentSelection, - ); - - // For command to the work we have to have the correct editor focused - await setSelectionsAndFocusEditor( - ide().getEditableTextEditor(editor), - targetSelections, - false, - ); - - const [updatedOriginalSelections, updatedTargetSelections] = - await callFunctionAndUpdateSelections( - this.graph.rangeUpdater, - () => - commands.executeCommand(options.command, ...options.commandArgs), - editor.document, - [originalSelections, targetSelections], - ); - - // Reset original selections - if (options.restoreSelection) { - // NB: We don't focus the editor here because we'll do that at the - // very end. This code can run on multiple editors in the course of - // one command, so we want to avoid focusing the editor multiple - // times. - setSelectionsWithoutFocusingEditor( - ide().getEditableTextEditor(editor), - updatedOriginalSelections, - ); - } - - // If the document hasn't changed then we just return the original targets - // so that we preserve their rich types, but if it has changed then we - // just downgrade them to untyped targets - return editor.document.version === originalEditorVersion - ? targets - : updatedTargetSelections.map((selection) => - selectionToThatTarget({ - editor, - selection, - }), - ); - }), - ); - } - - async run( - [targets]: [Target[]], - options: CommandOptions = {}, - ): Promise { - const partialOptions = Object.assign( - {}, - defaultOptions, - this.options, - options, - ); - - if (partialOptions.command == null) { - throw Error("Command id must be specified"); - } - - const actualOptions = partialOptions as Required; - - if (actualOptions.showDecorations) { - await this.graph.editStyles.displayPendingEditDecorations( - targets, - this.graph.editStyles.referenced, - ); - } - - if (actualOptions.ensureSingleEditor) { - ensureSingleEditor(targets); - } - - if (actualOptions.ensureSingleTarget) { - ensureSingleTarget(targets); - } - - const originalEditor = ide().activeEditableTextEditor; - - const thatTargets = await this.runCommandAndUpdateSelections( - targets, - actualOptions, - ); - - // If necessary focus back original editor - if ( - actualOptions.restoreSelection && - originalEditor != null && - !originalEditor.isActive - ) { - // NB: We just do one editor focus at the end, instead of using - // setSelectionsAndFocusEditor because the command might operate on - // multiple editors, so we just do one focus at the end. - await originalEditor.focus(); - } - - return { thatTargets }; - } -} diff --git a/src/actions/ExecuteCommand.ts b/src/actions/ExecuteCommand.ts index faa44705a6..f518ad4a5a 100644 --- a/src/actions/ExecuteCommand.ts +++ b/src/actions/ExecuteCommand.ts @@ -1,7 +1,8 @@ +import { EditableTextEditor } from "../libs/common/types/TextEditor"; import { Target } from "../typings/target.types"; import { Graph } from "../typings/Types"; import { Action, ActionReturnValue } from "./actions.types"; -import CallbackAction from "./CallbackAction"; +import { CallbackAction } from "./CallbackAction"; interface Options { commandArgs?: any[]; @@ -36,10 +37,11 @@ export default class ExecuteCommand implements Action { const args = commandArgs ?? []; return this.callbackAction.run(targets, { - callback: (editor) => editor.executeCommand(command, ...args), + callback: (editor: EditableTextEditor) => + editor.executeCommand(command, ...args), setSelection: true, ensureSingleEditor: ensureSingleEditor ?? false, - ensureSingleTarget: ensureSingleTarget ?? true, + ensureSingleTarget: ensureSingleTarget ?? false, restoreSelection: restoreSelection ?? true, showDecorations: showDecorations ?? true, }); diff --git a/src/actions/MakeshiftActions.ts b/src/actions/MakeshiftActions.ts index b98f70b169..ab13d01b3f 100644 --- a/src/actions/MakeshiftActions.ts +++ b/src/actions/MakeshiftActions.ts @@ -1,9 +1,17 @@ -import { CommandId, EditableTextEditor } from "@cursorless/common"; +import { + CapabilitiesCommand, + CommandId, + EditableTextEditor, +} from "@cursorless/common"; import ide from "../libs/cursorless-engine/singletons/ide.singleton"; import { Target } from "../typings/target.types"; import { Graph } from "../typings/Types"; import { ActionReturnValue } from "./actions.types"; -import CallbackAction from "./CallbackAction"; +import { CallbackAction } from "./CallbackAction"; + +interface Options { + showDecorations?: boolean; +} abstract class MakeshiftAction extends CallbackAction { abstract command: CommandId; @@ -15,62 +23,29 @@ abstract class MakeshiftAction extends CallbackAction { constructor(graph: Graph) { super(graph); this.run = this.run.bind(this); - this.callback = this.callback.bind(this); } - async run(targets: [Target[]]): Promise { + async run( + targets: [Target[]], + { showDecorations }: Options = {}, + ): Promise { const capabilities = ide().capabilities.getCommand(this.command); return super.run(targets, { - callback: this.callback, + callback: (editor, targets) => + callback(editor, targets, this.command, capabilities), setSelection: !capabilities.acceptsLocation, ensureSingleEditor: this.ensureSingleEditor, ensureSingleTarget: this.ensureSingleTarget, restoreSelection: this.restoreSelection, - showDecorations: this.showDecorations, + showDecorations: showDecorations ?? this.showDecorations, }); } +} - private callback( - editor: EditableTextEditor, - targets: Target[], - ): Promise { - const ranges = targets.map((t) => t.contentRange); - - // Multi target actions - switch (this.command) { - case "toggleLineComment": - return editor.toggleLineComment(ranges); - case "indentLine": - return editor.indentLines(ranges); - case "outdentLine": - return editor.outdentLines(ranges); - } - - const range = ranges[0]; - - // Single target actions - switch (this.command) { - case "rename": - return editor.rename(range); - case "showReferences": - return editor.showReferences(range); - case "quickFix": - return editor.quickFix(range); - case "revealDefinition": - return editor.revealDefinition(range); - case "revealTypeDefinition": - return editor.revealTypeDefinition(range); - case "showHover": - return editor.showHover(range); - case "showDebugHover": - return editor.showDebugHover(range); - case "extractVariable": - return editor.extractVariable(range); - } - - throw Error(`Unknown command '${this.command}'`); - } +export class ClipboardCopy extends MakeshiftAction { + command: CommandId = "clipboardCopy"; + ensureSingleEditor = true; } export class ToggleLineComment extends MakeshiftAction { @@ -129,3 +104,50 @@ export class ExtractVariable extends MakeshiftAction { ensureSingleTarget = true; restoreSelection = false; } + +function callback( + editor: EditableTextEditor, + targets: Target[], + command: CommandId, + capabilities: CapabilitiesCommand, +): Promise { + const ranges = capabilities.acceptsLocation + ? targets.map((t) => t.contentRange) + : undefined; + + // Multi target actions + switch (command) { + case "toggleLineComment": + return editor.toggleLineComment(ranges); + case "indentLine": + return editor.indentLines(ranges); + case "outdentLine": + return editor.outdentLines(ranges); + case "clipboardCopy": + return editor.clipboardCopy(ranges); + } + + const range = ranges?.[0]; + + // Single target actions + switch (command) { + case "rename": + return editor.rename(range); + case "showReferences": + return editor.showReferences(range); + case "quickFix": + return editor.quickFix(range); + case "revealDefinition": + return editor.revealDefinition(range); + case "revealTypeDefinition": + return editor.revealTypeDefinition(range); + case "showHover": + return editor.showHover(range); + case "showDebugHover": + return editor.showDebugHover(range); + case "extractVariable": + return editor.extractVariable(range); + } + + throw Error(`Unknown command '${command}'`); +} diff --git a/src/actions/WrapWithSnippet.ts b/src/actions/WrapWithSnippet.ts index e75993328a..6df59d3fb8 100644 --- a/src/actions/WrapWithSnippet.ts +++ b/src/actions/WrapWithSnippet.ts @@ -73,13 +73,11 @@ export default class WrapWithSnippet implements Action { const targetSelections = targets.map((target) => target.contentSelection); - await this.graph.actions.setSelection.run([targets]); - // NB: We used the command "editor.action.insertSnippet" instead of calling editor.insertSnippet // because the latter doesn't support special variables like CLIPBOARD const [updatedTargetSelections] = await callFunctionAndUpdateSelections( this.graph.rangeUpdater, - () => editor.insertSnippet(snippetString), + () => editor.insertSnippet(snippetString, targetSelections), editor.document, [targetSelections], ); diff --git a/src/ide/vscode/VscodeCapabilities.ts b/src/ide/vscode/VscodeCapabilities.ts index 43a66d54ba..ccfcf9e62a 100644 --- a/src/ide/vscode/VscodeCapabilities.ts +++ b/src/ide/vscode/VscodeCapabilities.ts @@ -6,6 +6,7 @@ import { } from "../../libs/common/ide/types/Capabilities"; const capabilitiesCommands: CapabilitiesCommands = { + clipboardCopy: { acceptsLocation: false }, toggleLineComment: { acceptsLocation: false }, indentLine: { acceptsLocation: false }, outdentLine: { acceptsLocation: false }, diff --git a/src/ide/vscode/VscodeFold.ts b/src/ide/vscode/VscodeFold.ts index 64b6cdc3aa..d4d57f0bed 100644 --- a/src/ide/vscode/VscodeFold.ts +++ b/src/ide/vscode/VscodeFold.ts @@ -5,7 +5,7 @@ import { VscodeTextEditorImpl } from "./VscodeTextEditorImpl"; export async function vscodeFold( ide: VscodeIDE, editor: VscodeTextEditorImpl, - lineNumbers: number[], + lineNumbers: number[] | undefined, isFold: boolean, ): Promise { const command = isFold ? "editor.fold" : "editor.unfold"; @@ -16,10 +16,13 @@ export async function vscodeFold( await editor.focus(); } + const selectionLines = + lineNumbers ?? editor.selections.map((selection) => selection.start.line); + await vscode.commands.executeCommand(command, { levels: 1, direction: "down", - selectionLines: lineNumbers, + selectionLines, }); // If necessary focus back original editor diff --git a/src/ide/vscode/VscodeInsertSnippets.ts b/src/ide/vscode/VscodeInsertSnippets.ts new file mode 100644 index 0000000000..1583f72a3a --- /dev/null +++ b/src/ide/vscode/VscodeInsertSnippets.ts @@ -0,0 +1,17 @@ +import * as vscode from "vscode"; +import { Range } from "@cursorless/common"; +import { VscodeTextEditorImpl } from "./VscodeTextEditorImpl"; + +export async function vscodeInsertSnippet( + editor: VscodeTextEditorImpl, + snippet: string, + ranges: Range[] | undefined, +): Promise { + if (ranges != null) { + editor.selections = ranges.map((range) => range.toSelection(false)); + } + + await vscode.commands.executeCommand("editor.action.insertSnippet", { + snippet, + }); +} diff --git a/src/ide/vscode/VscodeOpenLink.ts b/src/ide/vscode/VscodeOpenLink.ts index 2c6e6996e2..1b65ba197e 100644 --- a/src/ide/vscode/VscodeOpenLink.ts +++ b/src/ide/vscode/VscodeOpenLink.ts @@ -2,10 +2,13 @@ import * as vscode from "vscode"; export default async function vscodeOpenLink( editor: vscode.TextEditor, - location: vscode.Position | vscode.Range, + location: vscode.Position | vscode.Range | undefined, ): Promise { const links = await getLinksForEditor(editor); - const filteredLinks = links.filter((link) => link.range.contains(location)); + const actualLocation = location ?? getSelection(editor); + const filteredLinks = links.filter((link) => + link.range.contains(actualLocation), + ); if (filteredLinks.length > 1) { throw Error("Multiple links found at location"); @@ -47,3 +50,10 @@ async function openUri(uri: vscode.Uri) { throw Error(`Unknown uri scheme '${uri.scheme}'`); } } + +function getSelection(editor: vscode.TextEditor) { + if (editor.selections.length > 1) { + throw Error("Can't open links for multiple selections"); + } + return editor.selection; +} diff --git a/src/ide/vscode/VscodeTextEditorImpl.ts b/src/ide/vscode/VscodeTextEditorImpl.ts index 4f8a83aeca..dff81bab7a 100644 --- a/src/ide/vscode/VscodeTextEditorImpl.ts +++ b/src/ide/vscode/VscodeTextEditorImpl.ts @@ -22,6 +22,7 @@ import vscodeEdit from "./VscodeEdit"; import vscodeFocusEditor from "./VscodeFocusEditor"; import { vscodeFold } from "./VscodeFold"; import VscodeIDE from "./VscodeIDE"; +import { vscodeInsertSnippet } from "./VscodeInsertSnippets"; import vscodeOpenLink from "./VscodeOpenLink"; import { vscodeRevealLine } from "./VscodeRevealLine"; import { VscodeTextDocumentImpl } from "./VscodeTextDocumentImpl"; @@ -103,74 +104,79 @@ export class VscodeTextEditorImpl implements TextEditor { return await vscode.commands.executeCommand(command, ...rest); } - public openLink(location: Position | Range): Promise { - return vscodeOpenLink(this.editor, toVscodePositionOrRange(location)); + public openLink(location?: Position | Range): Promise { + return vscodeOpenLink( + this.editor, + location != null ? toVscodePositionOrRange(location) : undefined, + ); } - public async clipboardPaste(): Promise { - await vscode.commands.executeCommand("editor.action.clipboardPasteAction"); - } - - public fold(lineNumbers: number[]): Promise { + public fold(lineNumbers?: number[]): Promise { return vscodeFold(this.ide, this, lineNumbers, true); } - public unfold(lineNumbers: number[]): Promise { + public unfold(lineNumbers?: number[]): Promise { return vscodeFold(this.ide, this, lineNumbers, false); } - public async insertSnippet(snippet: string): Promise { - await vscode.commands.executeCommand("editor.action.insertSnippet", { - snippet, - }); - } - - public toggleBreakpoint(ranges: Range[]): Promise { + public toggleBreakpoint(ranges?: Range[]): Promise { return vscodeToggleBreakpoint(this, ranges); } - public async toggleLineComment(): Promise { + public async toggleLineComment(_ranges?: Range[]): Promise { await vscode.commands.executeCommand("editor.action.commentLine"); } - public async indentLines(_ranges: Range[]): Promise { + public async clipboardCopy(_ranges?: Range[]): Promise { + await vscode.commands.executeCommand("editor.action.clipboardCopyAction"); + } + + public async clipboardPaste(_ranges?: Range[]): Promise { + await vscode.commands.executeCommand("editor.action.clipboardPasteAction"); + } + + public async indentLines(_ranges?: Range[]): Promise { await vscode.commands.executeCommand("editor.action.indentLines"); } - public async outdentLines(_ranges: Range[]): Promise { + public async outdentLines(_ranges?: Range[]): Promise { await vscode.commands.executeCommand("editor.action.outdentLines"); } - public async rename(_range: Range): Promise { + public insertSnippet(snippet: string, ranges?: Range[]): Promise { + return vscodeInsertSnippet(this, snippet, ranges); + } + + public async rename(_range?: Range): Promise { await vscode.commands.executeCommand("editor.action.rename"); } - public async showReferences(_range: Range): Promise { + public async showReferences(_range?: Range): Promise { await vscode.commands.executeCommand("references-view.find"); } - public async quickFix(_range: Range): Promise { + public async quickFix(_range?: Range): Promise { await vscode.commands.executeCommand("editor.action.quickFix"); await sleep(100); } - public async revealDefinition(_range: Range): Promise { + public async revealDefinition(_range?: Range): Promise { await vscode.commands.executeCommand("editor.action.revealDefinition"); } - public async revealTypeDefinition(_range: Range): Promise { + public async revealTypeDefinition(_range?: Range): Promise { await vscode.commands.executeCommand("editor.action.goToTypeDefinition"); } - public async showHover(_range: Range): Promise { + public async showHover(_range?: Range): Promise { await vscode.commands.executeCommand("editor.action.showHover"); } - public async showDebugHover(_range: Range): Promise { + public async showDebugHover(_range?: Range): Promise { await vscode.commands.executeCommand("editor.debug.action.showDebugHover"); } - public async extractVariable(_range: Range): Promise { + public async extractVariable(_range?: Range): Promise { await vscode.commands.executeCommand("editor.action.codeAction", { kind: "refactor.extract.constant", preferred: true, diff --git a/src/ide/vscode/VscodeToggleBreakpoint.ts b/src/ide/vscode/VscodeToggleBreakpoint.ts index 86748528b9..336f436ca3 100644 --- a/src/ide/vscode/VscodeToggleBreakpoint.ts +++ b/src/ide/vscode/VscodeToggleBreakpoint.ts @@ -5,13 +5,14 @@ import { VscodeTextEditorImpl } from "./VscodeTextEditorImpl"; export async function vscodeToggleBreakpoint( editor: VscodeTextEditorImpl, - ranges: Range[], + ranges: Range[] | undefined, ): Promise { const uri = editor.document.uri; const toAdd: vscode.Breakpoint[] = []; const toRemove: vscode.Breakpoint[] = []; + const actualRanges = ranges ?? editor.selections; - ranges.forEach((range) => { + actualRanges.forEach((range) => { const vscodeRange = toVscodeRange(range); const existing = getBreakpoints(uri, vscodeRange); if (existing.length > 0) { diff --git a/src/libs/common/ide/types/CommandId.ts b/src/libs/common/ide/types/CommandId.ts index 812a44abc4..c863069c6b 100644 --- a/src/libs/common/ide/types/CommandId.ts +++ b/src/libs/common/ide/types/CommandId.ts @@ -1,4 +1,5 @@ export type CommandId = + | "clipboardCopy" | "toggleLineComment" | "indentLine" | "outdentLine" diff --git a/src/libs/common/types/TextEditor.ts b/src/libs/common/types/TextEditor.ts index 8038274fde..814ba219e4 100644 --- a/src/libs/common/types/TextEditor.ts +++ b/src/libs/common/types/TextEditor.ts @@ -124,100 +124,108 @@ export interface EditableTextEditor extends TextEditor { * @param location Position or range * @return True if a link was opened */ - openLink(location: Position | Range): Promise; - - /** - * Paste clipboard content - */ - clipboardPaste(): Promise; + openLink(location?: Position | Range): Promise; /** * Fold lines * @param lineNumbers Lines to fold */ - fold(lineNumbers: number[]): Promise; + fold(lineNumbers?: number[]): Promise; /** * Unfold lines * @param lineNumbers Lines to unfold */ - unfold(lineNumbers: number[]): Promise; + unfold(lineNumbers?: number[]): Promise; /** - * Insert snippet - * @param snippet A snippet string + * Copy to clipboard + * @param ranges A list of {@link Range ranges} + */ + clipboardCopy(ranges?: Range[]): Promise; + + /** + * Paste clipboard content + * @param ranges A list of {@link Range ranges} */ - insertSnippet(snippet: string): Promise; + clipboardPaste(ranges?: Range[]): Promise; /** * Toggle breakpoints * @param ranges A list of {@link Range ranges} */ - toggleBreakpoint(ranges: Range[]): Promise; + toggleBreakpoint(ranges?: Range[]): Promise; /** * Toggle line comments * @param ranges A list of {@link Range ranges} */ - toggleLineComment(ranges: Range[]): Promise; + toggleLineComment(ranges?: Range[]): Promise; /** * Indent lines * @param ranges A list of {@link Range ranges} */ - indentLines(ranges: Range[]): Promise; + indentLines(ranges?: Range[]): Promise; /** * Outdent lines * @param ranges A list of {@link Range ranges} */ - outdentLines(ranges: Range[]): Promise; + outdentLines(ranges?: Range[]): Promise; + + /** + * Insert snippet + * @param snippet A snippet string + * @param ranges A list of {@link Range ranges} + */ + insertSnippet(snippet: string, ranges?: Range[]): Promise; /** * Rename * @param range A {@link Range range} */ - rename(range: Range): Promise; + rename(range?: Range): Promise; /** * Show references * @param range A {@link Range range} */ - showReferences(range: Range): Promise; + showReferences(range?: Range): Promise; /** * Show quick fixed dialogue * @param range A {@link Range range} */ - quickFix(range: Range): Promise; + quickFix(range?: Range): Promise; /** * Reveal definition * @param range A {@link Range range} */ - revealDefinition(range: Range): Promise; + revealDefinition(range?: Range): Promise; /** * Reveal type definition * @param range A {@link Range range} */ - revealTypeDefinition(range: Range): Promise; + revealTypeDefinition(range?: Range): Promise; /** * Show hover * @param range A {@link Range range} */ - showHover(range: Range): Promise; + showHover(range?: Range): Promise; /** * Show debug hover * @param range A {@link Range range} */ - showDebugHover(range: Range): Promise; + showDebugHover(range?: Range): Promise; /** * Extract variable * @param range A {@link Range range} */ - extractVariable(range: Range): Promise; + extractVariable(range?: Range): Promise; } From a440aa235536422ed6abb80325ed6a4c9d1bc055 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Sun, 20 Nov 2022 10:24:55 +0100 Subject: [PATCH 20/43] Updated edit new using insert new line --- src/actions/EditNew/EditNew.ts | 12 ++++--- src/actions/EditNew/EditNew.types.ts | 19 ----------- ...ts.ts => runEditInsertLineAfterTargets.ts} | 32 ++++++------------- ...ts.ts => runEditNewNotebookCellTargets.ts} | 2 +- src/actions/EditNew/runEditTargets.ts | 12 +++---- src/ide/vscode/VscodeTextEditorImpl.ts | 7 ++++ src/libs/common/types/TextEditor.ts | 6 ++++ src/processTargets/targets/BaseTarget.ts | 4 +-- src/processTargets/targets/PositionTarget.ts | 6 ++-- src/typings/target.types.ts | 10 +----- 10 files changed, 40 insertions(+), 70 deletions(-) rename src/actions/EditNew/{runCommandTargets.ts => runEditInsertLineAfterTargets.ts} (66%) rename src/actions/EditNew/{runNotebookCellTargets.ts => runEditNewNotebookCellTargets.ts} (95%) diff --git a/src/actions/EditNew/EditNew.ts b/src/actions/EditNew/EditNew.ts index 5658e5f6c9..1945318569 100644 --- a/src/actions/EditNew/EditNew.ts +++ b/src/actions/EditNew/EditNew.ts @@ -8,9 +8,9 @@ import { setSelectionsAndFocusEditor } from "../../util/setSelectionsAndFocusEdi import { createThatMark, ensureSingleEditor } from "../../util/targetUtils"; import { Action, ActionReturnValue } from "../actions.types"; import { State } from "./EditNew.types"; -import { runCommandTargets } from "./runCommandTargets"; +import { runEditInsertLineAfterTargets } from "./runEditInsertLineAfterTargets"; import { runEditTargets } from "./runEditTargets"; -import { runNotebookCellTargets } from "./runNotebookCellTargets"; +import { runEditNewNotebookCellTargets } from "./runEditNewNotebookCellTargets"; export class EditNew implements Action { getFinalStages(): ModifierStage[] { @@ -26,7 +26,7 @@ export class EditNew implements Action { // It is not possible to "pour" a notebook cell and something else, // because each notebook cell is its own editor, and you can't have // cursors in multiple editors. - return runNotebookCellTargets(this.graph, targets); + return runEditNewNotebookCellTargets(this.graph, targets); } const editableEditor = ide().getEditableTextEditor( @@ -43,7 +43,11 @@ export class EditNew implements Action { cursorRanges: new Array(targets.length).fill(undefined) as undefined[], }; - state = await runCommandTargets(this.graph, editableEditor, state); + state = await runEditInsertLineAfterTargets( + this.graph, + editableEditor, + state, + ); state = await runEditTargets(this.graph, editableEditor, state); const newSelections = state.targets.map((target, index) => diff --git a/src/actions/EditNew/EditNew.types.ts b/src/actions/EditNew/EditNew.types.ts index 27ca438d36..80abf0c6ff 100644 --- a/src/actions/EditNew/EditNew.types.ts +++ b/src/actions/EditNew/EditNew.types.ts @@ -1,25 +1,6 @@ import type { Range } from "@cursorless/common"; import type { Target } from "../../typings/target.types"; -/** - * Internal type to be used for storing a reference to a target that will use a - * VSCode command to insert a new target, eg `editor.action.insertLineAfter`. - */ -export interface CommandTarget { - target: Target; - - /** - * The original index of this target in the original list of targets passed - * to the action - */ - index: number; - - /** - * The VSCode command to run, eg `editor.action.insertLineAfter` - */ - command: string; -} - /** * Internal type to be used for storing a reference to a target that will use an * edit action to insert a new target diff --git a/src/actions/EditNew/runCommandTargets.ts b/src/actions/EditNew/runEditInsertLineAfterTargets.ts similarity index 66% rename from src/actions/EditNew/runCommandTargets.ts rename to src/actions/EditNew/runEditInsertLineAfterTargets.ts index 437a613615..044b62d5ee 100644 --- a/src/actions/EditNew/runCommandTargets.ts +++ b/src/actions/EditNew/runEditInsertLineAfterTargets.ts @@ -1,8 +1,7 @@ import { EditableTextEditor } from "@cursorless/common"; -import { commands } from "vscode"; import { callFunctionAndUpdateRanges } from "../../core/updateSelections/updateSelections"; import { Graph } from "../../typings/Types"; -import { CommandTarget, State } from "./EditNew.types"; +import { EditTarget, State } from "./EditNew.types"; /** * Handle targets that will use a VSCode command to insert a new target, eg @@ -15,38 +14,33 @@ import { CommandTarget, State } from "./EditNew.types"; * @param state The state object tracking cursors, thatMark, etc * @returns An updated `state` object */ -export async function runCommandTargets( +export async function runEditInsertLineAfterTargets( graph: Graph, editor: EditableTextEditor, state: State, ): Promise { - const commandTargets: CommandTarget[] = state.targets + const targets: EditTarget[] = state.targets .map((target, index) => { const context = target.getEditNewContext(); - if (context.type === "command") { + if (context === "insertLineAfter") { return { target, index, - command: context.command, }; } }) - .filter((target): target is CommandTarget => !!target); + .filter((target): target is EditTarget => !!target); - if (commandTargets.length === 0) { + if (targets.length === 0) { return state; } - const command = ensureSingleCommand(commandTargets); - - await graph.actions.setSelection.run([ - commandTargets.map(({ target }) => target), - ]); + const contentRanges = targets.map(({ target }) => target.contentRange); const [updatedTargetRanges, updatedThatRanges] = await callFunctionAndUpdateRanges( graph.rangeUpdater, - () => commands.executeCommand(command), + () => editor.insertLineAfter(contentRanges), editor.document, [state.targets.map(({ contentRange }) => contentRange), state.thatRanges], ); @@ -55,7 +49,7 @@ export async function runCommandTargets( // up after running the command. We add it to the state so that any // potential edit targets can update them after we return from this function. const cursorRanges = [...state.cursorRanges]; - commandTargets.forEach((commandTarget, index) => { + targets.forEach((commandTarget, index) => { cursorRanges[commandTarget.index] = editor.selections[index]; }); @@ -67,11 +61,3 @@ export async function runCommandTargets( cursorRanges, }; } - -function ensureSingleCommand(targets: CommandTarget[]) { - const commands = targets.map((target) => target.command); - if (new Set(commands).size > 1) { - throw new Error("Can't run different commands at once"); - } - return commands[0]; -} diff --git a/src/actions/EditNew/runNotebookCellTargets.ts b/src/actions/EditNew/runEditNewNotebookCellTargets.ts similarity index 95% rename from src/actions/EditNew/runNotebookCellTargets.ts rename to src/actions/EditNew/runEditNewNotebookCellTargets.ts index 5ad65b6888..28bf955d96 100644 --- a/src/actions/EditNew/runNotebookCellTargets.ts +++ b/src/actions/EditNew/runEditNewNotebookCellTargets.ts @@ -6,7 +6,7 @@ import { Graph } from "../../typings/Types"; import { createThatMark, ensureSingleTarget } from "../../util/targetUtils"; import { ActionReturnValue } from "../actions.types"; -export async function runNotebookCellTargets( +export async function runEditNewNotebookCellTargets( graph: Graph, targets: Target[], ): Promise { diff --git a/src/actions/EditNew/runEditTargets.ts b/src/actions/EditNew/runEditTargets.ts index a67b6225bf..bec062d8a7 100644 --- a/src/actions/EditNew/runEditTargets.ts +++ b/src/actions/EditNew/runEditTargets.ts @@ -22,10 +22,10 @@ export async function runEditTargets( editor: EditableTextEditor, state: State, ): Promise { - const editTargets: EditTarget[] = state.targets + const targets: EditTarget[] = state.targets .map((target, index) => { const context = target.getEditNewContext(); - if (context.type === "edit") { + if (context === "edit") { return { target, index, @@ -34,13 +34,11 @@ export async function runEditTargets( }) .filter((target): target is EditTarget => !!target); - if (editTargets.length === 0) { + if (targets.length === 0) { return state; } - const edits = editTargets.map((target) => - target.target.constructChangeEdit(""), - ); + const edits = targets.map((target) => target.target.constructChangeEdit("")); const thatSelections = { selections: state.thatRanges.map((r) => r.toSelection(false)), @@ -84,7 +82,7 @@ export async function runEditTargets( }); // Add cursor positions for our edit targets. - editTargets.forEach((delimiterTarget, index) => { + targets.forEach((delimiterTarget, index) => { const edit = edits[index]; const range = edit.updateRange(updatedEditSelections[index]); updatedCursorRanges[delimiterTarget.index] = range; diff --git a/src/ide/vscode/VscodeTextEditorImpl.ts b/src/ide/vscode/VscodeTextEditorImpl.ts index dff81bab7a..f94e595481 100644 --- a/src/ide/vscode/VscodeTextEditorImpl.ts +++ b/src/ide/vscode/VscodeTextEditorImpl.ts @@ -143,6 +143,13 @@ export class VscodeTextEditorImpl implements TextEditor { await vscode.commands.executeCommand("editor.action.outdentLines"); } + public async insertLineAfter(ranges?: Range[]): Promise { + if (ranges != null) { + this.selections = ranges.map((range) => range.toSelection(false)); + } + await vscode.commands.executeCommand("editor.action.insertLineAfter"); + } + public insertSnippet(snippet: string, ranges?: Range[]): Promise { return vscodeInsertSnippet(this, snippet, ranges); } diff --git a/src/libs/common/types/TextEditor.ts b/src/libs/common/types/TextEditor.ts index 814ba219e4..899ede41e8 100644 --- a/src/libs/common/types/TextEditor.ts +++ b/src/libs/common/types/TextEditor.ts @@ -174,6 +174,12 @@ export interface EditableTextEditor extends TextEditor { */ outdentLines(ranges?: Range[]): Promise; + /** + * Insert line after + * @param ranges A list of {@link Range ranges} + */ + insertLineAfter(ranges?: Range[]): Promise; + /** * Insert snippet * @param snippet A snippet string diff --git a/src/processTargets/targets/BaseTarget.ts b/src/processTargets/targets/BaseTarget.ts index e23b4c7a50..98bb9d7f5f 100644 --- a/src/processTargets/targets/BaseTarget.ts +++ b/src/processTargets/targets/BaseTarget.ts @@ -83,9 +83,7 @@ export default abstract class BaseTarget implements Target { } getEditNewContext(): EditNewContext { - return { - type: "edit", - }; + return "edit"; } getRemovalHighlightRange(): Range | undefined { diff --git a/src/processTargets/targets/PositionTarget.ts b/src/processTargets/targets/PositionTarget.ts index bd246301a0..512c374d4f 100644 --- a/src/processTargets/targets/PositionTarget.ts +++ b/src/processTargets/targets/PositionTarget.ts @@ -43,12 +43,10 @@ export default class PositionTarget extends BaseTarget { getEditNewContext(): EditNewContext { if (this.insertionDelimiter === "\n" && this.position === "after") { - return { type: "command", command: "editor.action.insertLineAfter" }; + return "insertLineAfter"; } - return { - type: "edit", - }; + return "edit"; } constructChangeEdit(text: string): EditWithRangeUpdater { diff --git a/src/typings/target.types.ts b/src/typings/target.types.ts index 1feb2a4243..4c991c92a2 100644 --- a/src/typings/target.types.ts +++ b/src/typings/target.types.ts @@ -20,15 +20,7 @@ import type { Snippet, SnippetVariable } from "./snippet"; import type { Position } from "./targetDescriptor.types"; import type { EditWithRangeUpdater } from "./Types"; -export interface EditNewCommandContext { - type: "command"; - command: string; -} -export interface EditNewDelimiterContext { - type: "edit"; -} - -export type EditNewContext = EditNewCommandContext | EditNewDelimiterContext; +export type EditNewContext = "edit" | "insertLineAfter"; export interface Target { /** The text editor used for all ranges */ From b5e4a905c9fccc912323dc9c7ad59568c11c4994 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Sun, 20 Nov 2022 14:57:50 +0100 Subject: [PATCH 21/43] Updated insert notebook cell action --- .../EditNew/runEditNewNotebookCellTargets.ts | 14 +++++--- src/ide/vscode/VscodeFold.ts | 19 ++++++++-- src/ide/vscode/VscodeNotebooks.ts | 35 +++++++++++++++++++ src/ide/vscode/VscodeTextEditorImpl.ts | 18 ++++++++-- src/libs/common/types/TextEditor.ts | 12 +++++++ .../targets/NotebookCellTarget.ts | 22 +----------- 6 files changed, 90 insertions(+), 30 deletions(-) create mode 100644 src/ide/vscode/VscodeNotebooks.ts diff --git a/src/actions/EditNew/runEditNewNotebookCellTargets.ts b/src/actions/EditNew/runEditNewNotebookCellTargets.ts index 28bf955d96..bdf2148f4f 100644 --- a/src/actions/EditNew/runEditNewNotebookCellTargets.ts +++ b/src/actions/EditNew/runEditNewNotebookCellTargets.ts @@ -1,5 +1,5 @@ import { Selection } from "@cursorless/common"; -import { commands } from "vscode"; +import ide from "../../libs/cursorless-engine/singletons/ide.singleton"; import { NotebookCellPositionTarget } from "../../processTargets/targets"; import { Target } from "../../typings/target.types"; import { Graph } from "../../typings/Types"; @@ -13,13 +13,19 @@ export async function runEditNewNotebookCellTargets( // Can only run on one target because otherwise we'd end up with cursors in // multiple cells, which is unsupported in VSCode const target = ensureSingleTarget(targets) as NotebookCellPositionTarget; + const editor = ide().getEditableTextEditor(target.editor); + const isAbove = target.position === "before"; + await graph.actions.setSelection.run([targets]); - const command = target.getEditNewCommand(); - await commands.executeCommand(command); + + const isJupyter = isAbove + ? await editor.insertNotebookCellAbove() + : await editor.insertNotebookCellBelow(); + const thatMark = createThatMark([target.thatTarget]); // Inserting a new jupyter cell above pushes the previous one down two lines - if (command === "jupyter.insertCellAbove") { + if (isAbove && isJupyter) { thatMark[0].selection = new Selection( thatMark[0].selection.anchor.translate(2, undefined), thatMark[0].selection.active.translate(2, undefined), diff --git a/src/ide/vscode/VscodeFold.ts b/src/ide/vscode/VscodeFold.ts index d4d57f0bed..7e72c6eda0 100644 --- a/src/ide/vscode/VscodeFold.ts +++ b/src/ide/vscode/VscodeFold.ts @@ -6,9 +6,24 @@ export async function vscodeFold( ide: VscodeIDE, editor: VscodeTextEditorImpl, lineNumbers: number[] | undefined, - isFold: boolean, ): Promise { - const command = isFold ? "editor.fold" : "editor.unfold"; + return foldOrUnfold(ide, editor, lineNumbers, "editor.fold"); +} + +export function vscodeUnfold( + ide: VscodeIDE, + editor: VscodeTextEditorImpl, + lineNumbers: number[] | undefined, +): Promise { + return foldOrUnfold(ide, editor, lineNumbers, "editor.unfold"); +} + +async function foldOrUnfold( + ide: VscodeIDE, + editor: VscodeTextEditorImpl, + lineNumbers: number[] | undefined, + command: string, +): Promise { const originalEditor = ide.activeEditableTextEditor; // Necessary to focus editor for fold command to work diff --git a/src/ide/vscode/VscodeNotebooks.ts b/src/ide/vscode/VscodeNotebooks.ts new file mode 100644 index 0000000000..e0d83a65ac --- /dev/null +++ b/src/ide/vscode/VscodeNotebooks.ts @@ -0,0 +1,35 @@ +import * as vscode from "vscode"; +import { getNotebookFromCellDocument } from "../../util/notebook"; +import { VscodeTextEditorImpl } from "./VscodeTextEditorImpl"; + +export async function vscodeInsertNotebookCellAbove( + editor: VscodeTextEditorImpl, +): Promise { + const isNotebook = isNotebookEditor(editor); + + const command = isNotebook + ? "notebook.cell.insertCodeCellAbove" + : "jupyter.insertCellAbove"; + + await vscode.commands.executeCommand(command); + + return !isNotebook; +} + +export async function vscodeInsertNotebookCellBelow( + editor: VscodeTextEditorImpl, +): Promise { + const isNotebook = isNotebookEditor(editor); + + const command = isNotebook + ? "notebook.cell.insertCodeCellBelow" + : "jupyter.insertCellBelow"; + + await vscode.commands.executeCommand(command); + + return !isNotebook; +} + +function isNotebookEditor(editor: VscodeTextEditorImpl) { + return getNotebookFromCellDocument(editor.vscodeEditor.document) != null; +} diff --git a/src/ide/vscode/VscodeTextEditorImpl.ts b/src/ide/vscode/VscodeTextEditorImpl.ts index f94e595481..7779d98602 100644 --- a/src/ide/vscode/VscodeTextEditorImpl.ts +++ b/src/ide/vscode/VscodeTextEditorImpl.ts @@ -20,9 +20,13 @@ import { import * as vscode from "vscode"; import vscodeEdit from "./VscodeEdit"; import vscodeFocusEditor from "./VscodeFocusEditor"; -import { vscodeFold } from "./VscodeFold"; +import { vscodeFold, vscodeUnfold } from "./VscodeFold"; import VscodeIDE from "./VscodeIDE"; import { vscodeInsertSnippet } from "./VscodeInsertSnippets"; +import { + vscodeInsertNotebookCellAbove, + vscodeInsertNotebookCellBelow, +} from "./VscodeNotebooks"; import vscodeOpenLink from "./VscodeOpenLink"; import { vscodeRevealLine } from "./VscodeRevealLine"; import { VscodeTextDocumentImpl } from "./VscodeTextDocumentImpl"; @@ -104,6 +108,14 @@ export class VscodeTextEditorImpl implements TextEditor { return await vscode.commands.executeCommand(command, ...rest); } + public insertNotebookCellAbove(): Promise { + return vscodeInsertNotebookCellAbove(this); + } + + public insertNotebookCellBelow(): Promise { + return vscodeInsertNotebookCellBelow(this); + } + public openLink(location?: Position | Range): Promise { return vscodeOpenLink( this.editor, @@ -112,11 +124,11 @@ export class VscodeTextEditorImpl implements TextEditor { } public fold(lineNumbers?: number[]): Promise { - return vscodeFold(this.ide, this, lineNumbers, true); + return vscodeFold(this.ide, this, lineNumbers); } public unfold(lineNumbers?: number[]): Promise { - return vscodeFold(this.ide, this, lineNumbers, false); + return vscodeUnfold(this.ide, this, lineNumbers); } public toggleBreakpoint(ranges?: Range[]): Promise { diff --git a/src/libs/common/types/TextEditor.ts b/src/libs/common/types/TextEditor.ts index 899ede41e8..70383407a2 100644 --- a/src/libs/common/types/TextEditor.ts +++ b/src/libs/common/types/TextEditor.ts @@ -119,6 +119,18 @@ export interface EditableTextEditor extends TextEditor { */ executeCommand(command: string, ...rest: any[]): Promise; + /** + * Insert new notebook cell above. + * @return A promised that resolves to a boolean indicating if this was a jupyter notebook + */ + insertNotebookCellAbove(): Promise; + + /** + * Insert new notebook cell below. + * @return A promised that resolves to a boolean indicating if this was a jupyter notebook + */ + insertNotebookCellBelow(): Promise; + /** * Open link at location. * @param location Position or range diff --git a/src/processTargets/targets/NotebookCellTarget.ts b/src/processTargets/targets/NotebookCellTarget.ts index 08ccb12144..e7f9e70e18 100644 --- a/src/processTargets/targets/NotebookCellTarget.ts +++ b/src/processTargets/targets/NotebookCellTarget.ts @@ -1,8 +1,5 @@ -import { TextEditor } from "@cursorless/common"; -import { toVscodeEditor } from "@cursorless/vscode-common"; import { Target } from "../../typings/target.types"; import { Position } from "../../typings/targetDescriptor.types"; -import { getNotebookFromCellDocument } from "../../util/notebook"; import BaseTarget, { CommonTargetParameters } from "./BaseTarget"; import { removalUnsupportedForPosition } from "./PositionTarget"; @@ -38,7 +35,7 @@ interface NotebookCellPositionTargetParameters extends CommonTargetParameters { export class NotebookCellPositionTarget extends BaseTarget { insertionDelimiter = "\n"; isNotebookCell = true; - private position: Position; + public position: Position; constructor(parameters: NotebookCellPositionTargetParameters) { super(parameters); @@ -49,27 +46,10 @@ export class NotebookCellPositionTarget extends BaseTarget { getTrailingDelimiterTarget = () => undefined; getRemovalRange = () => removalUnsupportedForPosition(this.position); - getEditNewCommand(): string { - if (this.isNotebookEditor(this.editor)) { - return this.position === "before" - ? "notebook.cell.insertCodeCellAbove" - : "notebook.cell.insertCodeCellBelow"; - } - - return this.position === "before" - ? "jupyter.insertCellAbove" - : "jupyter.insertCellBelow"; - } - protected getCloneParameters(): NotebookCellPositionTargetParameters { return { ...this.state, position: this.position, }; } - - private isNotebookEditor(editor: TextEditor) { - const vscodeEditor = toVscodeEditor(editor); - return getNotebookFromCellDocument(vscodeEditor.document) != null; - } } From 2b7919fd2632e9bc76c76ccff15d3e8297df61c8 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Sun, 20 Nov 2022 15:16:52 +0100 Subject: [PATCH 22/43] Merged with main --- src/libs/common/ide/PassthroughIDEBase.ts | 17 ++++++++++++++++- src/libs/common/ide/fake/FakeCapabilities.ts | 8 ++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 src/libs/common/ide/fake/FakeCapabilities.ts diff --git a/src/libs/common/ide/PassthroughIDEBase.ts b/src/libs/common/ide/PassthroughIDEBase.ts index f45a1b8e3f..4bfcbf960e 100644 --- a/src/libs/common/ide/PassthroughIDEBase.ts +++ b/src/libs/common/ide/PassthroughIDEBase.ts @@ -1,4 +1,5 @@ import { EditableTextEditor, TextEditor } from "../types/TextEditor"; +import { Capabilities } from "./types/Capabilities"; import { Clipboard } from "./types/Clipboard"; import { Configuration } from "./types/Configuration"; import { TextDocumentChangeEvent } from "./types/Events"; @@ -11,12 +12,14 @@ export default class PassthroughIDEBase implements IDE { globalState: State; clipboard: Clipboard; messages: Messages; + capabilities: Capabilities; constructor(private original: IDE) { this.configuration = original.configuration; this.globalState = original.globalState; this.clipboard = original.clipboard; this.messages = original.messages; + this.capabilities = original.capabilities; } public get activeTextEditor(): TextEditor | undefined { @@ -43,11 +46,23 @@ export default class PassthroughIDEBase implements IDE { return this.original.workspaceFolders; } + public findInWorkspace(query: string): Promise { + return this.original.findInWorkspace(query); + } + + public openTextDocument(path: string): Promise { + return this.original.openTextDocument(path); + } + + public showInputBox(options?: any): Promise { + return this.original.showInputBox(options); + } + public getEditableTextEditor(editor: TextEditor): EditableTextEditor { return this.original.getEditableTextEditor(editor); } - onDidChangeTextDocument( + public onDidChangeTextDocument( listener: (event: TextDocumentChangeEvent) => void, ): Disposable { return this.original.onDidChangeTextDocument(listener); diff --git a/src/libs/common/ide/fake/FakeCapabilities.ts b/src/libs/common/ide/fake/FakeCapabilities.ts new file mode 100644 index 0000000000..306b6f484c --- /dev/null +++ b/src/libs/common/ide/fake/FakeCapabilities.ts @@ -0,0 +1,8 @@ +import { Capabilities, CapabilitiesCommand } from "../types/Capabilities"; +import { CommandId } from "../types/CommandId"; + +export class FakeCapabilities implements Capabilities { + public getCommand(_commandId: CommandId): CapabilitiesCommand { + throw Error("Not implemented"); + } +} From ea5b29ae60f4d9765325e1a4d187176ae805992e Mon Sep 17 00:00:00 2001 From: Pokey Rule <755842+pokey@users.noreply.github.com> Date: Wed, 23 Nov 2022 17:52:43 +0000 Subject: [PATCH 23/43] Cleanup --- .../EditNew/runEditNewNotebookCellTargets.ts | 19 ++++++++------- src/ide/vscode/VscodeNotebooks.ts | 23 +++++++++++++------ src/ide/vscode/VscodeTextEditorImpl.ts | 17 ++++++++------ src/libs/common/types/TextEditor.ts | 14 ++++++----- 4 files changed, 43 insertions(+), 30 deletions(-) diff --git a/src/actions/EditNew/runEditNewNotebookCellTargets.ts b/src/actions/EditNew/runEditNewNotebookCellTargets.ts index bdf2148f4f..2873b8123c 100644 --- a/src/actions/EditNew/runEditNewNotebookCellTargets.ts +++ b/src/actions/EditNew/runEditNewNotebookCellTargets.ts @@ -18,19 +18,18 @@ export async function runEditNewNotebookCellTargets( await graph.actions.setSelection.run([targets]); - const isJupyter = isAbove - ? await editor.insertNotebookCellAbove() - : await editor.insertNotebookCellBelow(); + let modifyThatMark = (selection: Selection) => selection; + if (isAbove) { + modifyThatMark = await editor.editNewNotebookCellAbove(); + } else { + await editor.editNewNotebookCellBelow(); + } const thatMark = createThatMark([target.thatTarget]); - // Inserting a new jupyter cell above pushes the previous one down two lines - if (isAbove && isJupyter) { - thatMark[0].selection = new Selection( - thatMark[0].selection.anchor.translate(2, undefined), - thatMark[0].selection.active.translate(2, undefined), - ); - } + // 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/src/ide/vscode/VscodeNotebooks.ts b/src/ide/vscode/VscodeNotebooks.ts index e0d83a65ac..f29929c823 100644 --- a/src/ide/vscode/VscodeNotebooks.ts +++ b/src/ide/vscode/VscodeNotebooks.ts @@ -1,10 +1,11 @@ +import { Selection } from "@cursorless/common"; import * as vscode from "vscode"; import { getNotebookFromCellDocument } from "../../util/notebook"; import { VscodeTextEditorImpl } from "./VscodeTextEditorImpl"; -export async function vscodeInsertNotebookCellAbove( +export async function vscodeEditNewNotebookCellAbove( editor: VscodeTextEditorImpl, -): Promise { +): Promise<(selection: Selection) => Selection> { const isNotebook = isNotebookEditor(editor); const command = isNotebook @@ -13,12 +14,22 @@ export async function vscodeInsertNotebookCellAbove( await vscode.commands.executeCommand(command); - return !isNotebook; + // 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 vscodeInsertNotebookCellBelow( +export async function vscodeEditNewNotebookCellBelow( editor: VscodeTextEditorImpl, -): Promise { +): Promise { const isNotebook = isNotebookEditor(editor); const command = isNotebook @@ -26,8 +37,6 @@ export async function vscodeInsertNotebookCellBelow( : "jupyter.insertCellBelow"; await vscode.commands.executeCommand(command); - - return !isNotebook; } function isNotebookEditor(editor: VscodeTextEditorImpl) { diff --git a/src/ide/vscode/VscodeTextEditorImpl.ts b/src/ide/vscode/VscodeTextEditorImpl.ts index 7779d98602..89e69d446f 100644 --- a/src/ide/vscode/VscodeTextEditorImpl.ts +++ b/src/ide/vscode/VscodeTextEditorImpl.ts @@ -1,4 +1,5 @@ import { + EditableTextEditor, Position, Range, RevealLineAt, @@ -24,15 +25,15 @@ import { vscodeFold, vscodeUnfold } from "./VscodeFold"; import VscodeIDE from "./VscodeIDE"; import { vscodeInsertSnippet } from "./VscodeInsertSnippets"; import { - vscodeInsertNotebookCellAbove, - vscodeInsertNotebookCellBelow, + vscodeEditNewNotebookCellAbove, + vscodeEditNewNotebookCellBelow, } from "./VscodeNotebooks"; import vscodeOpenLink from "./VscodeOpenLink"; import { vscodeRevealLine } from "./VscodeRevealLine"; import { VscodeTextDocumentImpl } from "./VscodeTextDocumentImpl"; import { vscodeToggleBreakpoint } from "./VscodeToggleBreakpoint"; -export class VscodeTextEditorImpl implements TextEditor { +export class VscodeTextEditorImpl implements EditableTextEditor { readonly document: TextDocument; constructor( @@ -108,12 +109,14 @@ export class VscodeTextEditorImpl implements TextEditor { return await vscode.commands.executeCommand(command, ...rest); } - public insertNotebookCellAbove(): Promise { - return vscodeInsertNotebookCellAbove(this); + public editNewNotebookCellAbove(): Promise< + (selection: Selection) => Selection + > { + return vscodeEditNewNotebookCellAbove(this); } - public insertNotebookCellBelow(): Promise { - return vscodeInsertNotebookCellBelow(this); + public editNewNotebookCellBelow(): Promise { + return vscodeEditNewNotebookCellBelow(this); } public openLink(location?: Position | Range): Promise { diff --git a/src/libs/common/types/TextEditor.ts b/src/libs/common/types/TextEditor.ts index 70383407a2..097a69d358 100644 --- a/src/libs/common/types/TextEditor.ts +++ b/src/libs/common/types/TextEditor.ts @@ -120,16 +120,18 @@ export interface EditableTextEditor extends TextEditor { executeCommand(command: string, ...rest: any[]): Promise; /** - * Insert new notebook cell above. - * @return A promised that resolves to a boolean indicating if this was a jupyter notebook + * 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. */ - insertNotebookCellAbove(): Promise; + editNewNotebookCellAbove(): Promise<(selection: Selection) => Selection>; /** - * Insert new notebook cell below. - * @return A promised that resolves to a boolean indicating if this was a jupyter notebook + * Edit a new new notebook cell below. */ - insertNotebookCellBelow(): Promise; + editNewNotebookCellBelow(): Promise; /** * Open link at location. From a4089aac7da6da090865de67c018f27c3215d8e2 Mon Sep 17 00:00:00 2001 From: Pokey Rule <755842+pokey@users.noreply.github.com> Date: Wed, 23 Nov 2022 18:01:22 +0000 Subject: [PATCH 24/43] More tweaks --- src/actions/EditNew/EditNew.ts | 6 +++--- src/actions/EditNew/runEditTargets.ts | 4 ++-- ...sertLineAfterTargets.ts => runInsertLineAfterTargets.ts} | 6 +++--- ...tNewNotebookCellTargets.ts => runNotebookCellTargets.ts} | 0 src/processTargets/targets/BaseTarget.ts | 4 ++-- src/processTargets/targets/PositionTarget.ts | 4 ++-- src/typings/target.types.ts | 4 ++-- 7 files changed, 14 insertions(+), 14 deletions(-) rename src/actions/EditNew/{runEditInsertLineAfterTargets.ts => runInsertLineAfterTargets.ts} (92%) rename src/actions/EditNew/{runEditNewNotebookCellTargets.ts => runNotebookCellTargets.ts} (100%) diff --git a/src/actions/EditNew/EditNew.ts b/src/actions/EditNew/EditNew.ts index 1945318569..db5c1f7d39 100644 --- a/src/actions/EditNew/EditNew.ts +++ b/src/actions/EditNew/EditNew.ts @@ -8,9 +8,9 @@ import { setSelectionsAndFocusEditor } from "../../util/setSelectionsAndFocusEdi import { createThatMark, ensureSingleEditor } from "../../util/targetUtils"; import { Action, ActionReturnValue } from "../actions.types"; import { State } from "./EditNew.types"; -import { runEditInsertLineAfterTargets } from "./runEditInsertLineAfterTargets"; +import { runInsertLineAfterTargets } from "./runInsertLineAfterTargets"; import { runEditTargets } from "./runEditTargets"; -import { runEditNewNotebookCellTargets } from "./runEditNewNotebookCellTargets"; +import { runEditNewNotebookCellTargets } from "./runNotebookCellTargets"; export class EditNew implements Action { getFinalStages(): ModifierStage[] { @@ -43,7 +43,7 @@ export class EditNew implements Action { cursorRanges: new Array(targets.length).fill(undefined) as undefined[], }; - state = await runEditInsertLineAfterTargets( + state = await runInsertLineAfterTargets( this.graph, editableEditor, state, diff --git a/src/actions/EditNew/runEditTargets.ts b/src/actions/EditNew/runEditTargets.ts index bec062d8a7..a23b22001b 100644 --- a/src/actions/EditNew/runEditTargets.ts +++ b/src/actions/EditNew/runEditTargets.ts @@ -24,8 +24,8 @@ export async function runEditTargets( ): Promise { const targets: EditTarget[] = state.targets .map((target, index) => { - const context = target.getEditNewContext(); - if (context === "edit") { + const actionType = target.getEditNewActionType(); + if (actionType === "edit") { return { target, index, diff --git a/src/actions/EditNew/runEditInsertLineAfterTargets.ts b/src/actions/EditNew/runInsertLineAfterTargets.ts similarity index 92% rename from src/actions/EditNew/runEditInsertLineAfterTargets.ts rename to src/actions/EditNew/runInsertLineAfterTargets.ts index 044b62d5ee..96fd5bb330 100644 --- a/src/actions/EditNew/runEditInsertLineAfterTargets.ts +++ b/src/actions/EditNew/runInsertLineAfterTargets.ts @@ -14,15 +14,15 @@ import { EditTarget, State } from "./EditNew.types"; * @param state The state object tracking cursors, thatMark, etc * @returns An updated `state` object */ -export async function runEditInsertLineAfterTargets( +export async function runInsertLineAfterTargets( graph: Graph, editor: EditableTextEditor, state: State, ): Promise { const targets: EditTarget[] = state.targets .map((target, index) => { - const context = target.getEditNewContext(); - if (context === "insertLineAfter") { + const actionType = target.getEditNewActionType(); + if (actionType === "insertLineAfter") { return { target, index, diff --git a/src/actions/EditNew/runEditNewNotebookCellTargets.ts b/src/actions/EditNew/runNotebookCellTargets.ts similarity index 100% rename from src/actions/EditNew/runEditNewNotebookCellTargets.ts rename to src/actions/EditNew/runNotebookCellTargets.ts diff --git a/src/processTargets/targets/BaseTarget.ts b/src/processTargets/targets/BaseTarget.ts index 98bb9d7f5f..b6d77feff9 100644 --- a/src/processTargets/targets/BaseTarget.ts +++ b/src/processTargets/targets/BaseTarget.ts @@ -1,7 +1,7 @@ import { Range, Selection, TextEditor } from "@cursorless/common"; import { isEqual } from "lodash"; import { NoContainingScopeError } from "../../errors"; -import type { EditNewContext, Target } from "../../typings/target.types"; +import type { EditNewActionType, Target } from "../../typings/target.types"; import type { Position } from "../../typings/targetDescriptor.types"; import type { EditWithRangeUpdater } from "../../typings/Types"; import { isSameType } from "../../util/typeUtils"; @@ -82,7 +82,7 @@ export default abstract class BaseTarget implements Target { }; } - getEditNewContext(): EditNewContext { + getEditNewActionType(): EditNewActionType { return "edit"; } diff --git a/src/processTargets/targets/PositionTarget.ts b/src/processTargets/targets/PositionTarget.ts index 512c374d4f..ea01c646a1 100644 --- a/src/processTargets/targets/PositionTarget.ts +++ b/src/processTargets/targets/PositionTarget.ts @@ -1,7 +1,7 @@ import { Range, TextEditor } from "@cursorless/common"; import { BaseTarget, CommonTargetParameters } from "."; import { UnsupportedError } from "../../errors"; -import { EditNewContext } from "../../typings/target.types"; +import { EditNewActionType } from "../../typings/target.types"; import { Position } from "../../typings/targetDescriptor.types"; import { EditWithRangeUpdater } from "../../typings/Types"; @@ -41,7 +41,7 @@ export default class PositionTarget extends BaseTarget { getRemovalRange = () => removalUnsupportedForPosition(this.position); - getEditNewContext(): EditNewContext { + getEditNewActionType(): EditNewActionType { if (this.insertionDelimiter === "\n" && this.position === "after") { return "insertLineAfter"; } diff --git a/src/typings/target.types.ts b/src/typings/target.types.ts index 4c991c92a2..cd5bb6411d 100644 --- a/src/typings/target.types.ts +++ b/src/typings/target.types.ts @@ -20,7 +20,7 @@ import type { Snippet, SnippetVariable } from "./snippet"; import type { Position } from "./targetDescriptor.types"; import type { EditWithRangeUpdater } from "./Types"; -export type EditNewContext = "edit" | "insertLineAfter"; +export type EditNewActionType = "edit" | "insertLineAfter"; export interface Target { /** The text editor used for all ranges */ @@ -116,7 +116,7 @@ export interface Target { getTrailingDelimiterTarget(): Target | undefined; getRemovalRange(): Range; getRemovalHighlightRange(): Range | undefined; - getEditNewContext(): EditNewContext; + getEditNewActionType(): EditNewActionType; withThatTarget(thatTarget: Target): Target; withContentRange(contentRange: Range): Target; createContinuousRangeTarget( From 8bbe6cfcae48aa33d051941da97d8ed15d60b99d Mon Sep 17 00:00:00 2001 From: Pokey Rule <755842+pokey@users.noreply.github.com> Date: Wed, 23 Nov 2022 18:10:33 +0000 Subject: [PATCH 25/43] More tweaks --- src/ide/vscode/VscodeIDE.ts | 4 ++-- src/libs/common/ide/PassthroughIDEBase.ts | 2 +- src/libs/common/ide/fake/FakeIDE.ts | 2 +- src/libs/common/ide/types/ide.types.ts | 3 ++- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/ide/vscode/VscodeIDE.ts b/src/ide/vscode/VscodeIDE.ts index 89b08052d3..d583174c0f 100644 --- a/src/ide/vscode/VscodeIDE.ts +++ b/src/ide/vscode/VscodeIDE.ts @@ -80,9 +80,9 @@ export default class VscodeIDE implements IDE { }); } - public async openTextDocument(path: string): Promise { + public async openTextDocument(path: string): Promise { const textDocument = await workspace.openTextDocument(path); - await window.showTextDocument(textDocument); + return this.fromVscodeEditor(await window.showTextDocument(textDocument)); } public async showInputBox( diff --git a/src/libs/common/ide/PassthroughIDEBase.ts b/src/libs/common/ide/PassthroughIDEBase.ts index 4bfcbf960e..a8a2626ab6 100644 --- a/src/libs/common/ide/PassthroughIDEBase.ts +++ b/src/libs/common/ide/PassthroughIDEBase.ts @@ -50,7 +50,7 @@ export default class PassthroughIDEBase implements IDE { return this.original.findInWorkspace(query); } - public openTextDocument(path: string): Promise { + public openTextDocument(path: string): Promise { return this.original.openTextDocument(path); } diff --git a/src/libs/common/ide/fake/FakeIDE.ts b/src/libs/common/ide/fake/FakeIDE.ts index 21051bc825..014f79cd95 100644 --- a/src/libs/common/ide/fake/FakeIDE.ts +++ b/src/libs/common/ide/fake/FakeIDE.ts @@ -65,7 +65,7 @@ export default class FakeIDE implements IDE { throw Error("Not implemented"); } - public openTextDocument(_path: string): Promise { + public openTextDocument(_path: string): Promise { throw Error("Not implemented"); } diff --git a/src/libs/common/ide/types/ide.types.ts b/src/libs/common/ide/types/ide.types.ts index 44f625c084..4e6b98b8c9 100644 --- a/src/libs/common/ide/types/ide.types.ts +++ b/src/libs/common/ide/types/ide.types.ts @@ -91,8 +91,9 @@ export interface IDE { * * @see {@link openTextDocument} * @param path A path to a file on disk. + * @return An editor for the text document at the given path */ - openTextDocument(path: string): Promise; + openTextDocument(path: string): Promise; /** * Opens an input box to ask the user for input. From 876ccbe5599d801e43a9a1537cc27cf58497d620 Mon Sep 17 00:00:00 2001 From: Pokey Rule <755842+pokey@users.noreply.github.com> Date: Wed, 23 Nov 2022 18:14:46 +0000 Subject: [PATCH 26/43] Some renames --- src/actions/Actions.ts | 12 ++++++------ src/actions/{ClipboardCut.ts => CutToClipboard.ts} | 2 +- src/actions/MakeshiftActions.ts | 2 +- .../{ClipboardPaste.ts => PasteFromClipboard.ts} | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) rename src/actions/{ClipboardCut.ts => CutToClipboard.ts} (96%) rename src/actions/{ClipboardPaste.ts => PasteFromClipboard.ts} (98%) diff --git a/src/actions/Actions.ts b/src/actions/Actions.ts index cd0a803e1b..1fdf923717 100644 --- a/src/actions/Actions.ts +++ b/src/actions/Actions.ts @@ -12,12 +12,12 @@ import { ShowHover, ShowQuickFix, ShowReferences, - ClipboardCopy, + CopyToClipboard, } from "./MakeshiftActions"; import { Bring, Move, Swap } from "./BringMoveSwap"; import Call from "./Call"; import Clear from "./Clear"; -import { ClipboardCut } from "./ClipboardCut"; +import { CutToClipboard } from "./CutToClipboard"; import Deselect from "./Deselect"; import { EditNew, EditNewAfter, EditNewBefore } from "./EditNew"; import ExecuteCommand from "./ExecuteCommand"; @@ -37,7 +37,7 @@ import { InsertEmptyLinesAround, } from "./InsertEmptyLines"; import InsertSnippet from "./InsertSnippet"; -import { ClipboardPaste } from "./ClipboardPaste"; +import { PasteFromClipboard } from "./PasteFromClipboard"; import Remove from "./Remove"; import Replace from "./Replace"; import Rewrap from "./Rewrap"; @@ -57,8 +57,8 @@ class Actions implements ActionRecord { callAsFunction = new Call(this.graph); clearAndSetSelection = new Clear(this.graph); - copyToClipboard = new ClipboardCopy(this.graph); - cutToClipboard = new ClipboardCut(this.graph); + copyToClipboard = new CopyToClipboard(this.graph); + cutToClipboard = new CutToClipboard(this.graph); deselect = new Deselect(this.graph); editNew = new EditNew(this.graph); editNewLineAfter = new EditNewAfter(this.graph); @@ -80,7 +80,7 @@ class Actions implements ActionRecord { insertSnippet = new InsertSnippet(this.graph); moveToTarget = new Move(this.graph); outdentLine = new OutdentLine(this.graph); - pasteFromClipboard = new ClipboardPaste(this.graph); + pasteFromClipboard = new PasteFromClipboard(this.graph); randomizeTargets = new Random(this.graph); remove = new Remove(this.graph); rename = new Rename(this.graph); diff --git a/src/actions/ClipboardCut.ts b/src/actions/CutToClipboard.ts similarity index 96% rename from src/actions/ClipboardCut.ts rename to src/actions/CutToClipboard.ts index afed109d7c..6ac24c61d8 100644 --- a/src/actions/ClipboardCut.ts +++ b/src/actions/CutToClipboard.ts @@ -4,7 +4,7 @@ import { Graph } from "../typings/Types"; import { getOutsideOverflow } from "../util/targetUtils"; import { Action, ActionReturnValue } from "./actions.types"; -export class ClipboardCut implements Action { +export class CutToClipboard implements Action { constructor(private graph: Graph) { this.run = this.run.bind(this); } diff --git a/src/actions/MakeshiftActions.ts b/src/actions/MakeshiftActions.ts index ab13d01b3f..c53d0c2eff 100644 --- a/src/actions/MakeshiftActions.ts +++ b/src/actions/MakeshiftActions.ts @@ -43,7 +43,7 @@ abstract class MakeshiftAction extends CallbackAction { } } -export class ClipboardCopy extends MakeshiftAction { +export class CopyToClipboard extends MakeshiftAction { command: CommandId = "clipboardCopy"; ensureSingleEditor = true; } diff --git a/src/actions/ClipboardPaste.ts b/src/actions/PasteFromClipboard.ts similarity index 98% rename from src/actions/ClipboardPaste.ts rename to src/actions/PasteFromClipboard.ts index cb18ee9759..4b45acb2b9 100644 --- a/src/actions/ClipboardPaste.ts +++ b/src/actions/PasteFromClipboard.ts @@ -9,7 +9,7 @@ import { setSelectionsWithoutFocusingEditor } from "../util/setSelectionsAndFocu import { ensureSingleEditor } from "../util/targetUtils"; import { ActionReturnValue } from "./actions.types"; -export class ClipboardPaste { +export class PasteFromClipboard { constructor(private graph: Graph) {} async run([targets]: [Target[]]): Promise { From e6340216bed7cda430c7e972cb2e83826fd66214 Mon Sep 17 00:00:00 2001 From: Pokey Rule <755842+pokey@users.noreply.github.com> Date: Wed, 23 Nov 2022 18:33:07 +0000 Subject: [PATCH 27/43] Doc strings --- src/actions/CallbackAction.ts | 8 ++++++-- src/actions/ExecuteCommand.ts | 11 +++++++---- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/actions/CallbackAction.ts b/src/actions/CallbackAction.ts index b8772bac10..6daf9c8710 100644 --- a/src/actions/CallbackAction.ts +++ b/src/actions/CallbackAction.ts @@ -1,6 +1,5 @@ import { EditableTextEditor, TextEditor } from "@cursorless/common"; import { flatten } from "lodash"; -import { Options } from "semver"; import { selectionToThatTarget } from "../core/commandRunner/selectionToThatTarget"; import { callFunctionAndUpdateSelections } from "../core/updateSelections/updateSelections"; import ide from "../libs/cursorless-engine/singletons/ide.singleton"; @@ -18,7 +17,7 @@ import { } from "../util/targetUtils"; import { Action, ActionReturnValue } from "./actions.types"; -interface CallbackOptions extends Options { +interface CallbackOptions { callback: (editor: EditableTextEditor, targets: Target[]) => Promise; ensureSingleEditor: boolean; ensureSingleTarget: boolean; @@ -27,6 +26,11 @@ interface CallbackOptions extends Options { showDecorations: boolean; } +/** + * This is a helper action that is used internally to implement various actions. + * It takes a {@link CallbackOptions.callback callback} that is called once for + * each editor, receiving all the targets that are in the given editor. + */ export class CallbackAction implements Action { constructor(private graph: Graph) { this.run = this.run.bind(this); diff --git a/src/actions/ExecuteCommand.ts b/src/actions/ExecuteCommand.ts index f518ad4a5a..39e7763b48 100644 --- a/src/actions/ExecuteCommand.ts +++ b/src/actions/ExecuteCommand.ts @@ -13,8 +13,11 @@ interface Options { } /** - * This is just a wrapper for {@link CallbackAction} that allows the commands string without an options object. - * Should only be used by the API. Internally go directly to {@link CallbackAction} + * This action can be used to execute a built-in ide command on one or more + * targets by first setting the selection to those targets and then running the + * action, restoring the selections if + * {@link Options.restoreSelection restoreSelection} is `true`. Internally, most + * of the heavy lifting is done by {@link CallbackAction}. */ export default class ExecuteCommand implements Action { private callbackAction: CallbackAction; @@ -25,7 +28,7 @@ export default class ExecuteCommand implements Action { async run( targets: [Target[]], - command: string, + commandId: string, { commandArgs, ensureSingleEditor, @@ -38,7 +41,7 @@ export default class ExecuteCommand implements Action { return this.callbackAction.run(targets, { callback: (editor: EditableTextEditor) => - editor.executeCommand(command, ...args), + editor.executeCommand(commandId, ...args), setSelection: true, ensureSingleEditor: ensureSingleEditor ?? false, ensureSingleTarget: ensureSingleTarget ?? false, From d328f66f4889654f171e1f860f2db64500730878 Mon Sep 17 00:00:00 2001 From: Pokey Rule <755842+pokey@users.noreply.github.com> Date: Wed, 23 Nov 2022 18:38:44 +0000 Subject: [PATCH 28/43] More tweaks --- src/actions/ExecuteCommand.ts | 2 +- src/ide/vscode/VscodeFold.ts | 2 +- src/ide/vscode/VscodeTextEditorImpl.ts | 6 +++--- src/libs/common/types/TextEditor.ts | 15 +++++++++------ 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/actions/ExecuteCommand.ts b/src/actions/ExecuteCommand.ts index 39e7763b48..4cb6120c52 100644 --- a/src/actions/ExecuteCommand.ts +++ b/src/actions/ExecuteCommand.ts @@ -41,7 +41,7 @@ export default class ExecuteCommand implements Action { return this.callbackAction.run(targets, { callback: (editor: EditableTextEditor) => - editor.executeCommand(commandId, ...args), + editor.executeBuiltInCommand(commandId, ...args), setSelection: true, ensureSingleEditor: ensureSingleEditor ?? false, ensureSingleTarget: ensureSingleTarget ?? false, diff --git a/src/ide/vscode/VscodeFold.ts b/src/ide/vscode/VscodeFold.ts index 7e72c6eda0..03fb47bbd0 100644 --- a/src/ide/vscode/VscodeFold.ts +++ b/src/ide/vscode/VscodeFold.ts @@ -22,7 +22,7 @@ async function foldOrUnfold( ide: VscodeIDE, editor: VscodeTextEditorImpl, lineNumbers: number[] | undefined, - command: string, + command: "editor.fold" | "editor.unfold", ): Promise { const originalEditor = ide.activeEditableTextEditor; diff --git a/src/ide/vscode/VscodeTextEditorImpl.ts b/src/ide/vscode/VscodeTextEditorImpl.ts index 89e69d446f..d333bcac6d 100644 --- a/src/ide/vscode/VscodeTextEditorImpl.ts +++ b/src/ide/vscode/VscodeTextEditorImpl.ts @@ -102,11 +102,11 @@ export class VscodeTextEditorImpl implements EditableTextEditor { return vscodeFocusEditor(this.ide, this); } - public async executeCommand( + public async executeBuiltInCommand( command: string, - ...rest: any[] + ...args: any[] ): Promise { - return await vscode.commands.executeCommand(command, ...rest); + return await vscode.commands.executeCommand(command, ...args); } public editNewNotebookCellAbove(): Promise< diff --git a/src/libs/common/types/TextEditor.ts b/src/libs/common/types/TextEditor.ts index 097a69d358..2354ea7047 100644 --- a/src/libs/common/types/TextEditor.ts +++ b/src/libs/common/types/TextEditor.ts @@ -110,14 +110,17 @@ export interface EditableTextEditor extends TextEditor { ): Promise; /** - * Executes the command denoted by the given command identifier. + * Executes the built-in ide command denoted by the given command identifier. * * @param command Identifier of the command to execute. - * @param rest Parameters passed to the command function. - * @return A promise that resolves to the returned value of the given command. `undefined` when - * the command handler function doesn't return anything. - */ - executeCommand(command: string, ...rest: any[]): Promise; + * @param args Parameters passed to the command function. + * @return A promise that resolves to the returned value of the given command. + * `undefined` when the command handler function doesn't return anything. + */ + executeBuiltInCommand( + command: string, + ...args: any[] + ): Promise; /** * Edit a new new notebook cell above. From 8441fb3c373cb0667ff23e2f23690f62b6ce8c6f Mon Sep 17 00:00:00 2001 From: Pokey Rule <755842+pokey@users.noreply.github.com> Date: Wed, 23 Nov 2022 19:14:54 +0000 Subject: [PATCH 29/43] Minor tweaks --- src/actions/MakeshiftActions.ts | 14 +++++++------- src/ide/vscode/VscodeIDE.ts | 3 +-- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/actions/MakeshiftActions.ts b/src/actions/MakeshiftActions.ts index c53d0c2eff..73fb57b173 100644 --- a/src/actions/MakeshiftActions.ts +++ b/src/actions/MakeshiftActions.ts @@ -13,7 +13,9 @@ interface Options { showDecorations?: boolean; } -abstract class MakeshiftAction extends CallbackAction { +abstract class MakeshiftAction { + private callbackAction: CallbackAction; + abstract command: CommandId; ensureSingleEditor: boolean = false; ensureSingleTarget: boolean = false; @@ -21,7 +23,7 @@ abstract class MakeshiftAction extends CallbackAction { showDecorations: boolean = true; constructor(graph: Graph) { - super(graph); + this.callbackAction = new CallbackAction(graph); this.run = this.run.bind(this); } @@ -31,7 +33,7 @@ abstract class MakeshiftAction extends CallbackAction { ): Promise { const capabilities = ide().capabilities.getCommand(this.command); - return super.run(targets, { + return this.callbackAction.run(targets, { callback: (editor, targets) => callback(editor, targets, this.command, capabilities), setSelection: !capabilities.acceptsLocation, @@ -109,9 +111,9 @@ function callback( editor: EditableTextEditor, targets: Target[], command: CommandId, - capabilities: CapabilitiesCommand, + { acceptsLocation }: CapabilitiesCommand, ): Promise { - const ranges = capabilities.acceptsLocation + const ranges = acceptsLocation ? targets.map((t) => t.contentRange) : undefined; @@ -148,6 +150,4 @@ function callback( case "extractVariable": return editor.extractVariable(range); } - - throw Error(`Unknown command '${command}'`); } diff --git a/src/ide/vscode/VscodeIDE.ts b/src/ide/vscode/VscodeIDE.ts index d583174c0f..0ed21066d3 100644 --- a/src/ide/vscode/VscodeIDE.ts +++ b/src/ide/vscode/VscodeIDE.ts @@ -1,5 +1,4 @@ import type { - Capabilities, EditableTextEditor, InputBoxOptions, TextEditor, @@ -28,7 +27,7 @@ export default class VscodeIDE implements IDE { readonly globalState: VscodeGlobalState; readonly messages: VscodeMessages; readonly clipboard: VscodeClipboard; - readonly capabilities: Capabilities; + readonly capabilities: VscodeCapabilities; private editorMap; constructor(private extensionContext: ExtensionContext) { From 0e8afc6ec4a841075789214d5f573888669b9cf4 Mon Sep 17 00:00:00 2001 From: Pokey Rule <755842+pokey@users.noreply.github.com> Date: Wed, 23 Nov 2022 19:51:51 +0000 Subject: [PATCH 30/43] Cleanup capabilities --- src/actions/Actions.ts | 3 +- src/actions/Fold.ts | 55 -------------------- src/actions/MakeshiftActions.ts | 26 +++++++-- src/ide/vscode/VscodeCapabilities.ts | 19 +++---- src/ide/vscode/VscodeFold.ts | 26 ++++++--- src/ide/vscode/VscodeTextEditorImpl.ts | 12 ++--- src/libs/common/ide/fake/FakeCapabilities.ts | 22 ++++++-- src/libs/common/ide/types/Capabilities.ts | 9 ++-- src/libs/common/ide/types/CommandId.ts | 2 + src/libs/common/types/TextEditor.ts | 16 +++--- 10 files changed, 85 insertions(+), 105 deletions(-) delete mode 100644 src/actions/Fold.ts diff --git a/src/actions/Actions.ts b/src/actions/Actions.ts index 1fdf923717..addae6866e 100644 --- a/src/actions/Actions.ts +++ b/src/actions/Actions.ts @@ -13,6 +13,8 @@ import { ShowQuickFix, ShowReferences, CopyToClipboard, + Fold, + Unfold, } from "./MakeshiftActions"; import { Bring, Move, Swap } from "./BringMoveSwap"; import Call from "./Call"; @@ -22,7 +24,6 @@ import Deselect from "./Deselect"; import { EditNew, EditNewAfter, EditNewBefore } from "./EditNew"; import ExecuteCommand from "./ExecuteCommand"; import { FindInWorkspace } from "./Find"; -import { Fold, Unfold } from "./Fold"; import FollowLink from "./FollowLink"; import GenerateSnippet from "./GenerateSnippet"; import GetText from "./GetText"; diff --git a/src/actions/Fold.ts b/src/actions/Fold.ts deleted file mode 100644 index 957e0e1b20..0000000000 --- a/src/actions/Fold.ts +++ /dev/null @@ -1,55 +0,0 @@ -import ide from "../libs/cursorless-engine/singletons/ide.singleton"; -import { Target } from "../typings/target.types"; -import { Graph } from "../typings/Types"; -import { createThatMark, ensureSingleEditor } from "../util/targetUtils"; -import { Action, ActionReturnValue } from "./actions.types"; - -class FoldAction implements Action { - constructor(private isFold: boolean) { - this.run = this.run.bind(this); - } - - async run([targets]: [Target[], Target[]]): Promise { - const editor = ide().getEditableTextEditor(ensureSingleEditor(targets)); - - const singleLineTargets = targets.filter( - (target) => target.contentRange.isSingleLine, - ); - const multiLineTargets = targets.filter( - (target) => !target.contentRange.isSingleLine, - ); - - // Don't mix multi and single line targets. - // This is probably the result of an "every" command - // and folding the single line targets will fold the parent as well - const selectedTargets = multiLineTargets.length - ? multiLineTargets - : singleLineTargets; - - const selectionLines = selectedTargets.map( - (target) => target.contentRange.start.line, - ); - - if (this.isFold) { - await editor.fold(selectionLines); - } else { - await editor.unfold(selectionLines); - } - - return { - thatSelections: createThatMark(targets), - }; - } -} - -export class Fold extends FoldAction { - constructor(_graph: Graph) { - super(true); - } -} - -export class Unfold extends FoldAction { - constructor(_graph: Graph) { - super(false); - } -} diff --git a/src/actions/MakeshiftActions.ts b/src/actions/MakeshiftActions.ts index 73fb57b173..d672d1fadc 100644 --- a/src/actions/MakeshiftActions.ts +++ b/src/actions/MakeshiftActions.ts @@ -1,5 +1,5 @@ import { - CapabilitiesCommand, + CommandCapabilities, CommandId, EditableTextEditor, } from "@cursorless/common"; @@ -31,7 +31,11 @@ abstract class MakeshiftAction { targets: [Target[]], { showDecorations }: Options = {}, ): Promise { - const capabilities = ide().capabilities.getCommand(this.command); + const capabilities = ide().capabilities.commands[this.command]; + + if (capabilities == null) { + throw Error(`Action ${this.command} is not supported by your ide`); + } return this.callbackAction.run(targets, { callback: (editor, targets) => @@ -62,6 +66,14 @@ export class OutdentLine extends MakeshiftAction { command: CommandId = "outdentLine"; } +export class Fold extends MakeshiftAction { + command: CommandId = "fold"; +} + +export class Unfold extends MakeshiftAction { + command: CommandId = "unfold"; +} + export class Rename extends MakeshiftAction { command: CommandId = "rename"; ensureSingleTarget = true; @@ -111,7 +123,7 @@ function callback( editor: EditableTextEditor, targets: Target[], command: CommandId, - { acceptsLocation }: CapabilitiesCommand, + { acceptsLocation }: CommandCapabilities, ): Promise { const ranges = acceptsLocation ? targets.map((t) => t.contentRange) @@ -122,11 +134,15 @@ function callback( case "toggleLineComment": return editor.toggleLineComment(ranges); case "indentLine": - return editor.indentLines(ranges); + return editor.indentLine(ranges); case "outdentLine": - return editor.outdentLines(ranges); + return editor.outdentLine(ranges); case "clipboardCopy": return editor.clipboardCopy(ranges); + case "fold": + return editor.fold(ranges); + case "unfold": + return editor.unfold(ranges); } const range = ranges?.[0]; diff --git a/src/ide/vscode/VscodeCapabilities.ts b/src/ide/vscode/VscodeCapabilities.ts index ccfcf9e62a..5403af66f3 100644 --- a/src/ide/vscode/VscodeCapabilities.ts +++ b/src/ide/vscode/VscodeCapabilities.ts @@ -1,11 +1,9 @@ -import { CommandId } from "@cursorless/common"; import { Capabilities, - CapabilitiesCommand, - CapabilitiesCommands, + CommandCapabilityMap, } from "../../libs/common/ide/types/Capabilities"; -const capabilitiesCommands: CapabilitiesCommands = { +const COMMAND_CAPABILITIES: CommandCapabilityMap = { clipboardCopy: { acceptsLocation: false }, toggleLineComment: { acceptsLocation: false }, indentLine: { acceptsLocation: false }, @@ -17,16 +15,11 @@ const capabilitiesCommands: CapabilitiesCommands = { showHover: { acceptsLocation: false }, showDebugHover: { acceptsLocation: false }, extractVariable: { acceptsLocation: false }, + fold: { acceptsLocation: true }, + unfold: { acceptsLocation: true }, + showReferences: { acceptsLocation: false }, }; export class VscodeCapabilities implements Capabilities { - public getCommand(commandId: CommandId): CapabilitiesCommand { - const capabilities = capabilitiesCommands[commandId]; - - if (capabilities == null) { - throw Error(`Missing command capabilities for '${commandId}'`); - } - - return capabilities; - } + commands = COMMAND_CAPABILITIES; } diff --git a/src/ide/vscode/VscodeFold.ts b/src/ide/vscode/VscodeFold.ts index 03fb47bbd0..fe40b7f700 100644 --- a/src/ide/vscode/VscodeFold.ts +++ b/src/ide/vscode/VscodeFold.ts @@ -1,3 +1,4 @@ +import { Range } from "@cursorless/common"; import * as vscode from "vscode"; import VscodeIDE from "./VscodeIDE"; import { VscodeTextEditorImpl } from "./VscodeTextEditorImpl"; @@ -5,25 +6,35 @@ import { VscodeTextEditorImpl } from "./VscodeTextEditorImpl"; export async function vscodeFold( ide: VscodeIDE, editor: VscodeTextEditorImpl, - lineNumbers: number[] | undefined, + ranges: Range[] | undefined, ): Promise { - return foldOrUnfold(ide, editor, lineNumbers, "editor.fold"); + return foldOrUnfold(ide, editor, ranges, "editor.fold"); } export function vscodeUnfold( ide: VscodeIDE, editor: VscodeTextEditorImpl, - lineNumbers: number[] | undefined, + ranges: Range[] | undefined, ): Promise { - return foldOrUnfold(ide, editor, lineNumbers, "editor.unfold"); + return foldOrUnfold(ide, editor, ranges, "editor.unfold"); } async function foldOrUnfold( ide: VscodeIDE, editor: VscodeTextEditorImpl, - lineNumbers: number[] | undefined, + ranges: Range[] | undefined, command: "editor.fold" | "editor.unfold", ): Promise { + ranges = ranges ?? editor.selections; + + const singleLineRanges = ranges.filter((range) => range.isSingleLine); + const multiLineRanges = ranges.filter((range) => !range.isSingleLine); + + // Don't mix multi and single line targets. + // This is probably the result of an "every" command + // and folding the single line targets will fold the parent as well + ranges = multiLineRanges.length ? multiLineRanges : singleLineRanges; + const originalEditor = ide.activeEditableTextEditor; // Necessary to focus editor for fold command to work @@ -31,13 +42,12 @@ async function foldOrUnfold( await editor.focus(); } - const selectionLines = - lineNumbers ?? editor.selections.map((selection) => selection.start.line); + const lines = ranges.map((range) => range.start.line); await vscode.commands.executeCommand(command, { levels: 1, direction: "down", - selectionLines, + selectionLines: lines, }); // If necessary focus back original editor diff --git a/src/ide/vscode/VscodeTextEditorImpl.ts b/src/ide/vscode/VscodeTextEditorImpl.ts index d333bcac6d..653eee2b42 100644 --- a/src/ide/vscode/VscodeTextEditorImpl.ts +++ b/src/ide/vscode/VscodeTextEditorImpl.ts @@ -126,12 +126,12 @@ export class VscodeTextEditorImpl implements EditableTextEditor { ); } - public fold(lineNumbers?: number[]): Promise { - return vscodeFold(this.ide, this, lineNumbers); + public fold(ranges?: Range[]): Promise { + return vscodeFold(this.ide, this, ranges); } - public unfold(lineNumbers?: number[]): Promise { - return vscodeUnfold(this.ide, this, lineNumbers); + public unfold(ranges?: Range[]): Promise { + return vscodeUnfold(this.ide, this, ranges); } public toggleBreakpoint(ranges?: Range[]): Promise { @@ -150,11 +150,11 @@ export class VscodeTextEditorImpl implements EditableTextEditor { await vscode.commands.executeCommand("editor.action.clipboardPasteAction"); } - public async indentLines(_ranges?: Range[]): Promise { + public async indentLine(_ranges?: Range[]): Promise { await vscode.commands.executeCommand("editor.action.indentLines"); } - public async outdentLines(_ranges?: Range[]): Promise { + public async outdentLine(_ranges?: Range[]): Promise { await vscode.commands.executeCommand("editor.action.outdentLines"); } diff --git a/src/libs/common/ide/fake/FakeCapabilities.ts b/src/libs/common/ide/fake/FakeCapabilities.ts index 306b6f484c..529267a839 100644 --- a/src/libs/common/ide/fake/FakeCapabilities.ts +++ b/src/libs/common/ide/fake/FakeCapabilities.ts @@ -1,8 +1,20 @@ -import { Capabilities, CapabilitiesCommand } from "../types/Capabilities"; -import { CommandId } from "../types/CommandId"; +import { Capabilities } from "../types/Capabilities"; export class FakeCapabilities implements Capabilities { - public getCommand(_commandId: CommandId): CapabilitiesCommand { - throw Error("Not implemented"); - } + commands = { + clipboardCopy: undefined, + toggleLineComment: undefined, + indentLine: undefined, + outdentLine: undefined, + rename: undefined, + quickFix: undefined, + revealDefinition: undefined, + revealTypeDefinition: undefined, + showHover: undefined, + showDebugHover: undefined, + extractVariable: undefined, + fold: undefined, + unfold: undefined, + showReferences: undefined, + }; } diff --git a/src/libs/common/ide/types/Capabilities.ts b/src/libs/common/ide/types/Capabilities.ts index 7b61c766d7..fc34a134d4 100644 --- a/src/libs/common/ide/types/Capabilities.ts +++ b/src/libs/common/ide/types/Capabilities.ts @@ -1,13 +1,14 @@ import { CommandId } from "./CommandId"; export interface Capabilities { - readonly getCommand: (commandId: CommandId) => CapabilitiesCommand; + readonly commands: CommandCapabilityMap; } -export type CapabilitiesCommands = Partial< - Record +export type CommandCapabilityMap = Record< + CommandId, + CommandCapabilities | undefined >; -export interface CapabilitiesCommand { +export interface CommandCapabilities { acceptsLocation: boolean; } diff --git a/src/libs/common/ide/types/CommandId.ts b/src/libs/common/ide/types/CommandId.ts index c863069c6b..c458d03db1 100644 --- a/src/libs/common/ide/types/CommandId.ts +++ b/src/libs/common/ide/types/CommandId.ts @@ -1,5 +1,7 @@ export type CommandId = | "clipboardCopy" + | "fold" + | "unfold" | "toggleLineComment" | "indentLine" | "outdentLine" diff --git a/src/libs/common/types/TextEditor.ts b/src/libs/common/types/TextEditor.ts index 2354ea7047..905a3e489c 100644 --- a/src/libs/common/types/TextEditor.ts +++ b/src/libs/common/types/TextEditor.ts @@ -144,16 +144,16 @@ export interface EditableTextEditor extends TextEditor { openLink(location?: Position | Range): Promise; /** - * Fold lines - * @param lineNumbers Lines to fold + * Fold ranges + * @param ranges A list of {@link Range ranges} */ - fold(lineNumbers?: number[]): Promise; + fold(ranges?: Range[]): Promise; /** - * Unfold lines - * @param lineNumbers Lines to unfold + * Unfold ranges + * @param ranges A list of {@link Range ranges} */ - unfold(lineNumbers?: number[]): Promise; + unfold(ranges?: Range[]): Promise; /** * Copy to clipboard @@ -183,13 +183,13 @@ export interface EditableTextEditor extends TextEditor { * Indent lines * @param ranges A list of {@link Range ranges} */ - indentLines(ranges?: Range[]): Promise; + indentLine(ranges?: Range[]): Promise; /** * Outdent lines * @param ranges A list of {@link Range ranges} */ - outdentLines(ranges?: Range[]): Promise; + outdentLine(ranges?: Range[]): Promise; /** * Insert line after From 10cb41cc5d4ea6f8f25f2e858ee14175dd934825 Mon Sep 17 00:00:00 2001 From: Pokey Rule <755842+pokey@users.noreply.github.com> Date: Mon, 28 Nov 2022 11:18:52 -0800 Subject: [PATCH 31/43] More tweaks --- src/actions/MakeshiftActions.ts | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/actions/MakeshiftActions.ts b/src/actions/MakeshiftActions.ts index d672d1fadc..ad22379200 100644 --- a/src/actions/MakeshiftActions.ts +++ b/src/actions/MakeshiftActions.ts @@ -1,8 +1,4 @@ -import { - CommandCapabilities, - CommandId, - EditableTextEditor, -} from "@cursorless/common"; +import { CommandId, EditableTextEditor } from "@cursorless/common"; import ide from "../libs/cursorless-engine/singletons/ide.singleton"; import { Target } from "../typings/target.types"; import { Graph } from "../typings/Types"; @@ -37,10 +33,16 @@ abstract class MakeshiftAction { throw Error(`Action ${this.command} is not supported by your ide`); } + const { acceptsLocation } = capabilities; + return this.callbackAction.run(targets, { callback: (editor, targets) => - callback(editor, targets, this.command, capabilities), - setSelection: !capabilities.acceptsLocation, + callback( + editor, + acceptsLocation ? targets.map((t) => t.contentRange) : undefined, + this.command, + ), + setSelection: !acceptsLocation, ensureSingleEditor: this.ensureSingleEditor, ensureSingleTarget: this.ensureSingleTarget, restoreSelection: this.restoreSelection, @@ -121,14 +123,9 @@ export class ExtractVariable extends MakeshiftAction { function callback( editor: EditableTextEditor, - targets: Target[], + ranges: Range[] | undefined, command: CommandId, - { acceptsLocation }: CommandCapabilities, ): Promise { - const ranges = acceptsLocation - ? targets.map((t) => t.contentRange) - : undefined; - // Multi target actions switch (command) { case "toggleLineComment": From e8220a9fffa7171514104865907505933306c3cf Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 1 Dec 2022 19:58:37 +0000 Subject: [PATCH 32/43] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/actions/EditNew/EditNew.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/actions/EditNew/EditNew.ts b/src/actions/EditNew/EditNew.ts index db5c1f7d39..e599437b7f 100644 --- a/src/actions/EditNew/EditNew.ts +++ b/src/actions/EditNew/EditNew.ts @@ -43,11 +43,7 @@ export class EditNew implements Action { cursorRanges: new Array(targets.length).fill(undefined) as undefined[], }; - state = await runInsertLineAfterTargets( - this.graph, - editableEditor, - state, - ); + state = await runInsertLineAfterTargets(this.graph, editableEditor, state); state = await runEditTargets(this.graph, editableEditor, state); const newSelections = state.targets.map((target, index) => From 32a0047eff4cb6c0e3d78c59a5736a4fb564c6b1 Mon Sep 17 00:00:00 2001 From: Pokey Rule <755842+pokey@users.noreply.github.com> Date: Thu, 1 Dec 2022 13:03:33 -0700 Subject: [PATCH 33/43] Fix import --- src/actions/MakeshiftActions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/actions/MakeshiftActions.ts b/src/actions/MakeshiftActions.ts index ad22379200..c9c4ad7bea 100644 --- a/src/actions/MakeshiftActions.ts +++ b/src/actions/MakeshiftActions.ts @@ -1,4 +1,4 @@ -import { CommandId, EditableTextEditor } from "@cursorless/common"; +import { CommandId, EditableTextEditor, Range } from "@cursorless/common"; import ide from "../libs/cursorless-engine/singletons/ide.singleton"; import { Target } from "../typings/target.types"; import { Graph } from "../typings/Types"; From a4bea6ab7a2004114de5d45f5d6a92259c303bd6 Mon Sep 17 00:00:00 2001 From: Pokey Rule <755842+pokey@users.noreply.github.com> Date: Thu, 1 Dec 2022 13:08:05 -0700 Subject: [PATCH 34/43] Rename --- src/actions/Actions.ts | 2 +- ...hiftActions.ts => SimpleCommandActions.ts} | 37 +++++++++++-------- 2 files changed, 23 insertions(+), 16 deletions(-) rename src/actions/{MakeshiftActions.ts => SimpleCommandActions.ts} (76%) diff --git a/src/actions/Actions.ts b/src/actions/Actions.ts index addae6866e..d942d2ac02 100644 --- a/src/actions/Actions.ts +++ b/src/actions/Actions.ts @@ -15,7 +15,7 @@ import { CopyToClipboard, Fold, Unfold, -} from "./MakeshiftActions"; +} from "./SimpleCommandActions"; import { Bring, Move, Swap } from "./BringMoveSwap"; import Call from "./Call"; import Clear from "./Clear"; diff --git a/src/actions/MakeshiftActions.ts b/src/actions/SimpleCommandActions.ts similarity index 76% rename from src/actions/MakeshiftActions.ts rename to src/actions/SimpleCommandActions.ts index c9c4ad7bea..7d5aa3cb0f 100644 --- a/src/actions/MakeshiftActions.ts +++ b/src/actions/SimpleCommandActions.ts @@ -9,7 +9,14 @@ interface Options { showDecorations?: boolean; } -abstract class MakeshiftAction { +/** + * This is the base class for actions that simply call an ide command on the + * target. It includes machinery to automatically jump to the target if the + * editor does not support running the command directly on a target without + * moving the cursor. Note that most of the heavy lifting is done by + * {@link CallbackAction}. + */ +abstract class SimpleCommandActions { private callbackAction: CallbackAction; abstract command: CommandId; @@ -51,71 +58,71 @@ abstract class MakeshiftAction { } } -export class CopyToClipboard extends MakeshiftAction { +export class CopyToClipboard extends SimpleCommandActions { command: CommandId = "clipboardCopy"; ensureSingleEditor = true; } -export class ToggleLineComment extends MakeshiftAction { +export class ToggleLineComment extends SimpleCommandActions { command: CommandId = "toggleLineComment"; } -export class IndentLine extends MakeshiftAction { +export class IndentLine extends SimpleCommandActions { command: CommandId = "indentLine"; } -export class OutdentLine extends MakeshiftAction { +export class OutdentLine extends SimpleCommandActions { command: CommandId = "outdentLine"; } -export class Fold extends MakeshiftAction { +export class Fold extends SimpleCommandActions { command: CommandId = "fold"; } -export class Unfold extends MakeshiftAction { +export class Unfold extends SimpleCommandActions { command: CommandId = "unfold"; } -export class Rename extends MakeshiftAction { +export class Rename extends SimpleCommandActions { command: CommandId = "rename"; ensureSingleTarget = true; } -export class ShowReferences extends MakeshiftAction { +export class ShowReferences extends SimpleCommandActions { command: CommandId = "showReferences"; ensureSingleTarget = true; } -export class ShowQuickFix extends MakeshiftAction { +export class ShowQuickFix extends SimpleCommandActions { command: CommandId = "quickFix"; ensureSingleTarget = true; } -export class RevealDefinition extends MakeshiftAction { +export class RevealDefinition extends SimpleCommandActions { command: CommandId = "revealDefinition"; ensureSingleTarget = true; restoreSelection = false; } -export class RevealTypeDefinition extends MakeshiftAction { +export class RevealTypeDefinition extends SimpleCommandActions { command: CommandId = "revealTypeDefinition"; ensureSingleTarget = true; restoreSelection = false; } -export class ShowHover extends MakeshiftAction { +export class ShowHover extends SimpleCommandActions { command: CommandId = "showHover"; ensureSingleTarget = true; restoreSelection = false; } -export class ShowDebugHover extends MakeshiftAction { +export class ShowDebugHover extends SimpleCommandActions { command: CommandId = "showDebugHover"; ensureSingleTarget = true; restoreSelection = false; } -export class ExtractVariable extends MakeshiftAction { +export class ExtractVariable extends SimpleCommandActions { command: CommandId = "extractVariable"; ensureSingleTarget = true; restoreSelection = false; From 28fe23fe4b00d8df7a76ded76d039f419ca84492 Mon Sep 17 00:00:00 2001 From: Pokey Rule <755842+pokey@users.noreply.github.com> Date: Thu, 1 Dec 2022 13:09:49 -0700 Subject: [PATCH 35/43] More rename --- src/actions/Actions.ts | 2 +- ...dActions.ts => SimpleIdeCommandActions.ts} | 30 +++++++++---------- 2 files changed, 16 insertions(+), 16 deletions(-) rename src/actions/{SimpleCommandActions.ts => SimpleIdeCommandActions.ts} (82%) diff --git a/src/actions/Actions.ts b/src/actions/Actions.ts index d942d2ac02..37c7ebc702 100644 --- a/src/actions/Actions.ts +++ b/src/actions/Actions.ts @@ -15,7 +15,7 @@ import { CopyToClipboard, Fold, Unfold, -} from "./SimpleCommandActions"; +} from "./SimpleIdeCommandActions"; import { Bring, Move, Swap } from "./BringMoveSwap"; import Call from "./Call"; import Clear from "./Clear"; diff --git a/src/actions/SimpleCommandActions.ts b/src/actions/SimpleIdeCommandActions.ts similarity index 82% rename from src/actions/SimpleCommandActions.ts rename to src/actions/SimpleIdeCommandActions.ts index 7d5aa3cb0f..51ee5d9270 100644 --- a/src/actions/SimpleCommandActions.ts +++ b/src/actions/SimpleIdeCommandActions.ts @@ -16,7 +16,7 @@ interface Options { * moving the cursor. Note that most of the heavy lifting is done by * {@link CallbackAction}. */ -abstract class SimpleCommandActions { +abstract class SimpleIdeCommandActions { private callbackAction: CallbackAction; abstract command: CommandId; @@ -58,71 +58,71 @@ abstract class SimpleCommandActions { } } -export class CopyToClipboard extends SimpleCommandActions { +export class CopyToClipboard extends SimpleIdeCommandActions { command: CommandId = "clipboardCopy"; ensureSingleEditor = true; } -export class ToggleLineComment extends SimpleCommandActions { +export class ToggleLineComment extends SimpleIdeCommandActions { command: CommandId = "toggleLineComment"; } -export class IndentLine extends SimpleCommandActions { +export class IndentLine extends SimpleIdeCommandActions { command: CommandId = "indentLine"; } -export class OutdentLine extends SimpleCommandActions { +export class OutdentLine extends SimpleIdeCommandActions { command: CommandId = "outdentLine"; } -export class Fold extends SimpleCommandActions { +export class Fold extends SimpleIdeCommandActions { command: CommandId = "fold"; } -export class Unfold extends SimpleCommandActions { +export class Unfold extends SimpleIdeCommandActions { command: CommandId = "unfold"; } -export class Rename extends SimpleCommandActions { +export class Rename extends SimpleIdeCommandActions { command: CommandId = "rename"; ensureSingleTarget = true; } -export class ShowReferences extends SimpleCommandActions { +export class ShowReferences extends SimpleIdeCommandActions { command: CommandId = "showReferences"; ensureSingleTarget = true; } -export class ShowQuickFix extends SimpleCommandActions { +export class ShowQuickFix extends SimpleIdeCommandActions { command: CommandId = "quickFix"; ensureSingleTarget = true; } -export class RevealDefinition extends SimpleCommandActions { +export class RevealDefinition extends SimpleIdeCommandActions { command: CommandId = "revealDefinition"; ensureSingleTarget = true; restoreSelection = false; } -export class RevealTypeDefinition extends SimpleCommandActions { +export class RevealTypeDefinition extends SimpleIdeCommandActions { command: CommandId = "revealTypeDefinition"; ensureSingleTarget = true; restoreSelection = false; } -export class ShowHover extends SimpleCommandActions { +export class ShowHover extends SimpleIdeCommandActions { command: CommandId = "showHover"; ensureSingleTarget = true; restoreSelection = false; } -export class ShowDebugHover extends SimpleCommandActions { +export class ShowDebugHover extends SimpleIdeCommandActions { command: CommandId = "showDebugHover"; ensureSingleTarget = true; restoreSelection = false; } -export class ExtractVariable extends SimpleCommandActions { +export class ExtractVariable extends SimpleIdeCommandActions { command: CommandId = "extractVariable"; ensureSingleTarget = true; restoreSelection = false; From 67694bb334c7cd9f090e3fb2c5590c8947256e96 Mon Sep 17 00:00:00 2001 From: Pokey Rule <755842+pokey@users.noreply.github.com> Date: Sat, 3 Dec 2022 17:00:35 -0700 Subject: [PATCH 36/43] Move `executeCommand` to `ide` --- src/actions/ExecuteCommand.ts | 5 ++--- src/ide/vscode/VscodeIDE.ts | 7 +++++++ src/ide/vscode/VscodeTextEditorImpl.ts | 7 ------- src/libs/common/ide/PassthroughIDEBase.ts | 4 ++++ src/libs/common/ide/fake/FakeIDE.ts | 4 ++++ src/libs/common/ide/types/ide.types.ts | 10 ++++++++++ src/libs/common/types/TextEditor.ts | 13 ------------- 7 files changed, 27 insertions(+), 23 deletions(-) diff --git a/src/actions/ExecuteCommand.ts b/src/actions/ExecuteCommand.ts index 4cb6120c52..b9418816b3 100644 --- a/src/actions/ExecuteCommand.ts +++ b/src/actions/ExecuteCommand.ts @@ -1,4 +1,4 @@ -import { EditableTextEditor } from "../libs/common/types/TextEditor"; +import ide from "../libs/cursorless-engine/singletons/ide.singleton"; import { Target } from "../typings/target.types"; import { Graph } from "../typings/Types"; import { Action, ActionReturnValue } from "./actions.types"; @@ -40,8 +40,7 @@ export default class ExecuteCommand implements Action { const args = commandArgs ?? []; return this.callbackAction.run(targets, { - callback: (editor: EditableTextEditor) => - editor.executeBuiltInCommand(commandId, ...args), + callback: () => ide().executeCommand(commandId, ...args), setSelection: true, ensureSingleEditor: ensureSingleEditor ?? false, ensureSingleTarget: ensureSingleTarget ?? false, diff --git a/src/ide/vscode/VscodeIDE.ts b/src/ide/vscode/VscodeIDE.ts index 0ed21066d3..bf34cca204 100644 --- a/src/ide/vscode/VscodeIDE.ts +++ b/src/ide/vscode/VscodeIDE.ts @@ -90,6 +90,13 @@ export default class VscodeIDE implements IDE { return await vscode.window.showInputBox(options); } + public async executeCommand( + command: string, + ...args: any[] + ): Promise { + return await vscode.commands.executeCommand(command, ...args); + } + public onDidChangeTextDocument( listener: (event: TextDocumentChangeEvent) => void, ): Disposable { diff --git a/src/ide/vscode/VscodeTextEditorImpl.ts b/src/ide/vscode/VscodeTextEditorImpl.ts index 653eee2b42..9e0caed93e 100644 --- a/src/ide/vscode/VscodeTextEditorImpl.ts +++ b/src/ide/vscode/VscodeTextEditorImpl.ts @@ -102,13 +102,6 @@ export class VscodeTextEditorImpl implements EditableTextEditor { return vscodeFocusEditor(this.ide, this); } - public async executeBuiltInCommand( - command: string, - ...args: any[] - ): Promise { - return await vscode.commands.executeCommand(command, ...args); - } - public editNewNotebookCellAbove(): Promise< (selection: Selection) => Selection > { diff --git a/src/libs/common/ide/PassthroughIDEBase.ts b/src/libs/common/ide/PassthroughIDEBase.ts index a8a2626ab6..302bf74499 100644 --- a/src/libs/common/ide/PassthroughIDEBase.ts +++ b/src/libs/common/ide/PassthroughIDEBase.ts @@ -62,6 +62,10 @@ export default class PassthroughIDEBase implements IDE { return this.original.getEditableTextEditor(editor); } + executeCommand(command: string, ...args: any[]): Promise { + return this.original.executeCommand(command, ...args); + } + public onDidChangeTextDocument( listener: (event: TextDocumentChangeEvent) => void, ): Disposable { diff --git a/src/libs/common/ide/fake/FakeIDE.ts b/src/libs/common/ide/fake/FakeIDE.ts index 014f79cd95..dd0d2dc81b 100644 --- a/src/libs/common/ide/fake/FakeIDE.ts +++ b/src/libs/common/ide/fake/FakeIDE.ts @@ -73,6 +73,10 @@ export default class FakeIDE implements IDE { throw Error("Not implemented"); } + executeCommand(_command: string, ..._args: any[]): Promise { + throw new Error("Method not implemented."); + } + public onDidChangeTextDocument( _listener: (event: TextDocumentChangeEvent) => void, ): Disposable { diff --git a/src/libs/common/ide/types/ide.types.ts b/src/libs/common/ide/types/ide.types.ts index 4e6b98b8c9..5a6099f736 100644 --- a/src/libs/common/ide/types/ide.types.ts +++ b/src/libs/common/ide/types/ide.types.ts @@ -106,6 +106,16 @@ export interface IDE { * @return A promise that resolves to a string the user provided or to `undefined` in case of dismissal. */ showInputBox(options?: InputBoxOptions): Promise; + + /** + * Executes the built-in ide command denoted by the given command identifier. + * + * @param command Identifier of the command to execute. + * @param args Parameters passed to the command function. + * @return A promise that resolves to the returned value of the given command. + * `undefined` when the command handler function doesn't return anything. + */ + executeCommand(command: string, ...args: any[]): Promise; } export interface WorkspaceFolder { diff --git a/src/libs/common/types/TextEditor.ts b/src/libs/common/types/TextEditor.ts index 905a3e489c..3ecdc4a4a6 100644 --- a/src/libs/common/types/TextEditor.ts +++ b/src/libs/common/types/TextEditor.ts @@ -109,19 +109,6 @@ export interface EditableTextEditor extends TextEditor { options?: { undoStopBefore: boolean; undoStopAfter: boolean }, ): Promise; - /** - * Executes the built-in ide command denoted by the given command identifier. - * - * @param command Identifier of the command to execute. - * @param args Parameters passed to the command function. - * @return A promise that resolves to the returned value of the given command. - * `undefined` when the command handler function doesn't return anything. - */ - executeBuiltInCommand( - command: string, - ...args: any[] - ): Promise; - /** * Edit a new new notebook cell above. * @return A promise that resolves to a function that must be applied to any From c27cd8304e04081dbed31087cc9f653f4eebb1e9 Mon Sep 17 00:00:00 2001 From: Pokey Rule <755842+pokey@users.noreply.github.com> Date: Sat, 3 Dec 2022 17:04:32 -0700 Subject: [PATCH 37/43] tweak --- src/actions/SimpleIdeCommandActions.ts | 30 +++++++++++++------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/actions/SimpleIdeCommandActions.ts b/src/actions/SimpleIdeCommandActions.ts index 51ee5d9270..a2cc0e93d5 100644 --- a/src/actions/SimpleIdeCommandActions.ts +++ b/src/actions/SimpleIdeCommandActions.ts @@ -16,7 +16,7 @@ interface Options { * moving the cursor. Note that most of the heavy lifting is done by * {@link CallbackAction}. */ -abstract class SimpleIdeCommandActions { +abstract class SimpleIdeCommandAction { private callbackAction: CallbackAction; abstract command: CommandId; @@ -58,71 +58,71 @@ abstract class SimpleIdeCommandActions { } } -export class CopyToClipboard extends SimpleIdeCommandActions { +export class CopyToClipboard extends SimpleIdeCommandAction { command: CommandId = "clipboardCopy"; ensureSingleEditor = true; } -export class ToggleLineComment extends SimpleIdeCommandActions { +export class ToggleLineComment extends SimpleIdeCommandAction { command: CommandId = "toggleLineComment"; } -export class IndentLine extends SimpleIdeCommandActions { +export class IndentLine extends SimpleIdeCommandAction { command: CommandId = "indentLine"; } -export class OutdentLine extends SimpleIdeCommandActions { +export class OutdentLine extends SimpleIdeCommandAction { command: CommandId = "outdentLine"; } -export class Fold extends SimpleIdeCommandActions { +export class Fold extends SimpleIdeCommandAction { command: CommandId = "fold"; } -export class Unfold extends SimpleIdeCommandActions { +export class Unfold extends SimpleIdeCommandAction { command: CommandId = "unfold"; } -export class Rename extends SimpleIdeCommandActions { +export class Rename extends SimpleIdeCommandAction { command: CommandId = "rename"; ensureSingleTarget = true; } -export class ShowReferences extends SimpleIdeCommandActions { +export class ShowReferences extends SimpleIdeCommandAction { command: CommandId = "showReferences"; ensureSingleTarget = true; } -export class ShowQuickFix extends SimpleIdeCommandActions { +export class ShowQuickFix extends SimpleIdeCommandAction { command: CommandId = "quickFix"; ensureSingleTarget = true; } -export class RevealDefinition extends SimpleIdeCommandActions { +export class RevealDefinition extends SimpleIdeCommandAction { command: CommandId = "revealDefinition"; ensureSingleTarget = true; restoreSelection = false; } -export class RevealTypeDefinition extends SimpleIdeCommandActions { +export class RevealTypeDefinition extends SimpleIdeCommandAction { command: CommandId = "revealTypeDefinition"; ensureSingleTarget = true; restoreSelection = false; } -export class ShowHover extends SimpleIdeCommandActions { +export class ShowHover extends SimpleIdeCommandAction { command: CommandId = "showHover"; ensureSingleTarget = true; restoreSelection = false; } -export class ShowDebugHover extends SimpleIdeCommandActions { +export class ShowDebugHover extends SimpleIdeCommandAction { command: CommandId = "showDebugHover"; ensureSingleTarget = true; restoreSelection = false; } -export class ExtractVariable extends SimpleIdeCommandActions { +export class ExtractVariable extends SimpleIdeCommandAction { command: CommandId = "extractVariable"; ensureSingleTarget = true; restoreSelection = false; From e11f455c73004540e0673be6bf44babb8982d2ad Mon Sep 17 00:00:00 2001 From: Pokey Rule <755842+pokey@users.noreply.github.com> Date: Wed, 7 Dec 2022 15:04:39 +0000 Subject: [PATCH 38/43] Rework breakpoint implementation --- src/actions/ToggleBreakpoint.ts | 29 +++++++++++------ src/ide/vscode/VscodeTextEditorImpl.ts | 5 +-- src/ide/vscode/VscodeToggleBreakpoint.ts | 40 ++++++++++++++++++------ src/libs/common/types/TextEditor.ts | 27 ++++++++++++++-- 4 files changed, 77 insertions(+), 24 deletions(-) diff --git a/src/actions/ToggleBreakpoint.ts b/src/actions/ToggleBreakpoint.ts index e089e7f1c1..85a8e73b79 100644 --- a/src/actions/ToggleBreakpoint.ts +++ b/src/actions/ToggleBreakpoint.ts @@ -1,4 +1,4 @@ -import { Range } from "@cursorless/common"; +import { BreakpointDescriptor } from "../libs/common/types/TextEditor"; import ide from "../libs/cursorless-engine/singletons/ide.singleton"; import { containingLineIfUntypedStage } from "../processTargets/modifiers/commonContainingScopeIfUntypedStages"; import { Target } from "../typings/target.types"; @@ -22,16 +22,25 @@ export default class ToggleBreakpoint implements Action { ); await runOnTargetsForEachEditor(targets, async (editor, targets) => { - const ranges = targets.map((target) => { - const range = target.contentRange; - // The action preference give us line content but line breakpoints are registered on character 0 - if (target.isLine) { - return new Range(range.start.line, 0, range.end.line, 0); - } - return range; - }); + const breakpointDescriptors: BreakpointDescriptor[] = targets.map( + (target) => { + const range = target.contentRange; + return target.isLine + ? { + type: "line", + startLine: range.start.line, + endLine: range.end.line, + } + : { + type: "inline", + range, + }; + }, + ); - await ide().getEditableTextEditor(editor).toggleBreakpoint(ranges); + await ide() + .getEditableTextEditor(editor) + .toggleBreakpoint(breakpointDescriptors); }); return { diff --git a/src/ide/vscode/VscodeTextEditorImpl.ts b/src/ide/vscode/VscodeTextEditorImpl.ts index 9e0caed93e..ee9d242db8 100644 --- a/src/ide/vscode/VscodeTextEditorImpl.ts +++ b/src/ide/vscode/VscodeTextEditorImpl.ts @@ -1,4 +1,5 @@ import { + BreakpointDescriptor, EditableTextEditor, Position, Range, @@ -127,8 +128,8 @@ export class VscodeTextEditorImpl implements EditableTextEditor { return vscodeUnfold(this.ide, this, ranges); } - public toggleBreakpoint(ranges?: Range[]): Promise { - return vscodeToggleBreakpoint(this, ranges); + public toggleBreakpoint(descriptors?: BreakpointDescriptor[]): Promise { + return vscodeToggleBreakpoint(this, descriptors); } public async toggleLineComment(_ranges?: Range[]): Promise { diff --git a/src/ide/vscode/VscodeToggleBreakpoint.ts b/src/ide/vscode/VscodeToggleBreakpoint.ts index 336f436ca3..5d50a039d6 100644 --- a/src/ide/vscode/VscodeToggleBreakpoint.ts +++ b/src/ide/vscode/VscodeToggleBreakpoint.ts @@ -1,25 +1,36 @@ -import { Range } from "@cursorless/common"; +import { BreakpointDescriptor } from "@cursorless/common"; import { toVscodeRange } from "@cursorless/vscode-common"; import * as vscode from "vscode"; import { VscodeTextEditorImpl } from "./VscodeTextEditorImpl"; export async function vscodeToggleBreakpoint( editor: VscodeTextEditorImpl, - ranges: Range[] | undefined, + descriptors: BreakpointDescriptor[] | undefined, ): Promise { + if (descriptors == null) { + return await vscode.commands.executeCommand( + "editor.debug.action.toggleBreakpoint", + ); + } + const uri = editor.document.uri; const toAdd: vscode.Breakpoint[] = []; const toRemove: vscode.Breakpoint[] = []; - const actualRanges = ranges ?? editor.selections; - actualRanges.forEach((range) => { - const vscodeRange = toVscodeRange(range); - const existing = getBreakpoints(uri, vscodeRange); + descriptors.forEach((descriptor) => { + const existing = getBreakpoints(uri, descriptor); if (existing.length > 0) { toRemove.push(...existing); } else { toAdd.push( - new vscode.SourceBreakpoint(new vscode.Location(uri, vscodeRange)), + new vscode.SourceBreakpoint( + new vscode.Location( + uri, + descriptor.type === "line" + ? new vscode.Range(descriptor.startLine, 0, descriptor.endLine, 0) + : toVscodeRange(descriptor.range), + ), + ), ); } }); @@ -28,11 +39,22 @@ export async function vscodeToggleBreakpoint( vscode.debug.removeBreakpoints(toRemove); } -function getBreakpoints(uri: vscode.Uri, range: vscode.Range) { +function getBreakpoints(uri: vscode.Uri, descriptor: BreakpointDescriptor) { + let rangeInterceptsDescriptor: (range: vscode.Range) => boolean; + + if (descriptor.type === "line") { + rangeInterceptsDescriptor = ({ start, end }) => + descriptor.startLine <= end.line && descriptor.endLine >= start.line; + } else { + const descriptorRange = toVscodeRange(descriptor.range); + rangeInterceptsDescriptor = (range) => + range.intersection(descriptorRange) != null; + } + return vscode.debug.breakpoints.filter( (breakpoint) => breakpoint instanceof vscode.SourceBreakpoint && breakpoint.location.uri.toString() === uri.toString() && - breakpoint.location.range.intersection(range) != null, + rangeInterceptsDescriptor(breakpoint.location.range), ); } diff --git a/src/libs/common/types/TextEditor.ts b/src/libs/common/types/TextEditor.ts index 3ecdc4a4a6..a5f64594a8 100644 --- a/src/libs/common/types/TextEditor.ts +++ b/src/libs/common/types/TextEditor.ts @@ -155,10 +155,13 @@ export interface EditableTextEditor extends TextEditor { clipboardPaste(ranges?: Range[]): Promise; /** - * Toggle breakpoints - * @param ranges A list of {@link Range ranges} + * Toggle breakpoints. For each of the descriptors in {@link descriptors}, + * remove all breakpoints overlapping with the given descriptor if it overlaps + * with any existing breakpoint, otherwise add a new breakpoint at the given + * location. + * @param descriptors A list of breakpoint descriptors */ - toggleBreakpoint(ranges?: Range[]): Promise; + toggleBreakpoint(descriptors?: BreakpointDescriptor[]): Promise; /** * Toggle line comments @@ -239,3 +242,21 @@ export interface EditableTextEditor extends TextEditor { */ extractVariable(range?: Range): Promise; } + +interface LineBreakpointDescriptor { + type: "line"; + startLine: number; + /** + * Last line, inclusive + */ + endLine: number; +} + +interface InlineBreakpointDescriptor { + type: "inline"; + range: Range; +} + +export type BreakpointDescriptor = + | LineBreakpointDescriptor + | InlineBreakpointDescriptor; From bb874b3b8a88b2642b25e95e3b12c17ab2a02753 Mon Sep 17 00:00:00 2001 From: Pokey Rule <755842+pokey@users.noreply.github.com> Date: Wed, 7 Dec 2022 15:14:20 +0000 Subject: [PATCH 39/43] Switch `DecorationRangeBehavior` to enum --- src/actions/BringMoveSwap.ts | 14 ++++++-- src/actions/EditNew/runEditTargets.ts | 8 +++-- src/actions/InsertCopy.ts | 8 +++-- src/actions/InsertSnippet.ts | 7 +++- src/actions/PasteFromClipboard.ts | 3 +- src/actions/Wrap.ts | 34 +++++++++++++++---- src/core/updateSelections/updateSelections.ts | 12 +++---- .../common/types/DecorationRangeBehavior.ts | 14 ++++---- 8 files changed, 70 insertions(+), 30 deletions(-) diff --git a/src/actions/BringMoveSwap.ts b/src/actions/BringMoveSwap.ts index 0846d05caa..92f398826e 100644 --- a/src/actions/BringMoveSwap.ts +++ b/src/actions/BringMoveSwap.ts @@ -1,4 +1,8 @@ -import { Selection, TextEditor } from "@cursorless/common"; +import { + DecorationRangeBehavior, + Selection, + TextEditor, +} from "@cursorless/common"; import { flatten } from "lodash"; import { getSelectionInfo, @@ -168,12 +172,16 @@ class BringMoveSwap implements Action { getSelectionInfo( editor.document, range.toSelection(originalTarget.isReversed), - "OpenOpen", + DecorationRangeBehavior.openOpen, ), ); const cursorSelectionInfos = editor.selections.map((selection) => - getSelectionInfo(editor.document, selection, "ClosedClosed"), + getSelectionInfo( + editor.document, + selection, + DecorationRangeBehavior.closedClosed, + ), ); const editableEditor = ide().getEditableTextEditor(editor); diff --git a/src/actions/EditNew/runEditTargets.ts b/src/actions/EditNew/runEditTargets.ts index a23b22001b..9dded24622 100644 --- a/src/actions/EditNew/runEditTargets.ts +++ b/src/actions/EditNew/runEditTargets.ts @@ -1,4 +1,8 @@ -import { Selection, EditableTextEditor } from "@cursorless/common"; +import { + DecorationRangeBehavior, + EditableTextEditor, + Selection, +} from "@cursorless/common"; import { zip } from "lodash"; import { performEditsAndUpdateSelectionsWithBehavior } from "../../core/updateSelections/updateSelections"; import { Graph } from "../../typings/Types"; @@ -60,7 +64,7 @@ export async function runEditTargets( const editSelections = { selections: edits.map((edit) => edit.range.toSelection(false)), - rangeBehavior: "OpenOpen", + rangeBehavior: DecorationRangeBehavior.openOpen, }; const [ diff --git a/src/actions/InsertCopy.ts b/src/actions/InsertCopy.ts index d91b50f92a..f1a8b2a4b7 100644 --- a/src/actions/InsertCopy.ts +++ b/src/actions/InsertCopy.ts @@ -1,4 +1,8 @@ -import { Selection, TextEditor } from "@cursorless/common"; +import { + DecorationRangeBehavior, + Selection, + TextEditor, +} from "@cursorless/common"; import { flatten, zip } from "lodash"; import { performEditsAndUpdateSelectionsWithBehavior } from "../core/updateSelections/updateSelections"; import ide from "../libs/cursorless-engine/singletons/ide.singleton"; @@ -54,7 +58,7 @@ class InsertCopy implements Action { selections: edits.map( ({ range }) => new Selection(range.start, range.end), ), - rangeBehavior: "OpenOpen", + rangeBehavior: DecorationRangeBehavior.openOpen, }; const editableEditor = ide().getEditableTextEditor(editor); diff --git a/src/actions/InsertSnippet.ts b/src/actions/InsertSnippet.ts index 12d06bb077..52a49b9804 100644 --- a/src/actions/InsertSnippet.ts +++ b/src/actions/InsertSnippet.ts @@ -1,3 +1,4 @@ +import { DecorationRangeBehavior } from "@cursorless/common"; import textFormatters from "../core/textFormatters"; import { callFunctionAndUpdateSelectionInfos, @@ -76,7 +77,11 @@ export default class InsertSnippet implements Action { await this.graph.actions.editNew.run([targets]); const targetSelectionInfos = editor.selections.map((selection) => - getSelectionInfo(editor.document, selection, "OpenOpen"), + getSelectionInfo( + editor.document, + selection, + DecorationRangeBehavior.openOpen, + ), ); // NB: We used the command "editor.action.insertSnippet" instead of calling editor.insertSnippet diff --git a/src/actions/PasteFromClipboard.ts b/src/actions/PasteFromClipboard.ts index 4b45acb2b9..acf218c5fe 100644 --- a/src/actions/PasteFromClipboard.ts +++ b/src/actions/PasteFromClipboard.ts @@ -1,3 +1,4 @@ +import { DecorationRangeBehavior } from "@cursorless/common"; import { callFunctionAndUpdateSelections, callFunctionAndUpdateSelectionsWithBehavior, @@ -41,7 +42,7 @@ export class PasteFromClipboard { }, { selections: editor.selections, - rangeBehavior: "OpenOpen", + rangeBehavior: DecorationRangeBehavior.openOpen, }, ], ); diff --git a/src/actions/Wrap.ts b/src/actions/Wrap.ts index a87c6f795f..2444bc82ba 100644 --- a/src/actions/Wrap.ts +++ b/src/actions/Wrap.ts @@ -1,7 +1,7 @@ -import { Selection } from "@cursorless/common"; +import { DecorationRangeBehavior, Selection } from "@cursorless/common"; import { getSelectionInfo, - performEditsAndUpdateFullSelectionInfos, + performEditsAndUpdateFullSelectionInfos } from "../core/updateSelections/updateSelections"; import ide from "../libs/cursorless-engine/singletons/ide.singleton"; import { Target } from "../typings/target.types"; @@ -48,22 +48,42 @@ export default class Wrap implements Action { const delimiterSelectionInfos: FullSelectionInfo[] = boundaries.flatMap( ({ start, end }) => { return [ - getSelectionInfo(document, start, "OpenClosed"), - getSelectionInfo(document, end, "ClosedOpen"), + getSelectionInfo( + document, + start, + DecorationRangeBehavior.openClosed, + ), + getSelectionInfo( + document, + end, + DecorationRangeBehavior.closedOpen, + ), ]; }, ); const cursorSelectionInfos = editor.selections.map((selection) => - getSelectionInfo(document, selection, "ClosedClosed"), + getSelectionInfo( + document, + selection, + DecorationRangeBehavior.closedClosed, + ), ); const sourceMarkSelectionInfos = targets.map((target) => - getSelectionInfo(document, target.contentSelection, "ClosedClosed"), + getSelectionInfo( + document, + target.contentSelection, + DecorationRangeBehavior.closedClosed, + ), ); const thatMarkSelectionInfos = targets.map((target) => - getSelectionInfo(document, target.contentSelection, "OpenOpen"), + getSelectionInfo( + document, + target.contentSelection, + DecorationRangeBehavior.openOpen, + ), ); const editableEditor = ide().getEditableTextEditor(editor); diff --git a/src/core/updateSelections/updateSelections.ts b/src/core/updateSelections/updateSelections.ts index 7121f98eb7..05d42504a3 100644 --- a/src/core/updateSelections/updateSelections.ts +++ b/src/core/updateSelections/updateSelections.ts @@ -53,13 +53,13 @@ function getSelectionInfoInternal( expansionBehavior: { start: { type: - rangeBehavior === "ClosedClosed" || rangeBehavior === "ClosedOpen" + rangeBehavior === DecorationRangeBehavior.closedClosed || rangeBehavior === DecorationRangeBehavior.closedOpen ? "closed" : "open", }, end: { type: - rangeBehavior === "ClosedClosed" || rangeBehavior === "OpenClosed" + rangeBehavior === DecorationRangeBehavior.closedClosed || rangeBehavior === DecorationRangeBehavior.openClosed ? "closed" : "open", }, @@ -83,7 +83,7 @@ function getSelectionInfoInternal( function selectionsToSelectionInfos( document: TextDocument, selectionMatrix: (readonly Selection[])[], - rangeBehavior: DecorationRangeBehavior = "ClosedClosed", + rangeBehavior: DecorationRangeBehavior = DecorationRangeBehavior.closedClosed, ): FullSelectionInfo[][] { return selectionMatrix.map((selections) => selections.map((selection) => @@ -95,7 +95,7 @@ function selectionsToSelectionInfos( function rangesToSelectionInfos( document: TextDocument, rangeMatrix: (readonly Range[])[], - rangeBehavior: DecorationRangeBehavior = "ClosedClosed", + rangeBehavior: DecorationRangeBehavior = DecorationRangeBehavior.closedClosed, ): FullSelectionInfo[][] { return rangeMatrix.map((ranges) => ranges.map((range) => @@ -228,7 +228,7 @@ export function callFunctionAndUpdateSelectionsWithBehavior( getSelectionInfo( document, selection, - selectionsWithBehavior.rangeBehavior ?? "ClosedClosed", + selectionsWithBehavior.rangeBehavior ?? DecorationRangeBehavior.closedClosed, ), ), ), @@ -288,7 +288,7 @@ export function performEditsAndUpdateSelectionsWithBehavior( getSelectionInfo( editor.document, selection, - selectionsWithBehavior.rangeBehavior ?? "ClosedClosed", + selectionsWithBehavior.rangeBehavior ?? DecorationRangeBehavior.closedClosed, ), ), ), diff --git a/src/libs/common/types/DecorationRangeBehavior.ts b/src/libs/common/types/DecorationRangeBehavior.ts index 0bacf32481..897317b13c 100644 --- a/src/libs/common/types/DecorationRangeBehavior.ts +++ b/src/libs/common/types/DecorationRangeBehavior.ts @@ -1,23 +1,21 @@ /** * Describes the behavior of decorations when typing/editing at their edges. */ -export type DecorationRangeBehavior = +export enum DecorationRangeBehavior { /** * The decoration's range will widen when edits occur at the start or end. */ - | "OpenOpen" - + openOpen = 0, /** * The decoration's range will not widen when edits occur at the start of end. */ - | "ClosedClosed" - + closedClosed = 1, /** * The decoration's range will widen when edits occur at the start, but not at the end. */ - | "OpenClosed" - + openClosed = 2, /** * The decoration's range will widen when edits occur at the end, but not at the start. */ - | "ClosedOpen"; + closedOpen = 3, +} From 1d93c986d62e9319c608de49a37551870896b5c5 Mon Sep 17 00:00:00 2001 From: Pokey Rule <755842+pokey@users.noreply.github.com> Date: Wed, 7 Dec 2022 15:17:22 +0000 Subject: [PATCH 40/43] Rename `DecorationRangeBehavior` to `RangeExpansionBehavior` --- src/actions/BringMoveSwap.ts | 6 +++--- src/actions/EditNew/runEditTargets.ts | 4 ++-- src/actions/InsertCopy.ts | 4 ++-- src/actions/InsertSnippet.ts | 4 ++-- src/actions/PasteFromClipboard.ts | 4 ++-- src/actions/Wrap.ts | 12 +++++------ src/core/updateSelections/updateSelections.ts | 20 +++++++++---------- src/libs/common/index.ts | 2 +- ...eBehavior.ts => RangeExpansionBehavior.ts} | 4 ++-- 9 files changed, 30 insertions(+), 30 deletions(-) rename src/libs/common/types/{DecorationRangeBehavior.ts => RangeExpansionBehavior.ts} (80%) diff --git a/src/actions/BringMoveSwap.ts b/src/actions/BringMoveSwap.ts index 92f398826e..bac206db37 100644 --- a/src/actions/BringMoveSwap.ts +++ b/src/actions/BringMoveSwap.ts @@ -1,5 +1,5 @@ import { - DecorationRangeBehavior, + RangeExpansionBehavior, Selection, TextEditor, } from "@cursorless/common"; @@ -172,7 +172,7 @@ class BringMoveSwap implements Action { getSelectionInfo( editor.document, range.toSelection(originalTarget.isReversed), - DecorationRangeBehavior.openOpen, + RangeExpansionBehavior.openOpen, ), ); @@ -180,7 +180,7 @@ class BringMoveSwap implements Action { getSelectionInfo( editor.document, selection, - DecorationRangeBehavior.closedClosed, + RangeExpansionBehavior.closedClosed, ), ); diff --git a/src/actions/EditNew/runEditTargets.ts b/src/actions/EditNew/runEditTargets.ts index 9dded24622..a640f61111 100644 --- a/src/actions/EditNew/runEditTargets.ts +++ b/src/actions/EditNew/runEditTargets.ts @@ -1,5 +1,5 @@ import { - DecorationRangeBehavior, + RangeExpansionBehavior, EditableTextEditor, Selection, } from "@cursorless/common"; @@ -64,7 +64,7 @@ export async function runEditTargets( const editSelections = { selections: edits.map((edit) => edit.range.toSelection(false)), - rangeBehavior: DecorationRangeBehavior.openOpen, + rangeBehavior: RangeExpansionBehavior.openOpen, }; const [ diff --git a/src/actions/InsertCopy.ts b/src/actions/InsertCopy.ts index f1a8b2a4b7..fd392c62e8 100644 --- a/src/actions/InsertCopy.ts +++ b/src/actions/InsertCopy.ts @@ -1,5 +1,5 @@ import { - DecorationRangeBehavior, + RangeExpansionBehavior, Selection, TextEditor, } from "@cursorless/common"; @@ -58,7 +58,7 @@ class InsertCopy implements Action { selections: edits.map( ({ range }) => new Selection(range.start, range.end), ), - rangeBehavior: DecorationRangeBehavior.openOpen, + rangeBehavior: RangeExpansionBehavior.openOpen, }; const editableEditor = ide().getEditableTextEditor(editor); diff --git a/src/actions/InsertSnippet.ts b/src/actions/InsertSnippet.ts index 52a49b9804..0006283d5d 100644 --- a/src/actions/InsertSnippet.ts +++ b/src/actions/InsertSnippet.ts @@ -1,4 +1,4 @@ -import { DecorationRangeBehavior } from "@cursorless/common"; +import { RangeExpansionBehavior } from "@cursorless/common"; import textFormatters from "../core/textFormatters"; import { callFunctionAndUpdateSelectionInfos, @@ -80,7 +80,7 @@ export default class InsertSnippet implements Action { getSelectionInfo( editor.document, selection, - DecorationRangeBehavior.openOpen, + RangeExpansionBehavior.openOpen, ), ); diff --git a/src/actions/PasteFromClipboard.ts b/src/actions/PasteFromClipboard.ts index acf218c5fe..3dc6c04739 100644 --- a/src/actions/PasteFromClipboard.ts +++ b/src/actions/PasteFromClipboard.ts @@ -1,4 +1,4 @@ -import { DecorationRangeBehavior } from "@cursorless/common"; +import { RangeExpansionBehavior } from "@cursorless/common"; import { callFunctionAndUpdateSelections, callFunctionAndUpdateSelectionsWithBehavior, @@ -42,7 +42,7 @@ export class PasteFromClipboard { }, { selections: editor.selections, - rangeBehavior: DecorationRangeBehavior.openOpen, + rangeBehavior: RangeExpansionBehavior.openOpen, }, ], ); diff --git a/src/actions/Wrap.ts b/src/actions/Wrap.ts index 2444bc82ba..f6e064a681 100644 --- a/src/actions/Wrap.ts +++ b/src/actions/Wrap.ts @@ -1,4 +1,4 @@ -import { DecorationRangeBehavior, Selection } from "@cursorless/common"; +import { RangeExpansionBehavior, Selection } from "@cursorless/common"; import { getSelectionInfo, performEditsAndUpdateFullSelectionInfos @@ -51,12 +51,12 @@ export default class Wrap implements Action { getSelectionInfo( document, start, - DecorationRangeBehavior.openClosed, + RangeExpansionBehavior.openClosed, ), getSelectionInfo( document, end, - DecorationRangeBehavior.closedOpen, + RangeExpansionBehavior.closedOpen, ), ]; }, @@ -66,7 +66,7 @@ export default class Wrap implements Action { getSelectionInfo( document, selection, - DecorationRangeBehavior.closedClosed, + RangeExpansionBehavior.closedClosed, ), ); @@ -74,7 +74,7 @@ export default class Wrap implements Action { getSelectionInfo( document, target.contentSelection, - DecorationRangeBehavior.closedClosed, + RangeExpansionBehavior.closedClosed, ), ); @@ -82,7 +82,7 @@ export default class Wrap implements Action { getSelectionInfo( document, target.contentSelection, - DecorationRangeBehavior.openOpen, + RangeExpansionBehavior.openOpen, ), ); diff --git a/src/core/updateSelections/updateSelections.ts b/src/core/updateSelections/updateSelections.ts index 05d42504a3..efe9a462e1 100644 --- a/src/core/updateSelections/updateSelections.ts +++ b/src/core/updateSelections/updateSelections.ts @@ -1,5 +1,5 @@ import { - DecorationRangeBehavior, + RangeExpansionBehavior, EditableTextEditor, Range, Selection, @@ -16,7 +16,7 @@ import { RangeUpdater } from "./RangeUpdater"; interface SelectionsWithBehavior { selections: readonly Selection[]; - rangeBehavior?: DecorationRangeBehavior; + rangeBehavior?: RangeExpansionBehavior; } /** @@ -31,7 +31,7 @@ interface SelectionsWithBehavior { export function getSelectionInfo( document: TextDocument, selection: Selection, - rangeBehavior: DecorationRangeBehavior, + rangeBehavior: RangeExpansionBehavior, ): FullSelectionInfo { return getSelectionInfoInternal( document, @@ -45,7 +45,7 @@ function getSelectionInfoInternal( document: TextDocument, range: Range, isForward: boolean, - rangeBehavior: DecorationRangeBehavior, + rangeBehavior: RangeExpansionBehavior, ): FullSelectionInfo { return { range, @@ -53,13 +53,13 @@ function getSelectionInfoInternal( expansionBehavior: { start: { type: - rangeBehavior === DecorationRangeBehavior.closedClosed || rangeBehavior === DecorationRangeBehavior.closedOpen + rangeBehavior === RangeExpansionBehavior.closedClosed || rangeBehavior === RangeExpansionBehavior.closedOpen ? "closed" : "open", }, end: { type: - rangeBehavior === DecorationRangeBehavior.closedClosed || rangeBehavior === DecorationRangeBehavior.openClosed + rangeBehavior === RangeExpansionBehavior.closedClosed || rangeBehavior === RangeExpansionBehavior.openClosed ? "closed" : "open", }, @@ -83,7 +83,7 @@ function getSelectionInfoInternal( function selectionsToSelectionInfos( document: TextDocument, selectionMatrix: (readonly Selection[])[], - rangeBehavior: DecorationRangeBehavior = DecorationRangeBehavior.closedClosed, + rangeBehavior: RangeExpansionBehavior = RangeExpansionBehavior.closedClosed, ): FullSelectionInfo[][] { return selectionMatrix.map((selections) => selections.map((selection) => @@ -95,7 +95,7 @@ function selectionsToSelectionInfos( function rangesToSelectionInfos( document: TextDocument, rangeMatrix: (readonly Range[])[], - rangeBehavior: DecorationRangeBehavior = DecorationRangeBehavior.closedClosed, + rangeBehavior: RangeExpansionBehavior = RangeExpansionBehavior.closedClosed, ): FullSelectionInfo[][] { return rangeMatrix.map((ranges) => ranges.map((range) => @@ -228,7 +228,7 @@ export function callFunctionAndUpdateSelectionsWithBehavior( getSelectionInfo( document, selection, - selectionsWithBehavior.rangeBehavior ?? DecorationRangeBehavior.closedClosed, + selectionsWithBehavior.rangeBehavior ?? RangeExpansionBehavior.closedClosed, ), ), ), @@ -288,7 +288,7 @@ export function performEditsAndUpdateSelectionsWithBehavior( getSelectionInfo( editor.document, selection, - selectionsWithBehavior.rangeBehavior ?? DecorationRangeBehavior.closedClosed, + selectionsWithBehavior.rangeBehavior ?? RangeExpansionBehavior.closedClosed, ), ), ), diff --git a/src/libs/common/index.ts b/src/libs/common/index.ts index 0dc5f91944..ce33818395 100644 --- a/src/libs/common/index.ts +++ b/src/libs/common/index.ts @@ -19,7 +19,7 @@ export { TokenHatSplittingMode } from "./ide/types/Configuration"; export * from "./ide/types/ide.types"; export * from "./ide/types/Capabilities"; export * from "./ide/types/CommandId"; -export * from "./types/DecorationRangeBehavior"; +export * from "./types/RangeExpansionBehavior"; export * from "./types/InputBoxOptions"; export * from "./types/Position"; export * from "./types/Range"; diff --git a/src/libs/common/types/DecorationRangeBehavior.ts b/src/libs/common/types/RangeExpansionBehavior.ts similarity index 80% rename from src/libs/common/types/DecorationRangeBehavior.ts rename to src/libs/common/types/RangeExpansionBehavior.ts index 897317b13c..5a82df4fa0 100644 --- a/src/libs/common/types/DecorationRangeBehavior.ts +++ b/src/libs/common/types/RangeExpansionBehavior.ts @@ -1,7 +1,7 @@ /** - * Describes the behavior of decorations when typing/editing at their edges. + * Describes the behavior of ranges when typing/editing at their edges. */ -export enum DecorationRangeBehavior { +export enum RangeExpansionBehavior { /** * The decoration's range will widen when edits occur at the start or end. */ From a0a073654449a6335bb1624d41a2a191b707efb8 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 7 Dec 2022 15:18:52 +0000 Subject: [PATCH 41/43] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/actions/Wrap.ts | 2 +- src/core/updateSelections/updateSelections.ts | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/actions/Wrap.ts b/src/actions/Wrap.ts index f6e064a681..e175d22ff5 100644 --- a/src/actions/Wrap.ts +++ b/src/actions/Wrap.ts @@ -1,7 +1,7 @@ import { RangeExpansionBehavior, Selection } from "@cursorless/common"; import { getSelectionInfo, - performEditsAndUpdateFullSelectionInfos + performEditsAndUpdateFullSelectionInfos, } from "../core/updateSelections/updateSelections"; import ide from "../libs/cursorless-engine/singletons/ide.singleton"; import { Target } from "../typings/target.types"; diff --git a/src/core/updateSelections/updateSelections.ts b/src/core/updateSelections/updateSelections.ts index efe9a462e1..87547ba380 100644 --- a/src/core/updateSelections/updateSelections.ts +++ b/src/core/updateSelections/updateSelections.ts @@ -53,13 +53,15 @@ function getSelectionInfoInternal( expansionBehavior: { start: { type: - rangeBehavior === RangeExpansionBehavior.closedClosed || rangeBehavior === RangeExpansionBehavior.closedOpen + rangeBehavior === RangeExpansionBehavior.closedClosed || + rangeBehavior === RangeExpansionBehavior.closedOpen ? "closed" : "open", }, end: { type: - rangeBehavior === RangeExpansionBehavior.closedClosed || rangeBehavior === RangeExpansionBehavior.openClosed + rangeBehavior === RangeExpansionBehavior.closedClosed || + rangeBehavior === RangeExpansionBehavior.openClosed ? "closed" : "open", }, @@ -228,7 +230,8 @@ export function callFunctionAndUpdateSelectionsWithBehavior( getSelectionInfo( document, selection, - selectionsWithBehavior.rangeBehavior ?? RangeExpansionBehavior.closedClosed, + selectionsWithBehavior.rangeBehavior ?? + RangeExpansionBehavior.closedClosed, ), ), ), @@ -288,7 +291,8 @@ export function performEditsAndUpdateSelectionsWithBehavior( getSelectionInfo( editor.document, selection, - selectionsWithBehavior.rangeBehavior ?? RangeExpansionBehavior.closedClosed, + selectionsWithBehavior.rangeBehavior ?? + RangeExpansionBehavior.closedClosed, ), ), ), From 87828b7f2931df4e8d85ed014affc14f03f5371c Mon Sep 17 00:00:00 2001 From: Pokey Rule <755842+pokey@users.noreply.github.com> Date: Wed, 7 Dec 2022 15:31:07 +0000 Subject: [PATCH 42/43] Switch `RevealLineAt` to enum --- src/actions/Scroll.ts | 14 +++++++------- src/ide/vscode/VscodeRevealLine.ts | 7 ++++++- src/libs/common/index.ts | 2 +- src/libs/common/types/RevealLineAt.ts | 12 ++++++++---- 4 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/actions/Scroll.ts b/src/actions/Scroll.ts index 688f489c1d..a14f2900de 100644 --- a/src/actions/Scroll.ts +++ b/src/actions/Scroll.ts @@ -1,4 +1,4 @@ -import type { RevealLineAt } from "@cursorless/common"; +import { RevealLineAt } from "@cursorless/common"; import ide from "../libs/cursorless-engine/singletons/ide.singleton"; import { Target } from "../typings/target.types"; import { Graph } from "../typings/Types"; @@ -57,23 +57,23 @@ class Scroll implements Action { export class ScrollToTop extends Scroll { constructor(graph: Graph) { - super(graph, "top"); + super(graph, RevealLineAt.top); } } export class ScrollToCenter extends Scroll { constructor(graph: Graph) { - super(graph, "center"); + super(graph, RevealLineAt.center); } } export class ScrollToBottom extends Scroll { constructor(graph: Graph) { - super(graph, "bottom"); + super(graph, RevealLineAt.bottom); } } -function getLineNumber(targets: Target[], at: string) { +function getLineNumber(targets: Target[], at: RevealLineAt) { let startLine = Number.MAX_SAFE_INTEGER; let endLine = 0; targets.forEach((target: Target) => { @@ -81,10 +81,10 @@ function getLineNumber(targets: Target[], at: string) { endLine = Math.max(endLine, target.contentRange.end.line); }); - if (at === "top") { + if (at === RevealLineAt.top) { return startLine; } - if (at === "bottom") { + if (at === RevealLineAt.bottom) { return endLine; } return Math.floor((startLine + endLine) / 2); diff --git a/src/ide/vscode/VscodeRevealLine.ts b/src/ide/vscode/VscodeRevealLine.ts index b5b00fc053..09dded1796 100644 --- a/src/ide/vscode/VscodeRevealLine.ts +++ b/src/ide/vscode/VscodeRevealLine.ts @@ -14,6 +14,11 @@ export async function vscodeRevealLine( await vscode.commands.executeCommand("revealLine", { lineNumber, - at, + at: + at === RevealLineAt.top + ? "top" + : at === RevealLineAt.bottom + ? "bottom" + : "center", }); } diff --git a/src/libs/common/index.ts b/src/libs/common/index.ts index ce33818395..0dc5f91944 100644 --- a/src/libs/common/index.ts +++ b/src/libs/common/index.ts @@ -19,7 +19,7 @@ export { TokenHatSplittingMode } from "./ide/types/Configuration"; export * from "./ide/types/ide.types"; export * from "./ide/types/Capabilities"; export * from "./ide/types/CommandId"; -export * from "./types/RangeExpansionBehavior"; +export * from "./types/DecorationRangeBehavior"; export * from "./types/InputBoxOptions"; export * from "./types/Position"; export * from "./types/Range"; diff --git a/src/libs/common/types/RevealLineAt.ts b/src/libs/common/types/RevealLineAt.ts index 40abf8a3a5..03d48bba6d 100644 --- a/src/libs/common/types/RevealLineAt.ts +++ b/src/libs/common/types/RevealLineAt.ts @@ -1,15 +1,19 @@ -export type RevealLineAt = +/** + * Describes the behavior of ranges when typing/editing at their edges. + */ +export enum RevealLineAt { /** * Reveal line at top of viewport */ - | "top" + top = "top", /** * Reveal line at center of viewport */ - | "center" + center = "center", /** * Reveal line at bottom of viewport */ - | "bottom"; + bottom = "bottom", +} From 70b56a044c0e20e2fee8699701f973c3d5b56838 Mon Sep 17 00:00:00 2001 From: Pokey Rule <755842+pokey@users.noreply.github.com> Date: Wed, 7 Dec 2022 15:31:18 +0000 Subject: [PATCH 43/43] Fix --- src/libs/common/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/common/index.ts b/src/libs/common/index.ts index 0dc5f91944..ce33818395 100644 --- a/src/libs/common/index.ts +++ b/src/libs/common/index.ts @@ -19,7 +19,7 @@ export { TokenHatSplittingMode } from "./ide/types/Configuration"; export * from "./ide/types/ide.types"; export * from "./ide/types/Capabilities"; export * from "./ide/types/CommandId"; -export * from "./types/DecorationRangeBehavior"; +export * from "./types/RangeExpansionBehavior"; export * from "./types/InputBoxOptions"; export * from "./types/Position"; export * from "./types/Range";