From f102b6278524300a4877fc216f7af9c8945a7b23 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Sun, 25 Jul 2021 01:15:41 +0200 Subject: [PATCH] Added selection type paragraph (#125) * added selection type paragraph * include empty line for paragraph delimiter * Update src/processTargets.ts Co-authored-by: Pokey Rule * expand the limited range four paragraph Co-authored-by: Pokey Rule --- src/Types.ts | 2 +- src/inferFullTargets.ts | 2 +- src/performInsideOutsideAdjustment.ts | 19 +- src/processTargets.ts | 179 +++++++++++++----- src/selectionType.ts | 2 +- .../fixtures/inferFullTargets.fixture.ts | 18 +- 6 files changed, 149 insertions(+), 73 deletions(-) diff --git a/src/Types.ts b/src/Types.ts index 0ccb4b95e..31bde79f4 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -99,7 +99,7 @@ export type SelectionType = | "character" | "token" | "line" - | "block" + | "paragraph" | "document"; export type Position = "before" | "after" | "contents"; export type InsideOutsideType = "inside" | "outside" | null; diff --git a/src/inferFullTargets.ts b/src/inferFullTargets.ts index b0a41fd79..77ed9be23 100644 --- a/src/inferFullTargets.ts +++ b/src/inferFullTargets.ts @@ -441,7 +441,7 @@ export function inferRangeEndTarget( function getContentSelectionType(contents: string[]): SelectionType { if (contents.every((string) => string.endsWith("\n"))) { if (contents.every((string) => string.startsWith("\n"))) { - return "block"; + return "paragraph"; } return "line"; } diff --git a/src/performInsideOutsideAdjustment.ts b/src/performInsideOutsideAdjustment.ts index b4a497a7e..2d4631d77 100644 --- a/src/performInsideOutsideAdjustment.ts +++ b/src/performInsideOutsideAdjustment.ts @@ -10,23 +10,18 @@ export function performInsideOutsideAdjustment( selection.insideOutsideType ?? preferredInsideOutsideType; if (insideOutsideType === "outside") { - const delimiterRange = - selection.selectionContext.trailingDelimiterRange ?? - selection.selectionContext.leadingDelimiterRange; - const usedSelection = selection.selectionContext.outerSelection ?? selection.selection.selection; - if (delimiterRange == null) { - return update(selection, { - selection: { - selection: () => usedSelection, - }, - }); - } + const delimiterRange = + selection.selectionContext.trailingDelimiterRange ?? + selection.selectionContext.leadingDelimiterRange; - const range = usedSelection.union(delimiterRange); + const range = + delimiterRange != null + ? usedSelection.union(delimiterRange) + : usedSelection; return update(selection, { selection: { diff --git a/src/processTargets.ts b/src/processTargets.ts index daf3a9834..27b36144a 100644 --- a/src/processTargets.ts +++ b/src/processTargets.ts @@ -2,7 +2,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 } from "vscode"; +import { Selection, Range, Position } from "vscode"; import { nodeMatchers } from "./languages"; import { Mark, @@ -253,57 +253,47 @@ function createTypedSelection( selection: SelectionWithEditor, selectionContext: SelectionContext ): TypedSelection { - const { selectionType, insideOutsideType } = target; - let start: vscode.Position; - let end: vscode.Position; + const { selectionType, insideOutsideType, position } = target; + const { document } = selection.editor; switch (selectionType) { case "token": return { selection, selectionType, + position, + insideOutsideType, selectionContext: getTokenSelectionContext(selection, selectionContext), - insideOutsideType: target.insideOutsideType ?? null, - position: target.position, }; - case "line": - const originalSelectionStart = selection.selection.start; - const originalSelectionEnd = selection.selection.end; - - const startLine = selection.editor.document.lineAt( - originalSelectionStart - ); - const endLine = selection.editor.document.lineAt(originalSelectionEnd); - start = new vscode.Position( - originalSelectionStart.line, + case "line": { + const startLine = document.lineAt(selection.selection.start); + const endLine = document.lineAt(selection.selection.end); + const start = new Position( + startLine.lineNumber, startLine.firstNonWhitespaceCharacterIndex ); - end = endLine.range.end; + const end = endLine.range.end; - const isSelectionReversed = selection.selection.isReversed; - const anchor = isSelectionReversed ? start : end; - const active = isSelectionReversed ? end : start; - const newSelection = new Selection(anchor, active); - selection = { - selection: newSelection, - editor: selection.editor, - }; + const newSelection = update(selection, { + selection: (s) => + s.isReversed ? new Selection(end, start) : new Selection(start, end), + }); return { - selection, + selection: newSelection, selectionType, - selectionContext: getLineSelectionContext(selection, selectionContext), - insideOutsideType: target.insideOutsideType ?? null, - position: target.position, + position, + insideOutsideType, + selectionContext: getLineSelectionContext(newSelection), }; - case "document": - const document = selection.editor.document; - // From https://stackoverflow.com/a/46427868 + } + + case "document": { const firstLine = document.lineAt(0); const lastLine = document.lineAt(document.lineCount - 1); - start = firstLine.range.start; - end = lastLine.range.end; + const start = firstLine.range.start; + const end = lastLine.range.end; return { selection: update(selection, { @@ -313,11 +303,51 @@ function createTypedSelection( : new Selection(start, end), }), selectionType, + position, + insideOutsideType, selectionContext, - insideOutsideType: target.insideOutsideType ?? null, - position: target.position, }; - case "block": + } + + case "paragraph": { + let startLine = document.lineAt(selection.selection.start); + while (startLine.lineNumber > 0) { + const line = document.lineAt(startLine.lineNumber - 1); + if (line.isEmptyOrWhitespace) { + break; + } + startLine = line; + } + const lineCount = document.lineCount; + let endLine = document.lineAt(selection.selection.end); + while (endLine.lineNumber + 1 < lineCount) { + const line = document.lineAt(endLine.lineNumber + 1); + if (line.isEmptyOrWhitespace) { + break; + } + endLine = line; + } + + const start = new Position( + startLine.lineNumber, + startLine.firstNonWhitespaceCharacterIndex + ); + const end = endLine.range.end; + + const newSelection = update(selection, { + selection: (s) => + s.isReversed ? new Selection(end, start) : new Selection(start, end), + }); + + return { + selection: newSelection, + position, + selectionType, + insideOutsideType, + selectionContext: getParagraphSelectionContext(newSelection), + }; + } + case "character": throw new Error("Not implemented"); } @@ -428,11 +458,11 @@ function isSelectionContextEmpty(selectionContext: SelectionContext) { } function getLineSelectionContext( - selection: SelectionWithEditor, - selectionContext: SelectionContext + selection: SelectionWithEditor ): SelectionContext { - const document = selection.editor.document; - const { start, end } = selection.selection; + const { document } = selection.editor; + const start = selection.selection.start; + const end = selection.selection.end; const leadingDelimiterRange = start.line > 0 @@ -446,14 +476,9 @@ function getLineSelectionContext( const trailingDelimiterRange = end.line + 1 < document.lineCount - ? new Range(end.line, 0, end.line + 1, 0) + ? new Range(end.line, end.character, end.line + 1, 0) : null; - // Didn't find any delimiters - if (leadingDelimiterRange == null && trailingDelimiterRange == null) { - return selectionContext; - } - // Outer selection contains the entire lines const outerSelection = new Selection( start.line, @@ -462,9 +487,65 @@ function getLineSelectionContext( selection.editor.document.lineAt(end.line).range.end.character ); + const isInDelimitedList = + leadingDelimiterRange != null || trailingDelimiterRange != null; + return { - isInDelimitedList: true, - containingListDelimiter: "\n", + isInDelimitedList, + containingListDelimiter: isInDelimitedList ? "\n" : undefined, + leadingDelimiterRange, + trailingDelimiterRange, + outerSelection, + }; +} + +function getParagraphSelectionContext( + selection: SelectionWithEditor +): SelectionContext { + const { document } = selection.editor; + const start = selection.selection.start; + const end = selection.selection.end; + + let leadingLine = document.lineAt(selection.selection.start); + while (leadingLine.lineNumber > 0) { + leadingLine = document.lineAt(leadingLine.lineNumber - 1); + if (!leadingLine.isEmptyOrWhitespace) { + break; + } + } + + const lineCount = document.lineCount; + let trailingLine = document.lineAt(selection.selection.end); + while (trailingLine.lineNumber + 1 < lineCount) { + trailingLine = document.lineAt(trailingLine.lineNumber + 1); + if (!trailingLine.isEmptyOrWhitespace) { + break; + } + } + + const leadingDelimiterRange = + leadingLine.lineNumber !== start.line + ? new Range(leadingLine.range.end, start) + : null; + const trailingDelimiterRange = + trailingLine.lineNumber !== end.line + ? new Range(end, trailingLine.range.start) + : null; + + // Outer selection contains the entire lines + const outerSelection = new Selection( + start.line, + 0, + end.line, + selection.editor.document.lineAt(end.line).range.end.character + ); + + const isInDelimitedList = + leadingDelimiterRange != null || trailingDelimiterRange != null; + + return { + isInDelimitedList, + containingListDelimiter: isInDelimitedList ? "\n\n" : undefined, leadingDelimiterRange, trailingDelimiterRange, outerSelection, diff --git a/src/selectionType.ts b/src/selectionType.ts index 5cf80d86a..a30a35ad3 100644 --- a/src/selectionType.ts +++ b/src/selectionType.ts @@ -1,5 +1,5 @@ import { SelectionType } from "./Types"; export function isLineSelectionType(selectionType: SelectionType) { - return selectionType === "line" || selectionType === "block"; + return selectionType === "line" || selectionType === "paragraph"; } diff --git a/src/test/suite/fixtures/inferFullTargets.fixture.ts b/src/test/suite/fixtures/inferFullTargets.fixture.ts index 1edba630f..b2e1e6e98 100644 --- a/src/test/suite/fixtures/inferFullTargets.fixture.ts +++ b/src/test/suite/fixtures/inferFullTargets.fixture.ts @@ -330,7 +330,7 @@ const fixture: Fixture[] = [ }, end: { type: "primitive", - selectionType: "block", + selectionType: "paragraph", mark: { type: "decoratedSymbol", symbolColor: "red", @@ -349,7 +349,7 @@ const fixture: Fixture[] = [ mark: { type: "cursor", }, - selectionType: "block", + selectionType: "paragraph", position: "contents", modifier: { type: "identity", @@ -362,7 +362,7 @@ const fixture: Fixture[] = [ symbolColor: "red", character: "h", }, - selectionType: "block", + selectionType: "paragraph", position: "contents", modifier: { type: "identity", @@ -786,7 +786,7 @@ const fixture: Fixture[] = [ symbolColor: "default", character: "f", }, - selectionType: "block", + selectionType: "paragraph", position: "after", modifier: { type: "identity", @@ -817,7 +817,7 @@ const fixture: Fixture[] = [ mark: { type: "cursor", }, - selectionType: "block", + selectionType: "paragraph", position: "after", modifier: { type: "identity", @@ -848,7 +848,7 @@ const fixture: Fixture[] = [ mark: { type: "cursor", }, - selectionType: "block", + selectionType: "paragraph", position: "contents", modifier: { type: "identity", @@ -908,7 +908,7 @@ const fixture: Fixture[] = [ mark: { type: "cursor", }, - selectionType: "block", + selectionType: "paragraph", position: "contents", modifier: { type: "identity", @@ -1814,7 +1814,7 @@ const fixture: Fixture[] = [ end: { type: "primitive", position: "end", - selectionType: "block", + selectionType: "paragraph", }, }, ], @@ -1839,7 +1839,7 @@ const fixture: Fixture[] = [ mark: { type: "cursor", }, - selectionType: "block", + selectionType: "paragraph", position: "end", modifier: { type: "identity",