From 7e8c4c4cffbfa83f61adf441ff62aa7bf2fb5d93 Mon Sep 17 00:00:00 2001 From: Pokey Rule <755842+pokey@users.noreply.github.com> Date: Wed, 7 Jun 2023 20:47:39 +0100 Subject: [PATCH] Migrate `functionName` to just pipe `namedFunction` into `name` --- packages/common/src/index.ts | 4 +- ...{CommandV5.types.ts => CommandV6.types.ts} | 4 +- .../common/src/types/command/command.types.ts | 8 +- .../types/command/legacy/CommandV5.types.ts | 99 +++++ .../legacy/PartialTargetDescriptorV5.types.ts | 355 ++++++++++++++++++ .../canonicalizeAndValidateCommand.ts | 4 + .../upgradeV5ToV6/index.ts | 1 + .../upgradeV5ToV6/upgradeV5ToV6.ts | 135 +++++++ 8 files changed, 604 insertions(+), 6 deletions(-) rename packages/common/src/types/command/{CommandV5.types.ts => CommandV6.types.ts} (94%) create mode 100644 packages/common/src/types/command/legacy/CommandV5.types.ts create mode 100644 packages/common/src/types/command/legacy/PartialTargetDescriptorV5.types.ts create mode 100644 packages/cursorless-engine/src/core/commandVersionUpgrades/upgradeV5ToV6/index.ts create mode 100644 packages/cursorless-engine/src/core/commandVersionUpgrades/upgradeV5ToV6/upgradeV5ToV6.ts diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index 9949e463f7..e6a848901b 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -70,10 +70,12 @@ export * from "./types/command/legacy/CommandV0V1.types"; export * from "./types/command/legacy/CommandV2.types"; export * from "./types/command/legacy/CommandV3.types"; export * from "./types/command/legacy/CommandV4.types"; +export * from "./types/command/legacy/CommandV5.types"; export * from "./types/command/legacy/targetDescriptorV2.types"; -export * from "./types/command/CommandV5.types"; +export * from "./types/command/CommandV6.types"; export * from "./types/command/legacy/PartialTargetDescriptorV3.types"; export * from "./types/command/legacy/PartialTargetDescriptorV4.types"; +export * from "./types/command/legacy/PartialTargetDescriptorV5.types"; export * from "./types/CommandServerApi"; export * from "./util/itertools"; export * from "./extensionDependencies"; diff --git a/packages/common/src/types/command/CommandV5.types.ts b/packages/common/src/types/command/CommandV6.types.ts similarity index 94% rename from packages/common/src/types/command/CommandV5.types.ts rename to packages/common/src/types/command/CommandV6.types.ts index 33bd6c2d79..2d9488c0a6 100644 --- a/packages/common/src/types/command/CommandV5.types.ts +++ b/packages/common/src/types/command/CommandV6.types.ts @@ -1,11 +1,11 @@ import type { PartialTargetDescriptor } from "./PartialTargetDescriptor.types"; import type { ActionCommand } from "./ActionCommand"; -export interface CommandV5 { +export interface CommandV6 { /** * The version number of the command API */ - version: 5; + version: 6; /** * The spoken form of the command if issued from a voice command system diff --git a/packages/common/src/types/command/command.types.ts b/packages/common/src/types/command/command.types.ts index 665a9baca8..b222b722b3 100644 --- a/packages/common/src/types/command/command.types.ts +++ b/packages/common/src/types/command/command.types.ts @@ -1,14 +1,15 @@ import type { ActionCommand } from "./ActionCommand"; -import type { CommandV5 } from "./CommandV5.types"; +import type { CommandV6 } from "./CommandV6.types"; import type { CommandV0, CommandV1 } from "./legacy/CommandV0V1.types"; import type { CommandV2 } from "./legacy/CommandV2.types"; import type { CommandV3 } from "./legacy/CommandV3.types"; import type { CommandV4 } from "./legacy/CommandV4.types"; +import { CommandV5 } from "./legacy/CommandV5.types"; export type CommandComplete = Required> & Pick & { action: Required }; -export const LATEST_VERSION = 5 as const; +export const LATEST_VERSION = 6 as const; export type CommandLatest = Command & { version: typeof LATEST_VERSION; @@ -20,4 +21,5 @@ export type Command = | CommandV2 | CommandV3 | CommandV4 - | CommandV5; + | CommandV5 + | CommandV6; diff --git a/packages/common/src/types/command/legacy/CommandV5.types.ts b/packages/common/src/types/command/legacy/CommandV5.types.ts new file mode 100644 index 0000000000..0d6fffd44a --- /dev/null +++ b/packages/common/src/types/command/legacy/CommandV5.types.ts @@ -0,0 +1,99 @@ +import { PartialTargetDescriptorV5 } from "./PartialTargetDescriptorV5.types"; + +const actionNames = [ + "callAsFunction", + "clearAndSetSelection", + "copyToClipboard", + "cutToClipboard", + "deselect", + "editNew", + "editNewLineAfter", + "editNewLineBefore", + "executeCommand", + "extractVariable", + "findInWorkspace", + "foldRegion", + "followLink", + "generateSnippet", + "getText", + "highlight", + "indentLine", + "insertCopyAfter", + "insertCopyBefore", + "insertEmptyLineAfter", + "insertEmptyLineBefore", + "insertEmptyLinesAround", + "insertSnippet", + "moveToTarget", + "outdentLine", + "pasteFromClipboard", + "randomizeTargets", + "remove", + "rename", + "replace", + "replaceWithTarget", + "revealDefinition", + "revealTypeDefinition", + "reverseTargets", + "rewrapWithPairedDelimiter", + "experimental.setInstanceReference", + "scrollToBottom", + "scrollToCenter", + "scrollToTop", + "setSelection", + "setSelectionAfter", + "setSelectionBefore", + "showDebugHover", + "showHover", + "showQuickFix", + "showReferences", + "sortTargets", + "swapTargets", + "toggleLineBreakpoint", + "toggleLineComment", + "unfoldRegion", + "wrapWithPairedDelimiter", + "wrapWithSnippet", +] as const; + +type ActionType = (typeof actionNames)[number]; + +interface ActionCommand { + /** + * The action to run + */ + name: ActionType; + + /** + * A list of arguments expected by the given action. + */ + args?: unknown[]; +} + +export interface CommandV5 { + /** + * The version number of the command API + */ + version: 5; + + /** + * The spoken form of the command if issued from a voice command system + */ + spokenForm?: string; + + /** + * If the command is issued from a voice command system, this boolean indicates + * whether we should use the pre phrase snapshot. Only set this to true if the + * voice command system issues a pre phrase signal at the start of every + * phrase. + */ + usePrePhraseSnapshot: boolean; + + action: ActionCommand; + + /** + * A list of targets expected by the action. Inference will be run on the + * targets + */ + targets: PartialTargetDescriptorV5[]; +} diff --git a/packages/common/src/types/command/legacy/PartialTargetDescriptorV5.types.ts b/packages/common/src/types/command/legacy/PartialTargetDescriptorV5.types.ts new file mode 100644 index 0000000000..a2be15adcd --- /dev/null +++ b/packages/common/src/types/command/legacy/PartialTargetDescriptorV5.types.ts @@ -0,0 +1,355 @@ +interface CursorMark { + type: "cursor"; +} + +interface ThatMark { + type: "that"; +} + +interface SourceMark { + type: "source"; +} + +interface NothingMark { + type: "nothing"; +} + +interface DecoratedSymbolMark { + type: "decoratedSymbol"; + symbolColor: string; + character: string; +} + +type LineNumberType = "absolute" | "relative" | "modulo100"; + +interface LineNumberMark { + type: "lineNumber"; + lineNumberType: LineNumberType; + lineNumber: number; +} + +/** + * Constructs a range between {@link anchor} and {@link active} + */ +interface RangeMark { + type: "range"; + anchor: PartialMark; + active: PartialMark; + excludeAnchor?: boolean; + excludeActive?: boolean; +} + +type PartialMark = + | CursorMark + | ThatMark + | SourceMark + | DecoratedSymbolMark + | NothingMark + | LineNumberMark + | RangeMark; + +type SimpleSurroundingPairName = + | "angleBrackets" + | "backtickQuotes" + | "curlyBrackets" + | "doubleQuotes" + | "escapedDoubleQuotes" + | "escapedParentheses" + | "escapedSquareBrackets" + | "escapedSingleQuotes" + | "parentheses" + | "singleQuotes" + | "squareBrackets"; +type ComplexSurroundingPairName = "string" | "any" | "collectionBoundary"; +type SurroundingPairName = + | SimpleSurroundingPairName + | ComplexSurroundingPairName; + +export type SimpleScopeTypeTypeV5 = + | "argumentOrParameter" + | "anonymousFunction" + | "attribute" + | "branch" + | "class" + | "className" + | "collectionItem" + | "collectionKey" + | "comment" + | "functionCall" + | "functionCallee" + | "functionName" + | "ifStatement" + | "instance" + | "list" + | "map" + | "name" + | "namedFunction" + | "regularExpression" + | "statement" + | "string" + | "type" + | "value" + | "condition" + | "section" + | "sectionLevelOne" + | "sectionLevelTwo" + | "sectionLevelThree" + | "sectionLevelFour" + | "sectionLevelFive" + | "sectionLevelSix" + | "selector" + | "switchStatementSubject" + | "unit" + | "xmlBothTags" + | "xmlElement" + | "xmlEndTag" + | "xmlStartTag" + // Latex scope types + | "part" + | "chapter" + | "subSection" + | "subSubSection" + | "namedParagraph" + | "subParagraph" + | "environment" + // Text based scopes + | "token" + | "line" + | "notebookCell" + | "paragraph" + | "document" + | "character" + | "word" + | "identifier" + | "nonWhitespaceSequence" + | "boundedNonWhitespaceSequence" + | "url"; + +interface SimpleScopeTypeV5 { + type: SimpleScopeTypeTypeV5; +} + +interface CustomRegexScopeType { + type: "customRegex"; + regex: string; +} + +type SurroundingPairDirection = "left" | "right"; +interface SurroundingPairScopeType { + type: "surroundingPair"; + delimiter: SurroundingPairName; + forceDirection?: SurroundingPairDirection; + + /** + * If `true`, then only accept pairs where the pair completely contains the + * selection, ie without the edges touching. + */ + requireStrongContainment?: boolean; +} + +interface OneOfScopeType { + type: "oneOf"; + scopeTypes: ScopeTypeV5[]; +} + +export type ScopeTypeV5 = + | SimpleScopeTypeV5 + | SurroundingPairScopeType + | CustomRegexScopeType + | OneOfScopeType; + +interface InteriorOnlyModifier { + type: "interiorOnly"; +} + +interface ExcludeInteriorModifier { + type: "excludeInterior"; +} + +export interface ContainingScopeModifierV5 { + type: "containingScope"; + scopeType: ScopeTypeV5; + ancestorIndex?: number; +} + +export interface EveryScopeModifierV5 { + type: "everyScope"; + scopeType: ScopeTypeV5; +} + +/** + * Refer to scopes by absolute index relative to iteration scope, eg "first + * funk" to refer to the first function in a class. + */ +export interface OrdinalScopeModifierV5 { + type: "ordinalScope"; + + scopeType: ScopeTypeV5; + + /** The start of the range. Start from end of iteration scope if `start` is negative */ + start: number; + + /** The number of scopes to include. Will always be positive. If greater than 1, will include scopes after {@link start} */ + length: number; +} + +type Direction = "forward" | "backward"; + +/** + * Refer to scopes by offset relative to input target, eg "next + * funk" to refer to the first function after the function containing the target input. + */ +export interface RelativeScopeModifierV5 { + type: "relativeScope"; + + scopeType: ScopeTypeV5; + + /** Indicates how many scopes away to start relative to the input target. + * Note that if {@link direction} is `"backward"`, then this scope will be the + * end of the output range. */ + offset: number; + + /** The number of scopes to include. Will always be positive. If greater + * than 1, will include scopes in the direction of {@link direction} */ + length: number; + + /** Indicates which direction both {@link offset} and {@link length} go + * relative to input target */ + direction: Direction; +} + +/** + * Converts its input to a raw selection with no type information so for + * example if it is the destination of a bring or move it should inherit the + * type information such as delimiters from its source. + */ +interface RawSelectionModifier { + type: "toRawSelection"; +} + +interface LeadingModifier { + type: "leading"; +} + +interface TrailingModifier { + type: "trailing"; +} + +interface KeepContentFilterModifier { + type: "keepContentFilter"; +} + +interface KeepEmptyFilterModifier { + type: "keepEmptyFilter"; +} + +interface InferPreviousMarkModifier { + type: "inferPreviousMark"; +} + +type TargetPosition = "before" | "after" | "start" | "end"; + +interface PositionModifier { + type: "position"; + position: TargetPosition; +} + +export interface PartialPrimitiveTargetDescriptorV5 { + type: "primitive"; + mark?: PartialMark; + modifiers?: ModifierV5[]; +} + +interface HeadTailModifier { + type: "extendThroughStartOf" | "extendThroughEndOf"; + modifiers?: ModifierV5[]; +} + +/** + * Runs {@link modifier} if the target has no explicit scope type, ie if + * {@link Target.hasExplicitScopeType} is `false`. + */ +interface ModifyIfUntypedModifier { + type: "modifyIfUntyped"; + + /** + * The modifier to apply if the target is untyped + */ + modifier: ModifierV5; +} + +/** + * Tries each of the modifiers in {@link modifiers} in turn until one of them + * doesn't throw an error, returning the output from the first modifier not + * throwing an error. + */ +interface CascadingModifier { + type: "cascading"; + + /** + * The modifiers to try in turn + */ + modifiers: ModifierV5[]; +} + +/** + * First applies {@link anchor} to input, then independently applies + * {@link active}, and forms a range between the two resulting targets + */ +interface RangeModifier { + type: "range"; + anchor: ModifierV5; + active: ModifierV5; + excludeAnchor?: boolean; + excludeActive?: boolean; +} + +export type ModifierV5 = + | PositionModifier + | InteriorOnlyModifier + | ExcludeInteriorModifier + | ContainingScopeModifierV5 + | EveryScopeModifierV5 + | OrdinalScopeModifierV5 + | RelativeScopeModifierV5 + | HeadTailModifier + | LeadingModifier + | TrailingModifier + | RawSelectionModifier + | ModifyIfUntypedModifier + | CascadingModifier + | RangeModifier + | KeepContentFilterModifier + | KeepEmptyFilterModifier + | InferPreviousMarkModifier; + +// continuous is one single continuous selection between the two targets +// vertical puts a selection on each line vertically between the two targets +type PartialRangeType = "continuous" | "vertical"; + +export interface PartialRangeTargetDescriptorV5 { + type: "range"; + anchor: PartialPrimitiveTargetDescriptorV5 | ImplicitTargetDescriptorV5; + active: PartialPrimitiveTargetDescriptorV5; + excludeAnchor: boolean; + excludeActive: boolean; + rangeType?: PartialRangeType; +} + +export interface PartialListTargetDescriptorV5 { + type: "list"; + elements: ( + | PartialPrimitiveTargetDescriptorV5 + | PartialRangeTargetDescriptorV5 + )[]; +} + +export interface ImplicitTargetDescriptorV5 { + type: "implicit"; +} + +export type PartialTargetDescriptorV5 = + | PartialPrimitiveTargetDescriptorV5 + | PartialRangeTargetDescriptorV5 + | PartialListTargetDescriptorV5 + | ImplicitTargetDescriptorV5; diff --git a/packages/cursorless-engine/src/core/commandVersionUpgrades/canonicalizeAndValidateCommand.ts b/packages/cursorless-engine/src/core/commandVersionUpgrades/canonicalizeAndValidateCommand.ts index e428649eda..d0d5d49dd4 100644 --- a/packages/cursorless-engine/src/core/commandVersionUpgrades/canonicalizeAndValidateCommand.ts +++ b/packages/cursorless-engine/src/core/commandVersionUpgrades/canonicalizeAndValidateCommand.ts @@ -20,6 +20,7 @@ import { upgradeV1ToV2 } from "./upgradeV1ToV2"; import { upgradeV2ToV3 } from "./upgradeV2ToV3"; import { upgradeV3ToV4 } from "./upgradeV3ToV4"; import { upgradeV4ToV5 } from "./upgradeV4ToV5/upgradeV4ToV5"; +import { upgradeV5ToV6 } from "./upgradeV5ToV6"; /** * Given a command argument which comes from the client, normalize it so that it @@ -78,6 +79,9 @@ function upgradeCommand(command: Command): CommandLatest { case 4: command = upgradeV4ToV5(command); break; + case 5: + command = upgradeV5ToV6(command); + break; default: throw new Error( `Can't upgrade from unknown version ${command.version}`, diff --git a/packages/cursorless-engine/src/core/commandVersionUpgrades/upgradeV5ToV6/index.ts b/packages/cursorless-engine/src/core/commandVersionUpgrades/upgradeV5ToV6/index.ts new file mode 100644 index 0000000000..fb04f2a3a5 --- /dev/null +++ b/packages/cursorless-engine/src/core/commandVersionUpgrades/upgradeV5ToV6/index.ts @@ -0,0 +1 @@ +export * from "./upgradeV5ToV6"; diff --git a/packages/cursorless-engine/src/core/commandVersionUpgrades/upgradeV5ToV6/upgradeV5ToV6.ts b/packages/cursorless-engine/src/core/commandVersionUpgrades/upgradeV5ToV6/upgradeV5ToV6.ts new file mode 100644 index 0000000000..1cb160c8f2 --- /dev/null +++ b/packages/cursorless-engine/src/core/commandVersionUpgrades/upgradeV5ToV6/upgradeV5ToV6.ts @@ -0,0 +1,135 @@ +import { + CommandV5, + CommandV6, + ImplicitTargetDescriptor, + Modifier, + ModifierV5, + PartialPrimitiveTargetDescriptor, + PartialPrimitiveTargetDescriptorV5, + PartialRangeTargetDescriptor, + PartialTargetDescriptor, + PartialTargetDescriptorV5, + ScopeTypeV5, + SimpleScopeTypeType, +} from "@cursorless/common"; + +export function upgradeV5ToV6(command: CommandV5): CommandV6 { + return { + ...command, + version: 6, + targets: transformPartialPrimitiveTargets(command.targets, upgradeTarget), + }; +} + +function upgradeTarget( + target: PartialPrimitiveTargetDescriptorV5, +): PartialPrimitiveTargetDescriptor { + return { + ...target, + modifiers: target.modifiers?.flatMap(upgradeModifier), + }; +} + +const upgrades: Partial> = { + functionName: "namedFunction", + className: "class", +}; + +function upgradeModifier(modifier: ModifierV5): Modifier[] { + switch (modifier.type) { + case "containingScope": + case "everyScope": + case "ordinalScope": + case "relativeScope": { + const upgradedScopeType = upgrades[modifier.scopeType.type]; + + if (upgradedScopeType == null) { + return [modifier]; + } + + return [ + { + type: "containingScope", + scopeType: { + type: "name", + }, + }, + { + ...modifier, + scopeType: { + type: upgradedScopeType, + }, + }, + ]; + } + case "extendThroughStartOf": + case "extendThroughEndOf": + return [ + { + type: modifier.type, + modifiers: modifier.modifiers?.flatMap(upgradeModifier), + }, + ]; + case "modifyIfUntyped": + return [ + { + type: "modifyIfUntyped", + // TODO: This is a hack + // We should really use a new type of modifier that chains modifiers + modifier: upgradeModifier(modifier.modifier)[0], + }, + ]; + default: + return [modifier]; + } +} + +/** + * Given a list of targets, recursively descends all targets and applies `func` + * to every primitive target. + * + * @param targets The targets to extract from + * @returns A list of primitive targets + */ +function transformPartialPrimitiveTargets( + targets: PartialTargetDescriptorV5[], + func: ( + target: PartialPrimitiveTargetDescriptorV5, + ) => PartialPrimitiveTargetDescriptor, +) { + return targets.map((target) => + transformPartialPrimitiveTargetsHelper(target, func), + ); +} + +function transformPartialPrimitiveTargetsHelper( + target: PartialTargetDescriptorV5, + func: ( + target: PartialPrimitiveTargetDescriptorV5, + ) => PartialPrimitiveTargetDescriptor, +): PartialTargetDescriptor { + switch (target.type) { + case "primitive": + return func(target); + case "implicit": + return target; + case "list": + return { + ...target, + elements: target.elements.map( + (element) => + transformPartialPrimitiveTargetsHelper(element, func) as + | PartialPrimitiveTargetDescriptor + | PartialRangeTargetDescriptor, + ), + }; + case "range": + return { + ...target, + anchor: transformPartialPrimitiveTargetsHelper(target.anchor, func) as + | PartialPrimitiveTargetDescriptor + | ImplicitTargetDescriptor, + active: func(target.active), + }; + } +}