diff --git a/src/CommandAction.ts b/src/CommandAction.ts index 0028b4fab7..cd6f3a1059 100644 --- a/src/CommandAction.ts +++ b/src/CommandAction.ts @@ -63,8 +63,7 @@ export default class CommandAction implements Action { ]): Promise { await displayPendingEditDecorations( targets, - this.graph.editStyles.referenced, - this.graph.editStyles.referencedLine + this.graph.editStyles.referenced ); const originalEditor = window.activeTextEditor; diff --git a/src/Types.ts b/src/Types.ts index 86fff8e65a..0d4aac840c 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -2,7 +2,7 @@ import { SyntaxNode } from "web-tree-sitter"; import * as vscode from "vscode"; import { Location } from "vscode"; import { SymbolColor } from "./constants"; -import EditStyles from "./editStyles"; +import { EditStyles } from "./editStyles"; import NavigationMap from "./NavigationMap"; /** @@ -256,6 +256,7 @@ export type ActionType = | "move" | "outdentLines" | "paste" + | "replaceWithText" | "scrollToBottom" | "scrollToCenter" | "scrollToTop" diff --git a/src/actions/BringMoveSwap.ts b/src/actions/BringMoveSwap.ts index 642439ef98..e6f6571c61 100644 --- a/src/actions/BringMoveSwap.ts +++ b/src/actions/BringMoveSwap.ts @@ -9,17 +9,11 @@ import { import { runForEachEditor } from "../targetUtils"; import update from "immutability-helper"; import displayPendingEditDecorations from "../editDisplayUtils"; -import { performInsideOutsideAdjustment } from "../performInsideOutsideAdjustment"; +import { performOutsideAdjustment } from "../performInsideOutsideAdjustment"; import { flatten, zip } from "lodash"; -import { Selection, TextEditorDecorationType, TextEditor, Range } from "vscode"; +import { Selection, TextEditor, Range } from "vscode"; import { performEditsAndUpdateSelections } from "../updateSelections"; - -interface DecorationTypes { - sourceStyle: TextEditorDecorationType; - sourceLineStyle: TextEditorDecorationType; - destinationStyle: TextEditorDecorationType; - destinationLineStyle: TextEditorDecorationType; -} +import { getTextWithPossibleDelimiter } from "../getTextWithPossibleDelimiter"; interface ExtendedEdit extends Edit { editor: TextEditor; @@ -37,7 +31,7 @@ interface ThatMarkEntry { class BringMoveSwap implements Action { targetPreferences: ActionPreferences[] = [ { insideOutsideType: null }, - { insideOutsideType: "inside" }, + { insideOutsideType: null }, ]; constructor(private graph: Graph, private type: string) { @@ -56,25 +50,20 @@ class BringMoveSwap implements Action { return sources; } - private getDecorationStyles(): DecorationTypes { - let sourceStyle, sourceLineStyle; + private getDecorationStyles() { + let sourceStyle; if (this.type === "bring") { sourceStyle = this.graph.editStyles.referenced; - sourceLineStyle = this.graph.editStyles.referencedLine; - } else if (this.type === "swap") { - sourceStyle = this.graph.editStyles.pendingModification1; - sourceLineStyle = this.graph.editStyles.pendingLineModification1; } else if (this.type === "move") { sourceStyle = this.graph.editStyles.pendingDelete; - sourceLineStyle = this.graph.editStyles.pendingLineDelete; - } else { - throw Error(`Unknown type "${this.type}"`); + } + // NB this.type === "swap" + else { + sourceStyle = this.graph.editStyles.pendingModification1; } return { sourceStyle, - sourceLineStyle, destinationStyle: this.graph.editStyles.pendingModification0, - destinationLineStyle: this.graph.editStyles.pendingLineModification0, }; } @@ -84,15 +73,10 @@ class BringMoveSwap implements Action { ) { const decorationTypes = this.getDecorationStyles(); await Promise.all([ - displayPendingEditDecorations( - sources, - decorationTypes.sourceStyle, - decorationTypes.sourceLineStyle - ), + displayPendingEditDecorations(sources, decorationTypes.sourceStyle), displayPendingEditDecorations( destinations, - decorationTypes.destinationStyle, - decorationTypes.destinationLineStyle + decorationTypes.destinationStyle ), ]); } @@ -107,54 +91,45 @@ class BringMoveSwap implements Action { throw new Error("Targets must have same number of args"); } - const sourceText = source.selection.editor.document.getText( - source.selection.selection - ); - - const { containingListDelimiter } = destination.selectionContext; - const newText = - containingListDelimiter == null || destination.position === "contents" - ? sourceText - : destination.position === "after" - ? containingListDelimiter + sourceText - : sourceText + containingListDelimiter; + // Get text adjusting for destination position + const text = getTextWithPossibleDelimiter(source, destination); // Add destination edit const result = [ { - isSource: false, + range: destination.selection.selection as Range, + text, editor: destination.selection.editor, originalSelection: destination, - range: destination.selection.selection as Range, - text: newText, + isSource: false, }, ]; // Add source edit for move and swap // Prevent multiple instances of the same expanded source. if (this.type !== "bring" && !usedSources.includes(source)) { - let newText: string; + let text: string; let range: Range; if (this.type === "swap") { - newText = destination.selection.editor.document.getText( + text = destination.selection.editor.document.getText( destination.selection.selection ); range = source.selection.selection; - } else { - // NB: this.type === "move" - newText = ""; - range = performInsideOutsideAdjustment(source, "outside").selection - .selection; + } + // NB: this.type === "move" + else { + text = ""; + range = performOutsideAdjustment(source).selection.selection; } usedSources.push(source); result.push({ - isSource: true, + range, + text, editor: source.selection.editor, originalSelection: source, - range, - text: newText, + isSource: true, }); } @@ -206,15 +181,13 @@ class BringMoveSwap implements Action { thatMark .filter(({ isSource }) => isSource) .map(({ typedSelection }) => typedSelection), - decorationTypes.sourceStyle, - decorationTypes.sourceLineStyle + decorationTypes.sourceStyle ), displayPendingEditDecorations( thatMark .filter(({ isSource }) => !isSource) .map(({ typedSelection }) => typedSelection), - decorationTypes.destinationStyle, - decorationTypes.destinationLineStyle + decorationTypes.destinationStyle ), ]); } diff --git a/src/actions/CopyLines.ts b/src/actions/CopyLines.ts index f460a4380a..7c5e846a75 100644 --- a/src/actions/CopyLines.ts +++ b/src/actions/CopyLines.ts @@ -46,8 +46,7 @@ class CopyLines implements Action { async run([targets]: [TypedSelection[]]): Promise { await displayPendingEditDecorations( targets, - this.graph.editStyles.referenced, - this.graph.editStyles.referencedLine + this.graph.editStyles.referenced ); const thatMark = flatten( diff --git a/src/actions/Find.ts b/src/actions/Find.ts index 21d2d0fac7..703a59a50a 100644 --- a/src/actions/Find.ts +++ b/src/actions/Find.ts @@ -18,10 +18,14 @@ export class FindInFiles implements Action { async run([targets]: [TypedSelection[]]): Promise { ensureSingleTarget(targets); - const { returnValue: query, thatMark } = - await this.graph.actions.getText.run([targets]); + const { + returnValue: [query], + thatMark, + } = await this.graph.actions.getText.run([targets]); - await commands.executeCommand("workbench.action.findInFiles", { query }); + await commands.executeCommand("workbench.action.findInFiles", { + query, + }); return { returnValue: null, thatMark }; } diff --git a/src/actions/GetText.ts b/src/actions/GetText.ts index 106d2bb56c..dc335b872e 100644 --- a/src/actions/GetText.ts +++ b/src/actions/GetText.ts @@ -16,24 +16,21 @@ export default class GetText implements Action { async run( [targets]: [TypedSelection[]], - showDecorations = true + { showDecorations = true } = {} ): Promise { if (showDecorations) { await displayPendingEditDecorations( targets, - this.graph.editStyles.referenced, - this.graph.editStyles.referencedLine + this.graph.editStyles.referenced ); } - const text = targets - .map((target) => - target.selection.editor.document.getText(target.selection.selection) - ) - .join("\n"); + const returnValue = targets.map((target) => + target.selection.editor.document.getText(target.selection.selection) + ); return { - returnValue: text, + returnValue, thatMark: targets.map((target) => target.selection), }; } diff --git a/src/actions/InsertEmptyLines.ts b/src/actions/InsertEmptyLines.ts index 92d988bfc2..4526647e8c 100644 --- a/src/actions/InsertEmptyLines.ts +++ b/src/actions/InsertEmptyLines.ts @@ -24,7 +24,6 @@ class InsertEmptyLines implements Action { displayPendingEditDecorations( targets, this.graph.editStyles.referenced, - this.graph.editStyles.referencedLine ); const edits = await runForEachEditor( diff --git a/src/actions/Paste.ts b/src/actions/Paste.ts index 69ed99e16f..3329c9eb89 100644 --- a/src/actions/Paste.ts +++ b/src/actions/Paste.ts @@ -23,7 +23,6 @@ export default class Paste implements Action { await displayPendingEditDecorations( targets, this.graph.editStyles.pendingModification0, - this.graph.editStyles.pendingLineModification0 ); const text = await env.clipboard.readText(); @@ -78,7 +77,6 @@ export default class Paste implements Action { await displayPendingEditDecorations( thatMark.map(({ typedSelection }) => typedSelection), this.graph.editStyles.pendingModification0, - this.graph.editStyles.pendingLineModification0 ); return { returnValue: null, thatMark }; diff --git a/src/actions/ReplaceWithText.ts b/src/actions/ReplaceWithText.ts new file mode 100644 index 0000000000..be08ebdf4c --- /dev/null +++ b/src/actions/ReplaceWithText.ts @@ -0,0 +1,66 @@ +import { + Action, + ActionPreferences, + ActionReturnValue, + Graph, + TypedSelection, +} from "../Types"; +import displayPendingEditDecorations from "../editDisplayUtils"; +import { runForEachEditor } from "../targetUtils"; +import { flatten, zip } from "lodash"; +import { maybeAddDelimiter } from "../getTextWithPossibleDelimiter"; +import { performEditsAndUpdateSelections } from "../updateSelections"; + +export default class implements Action { + targetPreferences: ActionPreferences[] = [{ insideOutsideType: null }]; + + constructor(private graph: Graph) { + this.run = this.run.bind(this); + } + + async run( + [targets]: [TypedSelection[]], + texts: string[] + ): Promise { + await displayPendingEditDecorations( + targets, + this.graph.editStyles.pendingModification0 + ); + + // Broadcast single text for each target + if (texts.length === 1) { + texts = Array(targets.length).fill(texts[0]); + } + + if (targets.length !== texts.length) { + throw new Error("Targets and texts must have same length"); + } + + const edits = zip(targets, texts).map(([target, text]) => ({ + editor: target!.selection.editor, + range: target!.selection.selection, + text: maybeAddDelimiter(text!, target!), + })); + + const thatMark = flatten( + await runForEachEditor( + edits, + (edit) => edit.editor, + async (editor, edits) => { + const [updatedSelections] = await performEditsAndUpdateSelections( + editor, + edits, + [targets.map((target) => target.selection.selection)] + ); + + return updatedSelections.map((selection) => ({ + editor, + selection, + })); + } + ) + ); + + return { returnValue: null, thatMark }; + } +} diff --git a/src/actions/Scroll.ts b/src/actions/Scroll.ts index 6fb720b4d5..b7fce93a00 100644 --- a/src/actions/Scroll.ts +++ b/src/actions/Scroll.ts @@ -56,7 +56,7 @@ class Scroll implements Action { await displayDecorationsWhileRunningFunc( targets.map((target) => target.selection), - this.graph.editStyles.referencedLine, + this.graph.editStyles.referenced.line, scrollCallback, showAdditionalHighlightBeforeScroll ); diff --git a/src/actions/SetBreakpoint.ts b/src/actions/SetBreakpoint.ts index c27d26af21..2a420d6f65 100644 --- a/src/actions/SetBreakpoint.ts +++ b/src/actions/SetBreakpoint.ts @@ -41,8 +41,7 @@ export default class SetBreakpoint implements Action { ]): Promise { await displayPendingEditDecorations( targets, - this.graph.editStyles.referenced, - this.graph.editStyles.referencedLine + this.graph.editStyles.referenced ); const lines = targets.flatMap((target) => { diff --git a/src/actions/copy.ts b/src/actions/copy.ts index 33b01292ef..e082c4bfe2 100644 --- a/src/actions/copy.ts +++ b/src/actions/copy.ts @@ -18,8 +18,9 @@ export default class Copy implements Action { const { returnValue, thatMark } = await this.graph.actions.getText.run([ targets, ]); + const text = returnValue.join("\n"); - await env.clipboard.writeText(returnValue); + await env.clipboard.writeText(text); return { returnValue: null, thatMark }; } diff --git a/src/actions/cut.ts b/src/actions/cut.ts index bcf0b3ae92..c3b94d0d75 100644 --- a/src/actions/cut.ts +++ b/src/actions/cut.ts @@ -5,7 +5,10 @@ import { Graph, TypedSelection, } from "../Types"; -import { performInsideOutsideAdjustment } from "../performInsideOutsideAdjustment"; +import { + performInsideAdjustment, + performOutsideAdjustment, +} from "../performInsideOutsideAdjustment"; export default class Cut implements Action { targetPreferences: ActionPreferences[] = [{ insideOutsideType: null }]; @@ -15,14 +18,10 @@ export default class Cut implements Action { } async run([targets]: [TypedSelection[]]): Promise { - await this.graph.actions.copy.run([ - targets.map((target) => performInsideOutsideAdjustment(target, "inside")), - ]); + await this.graph.actions.copy.run([targets.map(performInsideAdjustment)]); const { thatMark } = await this.graph.actions.delete.run([ - targets.map((target) => - performInsideOutsideAdjustment(target, "outside") - ), + targets.map(performOutsideAdjustment), ]); return { diff --git a/src/actions/delete.ts b/src/actions/delete.ts index 4645bd5684..fab12244b3 100644 --- a/src/actions/delete.ts +++ b/src/actions/delete.ts @@ -21,7 +21,6 @@ export default class Delete implements Action { await displayPendingEditDecorations( targets, this.graph.editStyles.pendingDelete, - this.graph.editStyles.pendingLineDelete ); const thatMark = flatten( diff --git a/src/actions/index.ts b/src/actions/index.ts index b69217091a..30fcdf6f9c 100644 --- a/src/actions/index.ts +++ b/src/actions/index.ts @@ -24,6 +24,7 @@ import { } from "./InsertEmptyLines"; import GetText from "./GetText"; import { FindInFiles } from "./Find"; +import ReplaceWithText from "./ReplaceWithText"; import { CopyLinesUp, CopyLinesDown } from "./CopyLines"; import SetBreakpoint from "./SetBreakpoint"; @@ -56,6 +57,7 @@ class Actions implements ActionRecord { move = new Move(this.graph); outdentLines = new OutdentLines(this.graph); paste = new Paste(this.graph); + replaceWithText = new ReplaceWithText(this.graph); scrollToBottom = new ScrollToBottom(this.graph); scrollToCenter = new ScrollToCenter(this.graph); scrollToTop = new ScrollToTop(this.graph); diff --git a/src/actions/wrap.ts b/src/actions/wrap.ts index bedb23b08b..d64d5263c9 100644 --- a/src/actions/wrap.ts +++ b/src/actions/wrap.ts @@ -51,11 +51,12 @@ export default class Wrap implements Action { ); editor.setDecorations( - this.graph.editStyles.justAdded, + this.graph.editStyles.justAdded.token, updatedSelections ); await decorationSleep(); - editor.setDecorations(this.graph.editStyles.justAdded, []); + + editor.setDecorations(this.graph.editStyles.justAdded.token, []); return targets.map((target, index) => { const start = updatedSelections[index * 2].start; diff --git a/src/editDisplayUtils.ts b/src/editDisplayUtils.ts index 9291b8b624..367887d1a8 100644 --- a/src/editDisplayUtils.ts +++ b/src/editDisplayUtils.ts @@ -3,6 +3,7 @@ import { TypedSelection, SelectionWithEditor } from "./Types"; import { isLineSelectionType } from "./selectionType"; import { promisify } from "util"; import { runOnTargetsForEachEditor, runForEachEditor } from "./targetUtils"; +import { EditStyle } from "./editStyles"; const sleep = promisify(setTimeout); @@ -17,19 +18,18 @@ export async function decorationSleep() { export default async function displayPendingEditDecorations( targets: TypedSelection[], - tokenStyle: TextEditorDecorationType, - lineStyle: TextEditorDecorationType + editStyle: EditStyle ) { await runOnTargetsForEachEditor(targets, async (editor, selections) => { editor.setDecorations( - tokenStyle, + editStyle.token, selections .filter((selection) => !isLineSelectionType(selection.selectionType)) .map((selection) => selection.selection.selection) ); editor.setDecorations( - lineStyle, + editStyle.line, selections .filter((selection) => isLineSelectionType(selection.selectionType)) .map((selection) => { @@ -59,8 +59,8 @@ export default async function displayPendingEditDecorations( await decorationSleep(); await runOnTargetsForEachEditor(targets, async (editor) => { - editor.setDecorations(tokenStyle, []); - editor.setDecorations(lineStyle, []); + editor.setDecorations(editStyle.token, []); + editor.setDecorations(editStyle.line, []); }); } diff --git a/src/editStyles.ts b/src/editStyles.ts index 7828bb0be1..7ced477e8a 100644 --- a/src/editStyles.ts +++ b/src/editStyles.ts @@ -1,80 +1,34 @@ import * as vscode from "vscode"; -export default class EditStyles { - pendingDelete: vscode.TextEditorDecorationType; - pendingLineDelete: vscode.TextEditorDecorationType; - referenced: vscode.TextEditorDecorationType; - referencedLine: vscode.TextEditorDecorationType; - pendingModification0: vscode.TextEditorDecorationType; - pendingLineModification0: vscode.TextEditorDecorationType; - pendingModification1: vscode.TextEditorDecorationType; - pendingLineModification1: vscode.TextEditorDecorationType; - justAdded: vscode.TextEditorDecorationType; +export class EditStyle { + token: vscode.TextEditorDecorationType; + line: vscode.TextEditorDecorationType; - constructor() { - this.pendingDelete = vscode.window.createTextEditorDecorationType({ - backgroundColor: new vscode.ThemeColor( - "cursorless.pendingDeleteBackground" - ), - rangeBehavior: vscode.DecorationRangeBehavior.ClosedClosed, - }); - - this.pendingLineDelete = vscode.window.createTextEditorDecorationType({ - backgroundColor: new vscode.ThemeColor( - "cursorless.pendingDeleteBackground" - ), + constructor(colorName: string) { + const options = { + backgroundColor: new vscode.ThemeColor(`cursorless.${colorName}`), rangeBehavior: vscode.DecorationRangeBehavior.ClosedClosed, + }; + this.token = vscode.window.createTextEditorDecorationType(options); + this.line = vscode.window.createTextEditorDecorationType({ + ...options, isWholeLine: true, }); + } +} - this.justAdded = vscode.window.createTextEditorDecorationType({ - backgroundColor: new vscode.ThemeColor("cursorless.justAddedBackground"), - rangeBehavior: vscode.DecorationRangeBehavior.ClosedClosed, - }); - - this.referenced = vscode.window.createTextEditorDecorationType({ - backgroundColor: new vscode.ThemeColor("cursorless.referencedBackground"), - rangeBehavior: vscode.DecorationRangeBehavior.ClosedClosed, - }); - - this.referencedLine = vscode.window.createTextEditorDecorationType({ - backgroundColor: new vscode.ThemeColor("cursorless.referencedBackground"), - rangeBehavior: vscode.DecorationRangeBehavior.ClosedClosed, - isWholeLine: true, - }); - - this.pendingModification0 = vscode.window.createTextEditorDecorationType({ - backgroundColor: new vscode.ThemeColor( - "cursorless.pendingModification0Background" - ), - rangeBehavior: vscode.DecorationRangeBehavior.ClosedClosed, - }); - - this.pendingLineModification0 = vscode.window.createTextEditorDecorationType( - { - backgroundColor: new vscode.ThemeColor( - "cursorless.pendingModification0Background" - ), - rangeBehavior: vscode.DecorationRangeBehavior.ClosedClosed, - isWholeLine: true, - } - ); - - this.pendingModification1 = vscode.window.createTextEditorDecorationType({ - backgroundColor: new vscode.ThemeColor( - "cursorless.pendingModification1Background" - ), - rangeBehavior: vscode.DecorationRangeBehavior.ClosedClosed, - }); +export class EditStyles { + pendingDelete: EditStyle; + referenced: EditStyle; + pendingModification0: EditStyle; + pendingModification1: EditStyle; + justAdded: EditStyle; - this.pendingLineModification1 = vscode.window.createTextEditorDecorationType( - { - backgroundColor: new vscode.ThemeColor( - "cursorless.pendingModification1Background" - ), - rangeBehavior: vscode.DecorationRangeBehavior.ClosedClosed, - isWholeLine: true, - } - ); + constructor() { + this.pendingDelete = new EditStyle("pendingDeleteBackground"); + this.justAdded = new EditStyle("justAddedBackground"); + this.referenced = new EditStyle("referencedBackground"); + this.pendingModification0 = new EditStyle("pendingModification0Background"); + this.pendingModification1 = new EditStyle("pendingModification1Background"); } } diff --git a/src/getTextWithPossibleDelimiter.ts b/src/getTextWithPossibleDelimiter.ts new file mode 100644 index 0000000000..52e1f56025 --- /dev/null +++ b/src/getTextWithPossibleDelimiter.ts @@ -0,0 +1,29 @@ +import { TypedSelection } from "./Types"; + +/** Get text from selection. Possibly add delimiter for positions before/after */ +export function getTextWithPossibleDelimiter( + source: TypedSelection, + destination: TypedSelection +) { + const sourceText = source.selection.editor.document.getText( + source.selection.selection + ); + return maybeAddDelimiter(sourceText, destination); +} + +/** Possibly add delimiter for positions before/after */ +export function maybeAddDelimiter( + sourceText: string, + destination: TypedSelection +) { + const { insideOutsideType, position } = destination; + const containingListDelimiter = + destination.selectionContext.containingListDelimiter; + return containingListDelimiter == null || + position === "contents" || + insideOutsideType === "inside" + ? sourceText + : destination.position === "after" + ? containingListDelimiter + sourceText + : sourceText + containingListDelimiter; +} diff --git a/src/graphConstructors.ts b/src/graphConstructors.ts index c209e0fc21..cec34cfa8e 100644 --- a/src/graphConstructors.ts +++ b/src/graphConstructors.ts @@ -1,5 +1,5 @@ import Actions from "./actions"; -import EditStyles from "./editStyles"; +import { EditStyles } from "./editStyles"; import { Graph } from "./Types"; import { ConstructorMap } from "./makeGraph"; diff --git a/src/performInsideOutsideAdjustment.ts b/src/performInsideOutsideAdjustment.ts index 2d4631d77d..2e616dd9d5 100644 --- a/src/performInsideOutsideAdjustment.ts +++ b/src/performInsideOutsideAdjustment.ts @@ -1,6 +1,5 @@ -import update from "immutability-helper"; -import { Selection } from "vscode"; import { InsideOutsideType, TypedSelection } from "./Types"; +import { updateTypedSelectionRange } from "./selectionUtils"; export function performInsideOutsideAdjustment( selection: TypedSelection, @@ -10,6 +9,17 @@ export function performInsideOutsideAdjustment( selection.insideOutsideType ?? preferredInsideOutsideType; if (insideOutsideType === "outside") { + if (selection.position !== "contents") { + const delimiterRange = + selection.position === "before" + ? selection.selectionContext.leadingDelimiterRange + : selection.selectionContext.trailingDelimiterRange; + + return delimiterRange == null + ? selection + : updateTypedSelectionRange(selection, delimiterRange); + } + const usedSelection = selection.selectionContext.outerSelection ?? selection.selection.selection; @@ -23,15 +33,16 @@ export function performInsideOutsideAdjustment( ? usedSelection.union(delimiterRange) : usedSelection; - return update(selection, { - selection: { - selection: (s) => - s.isReversed - ? new Selection(range.end, range.start) - : new Selection(range.start, range.end), - }, - }); + return updateTypedSelectionRange(selection, range); } return selection; } + +export function performInsideAdjustment(selection: TypedSelection) { + return performInsideOutsideAdjustment(selection, "inside"); +} + +export function performOutsideAdjustment(selection: TypedSelection) { + return performInsideOutsideAdjustment(selection, "outside"); +} diff --git a/src/processTargets.ts b/src/processTargets.ts index 551496b681..d5cdb968d9 100644 --- a/src/processTargets.ts +++ b/src/processTargets.ts @@ -1,8 +1,7 @@ import { concat, range, zip } from "lodash"; import update from "immutability-helper"; import { SyntaxNode } from "web-tree-sitter"; -import * as vscode from "vscode"; -import { Selection, Range, Position } from "vscode"; +import { Selection, Range, Position, Location } from "vscode"; import { nodeMatchers } from "./languages"; import { Mark, @@ -14,6 +13,7 @@ import { SelectionWithEditor, Target, TypedSelection, + Modifier, } from "./Types"; import { performInsideOutsideAdjustment } from "./performInsideOutsideAdjustment"; import { SUBWORD_MATCHER } from "./constants"; @@ -219,7 +219,7 @@ function transformSelection( return [{ selection, context: {} }]; case "containingScope": var node: SyntaxNode | null = context.getNodeAtLocation( - new vscode.Location(selection.editor.document.uri, selection.selection) + new Location(selection.editor.document.uri, selection.selection) ); const nodeMatcher = @@ -304,7 +304,7 @@ function createTypedSelection( selection: SelectionWithEditor, selectionContext: SelectionContext ): TypedSelection { - const { selectionType, insideOutsideType, position } = target; + const { selectionType, insideOutsideType, position, modifier } = target; const { document } = selection.editor; switch (selectionType) { @@ -314,7 +314,11 @@ function createTypedSelection( selectionType, position, insideOutsideType, - selectionContext: getTokenSelectionContext(selection, selectionContext), + selectionContext: getTokenSelectionContext( + selection, + modifier, + selectionContext + ), }; case "line": { @@ -445,6 +449,7 @@ function performPositionAdjustment( function getTokenSelectionContext( selection: SelectionWithEditor, + modifier: Modifier, selectionContext: SelectionContext ): SelectionContext { if (!isSelectionContextEmpty(selectionContext)) { @@ -482,19 +487,20 @@ function getTokenSelectionContext( ) : null; - // Didn't find any delimiters - if (leadingDelimiterRange == null && trailingDelimiterRange == null) { - return selectionContext; + if ( + leadingDelimiterRange != null || + trailingDelimiterRange != null || + modifier.type !== "subpiece" + ) { + return { + isInDelimitedList: true, + containingListDelimiter: " ", + leadingDelimiterRange, + trailingDelimiterRange, + }; } - return { - isInDelimitedList: true, - containingListDelimiter: document.getText( - (trailingDelimiterRange ?? leadingDelimiterRange)! - ), - leadingDelimiterRange, - trailingDelimiterRange, - }; + return selectionContext; } // TODO Clean this up once we have rich targets and better polymorphic diff --git a/src/selectionUtils.ts b/src/selectionUtils.ts new file mode 100644 index 0000000000..0f3d209e8a --- /dev/null +++ b/src/selectionUtils.ts @@ -0,0 +1,31 @@ +import { Range, Selection, Position } from "vscode"; +import update from "immutability-helper"; +import { TypedSelection } from "./Types"; + +export function selectionFromRange( + range: Range, + isReversed: boolean +): Selection { + const { start, end } = range; + return isReversed ? new Selection(end, start) : new Selection(start, end); +} + +/** + * Returns a copy of the given typed selection so that the new selection has the new given range + * preserving the direction of the original selection + * + * @param selection The original typed selection to Update + * @param range The new range for the given selection + * @returns The updated typed selection + */ +export function updateTypedSelectionRange( + selection: TypedSelection, + range: Range +): TypedSelection { + return update(selection, { + selection: { + selection: () => + selectionFromRange(range, selection.selection.selection.isReversed), + }, + }); +}