mirror of
https://github.com/Silverquark/dance.git
synced 2024-08-16 22:30:55 +03:00
api: remove all circular dependencies
This commit is contained in:
parent
d17c6cdebd
commit
a3213bdb37
@ -4,21 +4,19 @@ module.exports = {
|
||||
{
|
||||
name: "no-circular",
|
||||
severity: "error",
|
||||
from: {
|
||||
pathNot: "^src/api",
|
||||
},
|
||||
from: {},
|
||||
to: {
|
||||
circular: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "api-only-depends-on-api/index-and-utils",
|
||||
name: "api-only-depends-on-api-and-utils",
|
||||
severity: "error",
|
||||
from: {
|
||||
path: "^src/api/(?!index)",
|
||||
},
|
||||
to: {
|
||||
pathNot: "^src/(api/index|utils)",
|
||||
pathNot: "^src/(api/(?!index)|utils)",
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -32,10 +30,10 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "only-api/index-depends-on-api",
|
||||
name: "only-api-depends-on-api",
|
||||
severity: "error",
|
||||
from: {
|
||||
pathNot: "^src/api/index",
|
||||
pathNot: "^src/api",
|
||||
},
|
||||
to: {
|
||||
path: "^src/api/(?!index)",
|
||||
|
@ -1,6 +1,6 @@
|
||||
import * as vscode from "vscode";
|
||||
|
||||
import { Context } from ".";
|
||||
import { Context } from "./context";
|
||||
|
||||
/**
|
||||
* Copies the given text to the clipboard.
|
||||
|
@ -1,6 +1,6 @@
|
||||
import * as vscode from "vscode";
|
||||
|
||||
import { SelectionBehavior, Selections } from ".";
|
||||
import { SelectionBehavior } from "./types";
|
||||
import type { CommandDescriptor } from "../commands";
|
||||
import type { PerEditorState } from "../state/editors";
|
||||
import type { Extension } from "../state/extension";
|
||||
@ -302,7 +302,7 @@ export class Context extends ContextWithoutActiveEditor {
|
||||
const editor = this.editor as vscode.TextEditor;
|
||||
|
||||
if (this.selectionBehavior === SelectionBehavior.Character) {
|
||||
return Selections.fromCharacterMode(editor.selections, editor.document);
|
||||
return selectionsFromCharacterMode(editor.selections, editor.document);
|
||||
}
|
||||
|
||||
return editor.selections;
|
||||
@ -318,7 +318,7 @@ export class Context extends ContextWithoutActiveEditor {
|
||||
const editor = this.editor as vscode.TextEditor;
|
||||
|
||||
if (this.selectionBehavior === SelectionBehavior.Character) {
|
||||
selections = Selections.toCharacterMode(selections, editor.document);
|
||||
selections = selectionsToCharacterMode(selections, editor.document);
|
||||
}
|
||||
|
||||
editor.selections = selections as vscode.Selection[];
|
||||
@ -333,7 +333,7 @@ export class Context extends ContextWithoutActiveEditor {
|
||||
const editor = this.editor as vscode.TextEditor;
|
||||
|
||||
if (this.selectionBehavior === SelectionBehavior.Character) {
|
||||
return Selections.fromCharacterMode([editor.selection], editor.document)[0];
|
||||
return selectionsFromCharacterMode([editor.selection], editor.document)[0];
|
||||
}
|
||||
|
||||
return editor.selection;
|
||||
@ -581,3 +581,221 @@ export function insertUndoStop(editor?: vscode.TextEditor) {
|
||||
|
||||
return Context.current.insertUndoStop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms a list of caret-mode selections (that is, regular selections as
|
||||
* manipulated internally) into a list of character-mode selections (that is,
|
||||
* selections modified to include a block character in them).
|
||||
*
|
||||
* This function should be used before setting the selections of a
|
||||
* `vscode.TextEditor` if the selection behavior is `Character`.
|
||||
*
|
||||
* ### Example
|
||||
* Forward-facing, non-empty selections are reduced by one character.
|
||||
*
|
||||
* ```js
|
||||
* // One-character selection becomes empty.
|
||||
* expect(Selections.toCharacterMode([Selections.fromAnchorActive(0, 0, 0, 1)]), "to satisfy", [
|
||||
* expect.it("to be empty at coords", 0, 0),
|
||||
* ]);
|
||||
*
|
||||
* // One-character selection becomes empty (at line break).
|
||||
* expect(Selections.toCharacterMode([Selections.fromAnchorActive(0, 1, 1, 0)]), "to satisfy", [
|
||||
* expect.it("to be empty at coords", 0, 1),
|
||||
* ]);
|
||||
*
|
||||
* // Forward-facing selection becomes shorter.
|
||||
* expect(Selections.toCharacterMode([Selections.fromAnchorActive(0, 0, 1, 1)]), "to satisfy", [
|
||||
* expect.it("to have anchor at coords", 0, 0).and("to have cursor at coords", 1, 0),
|
||||
* ]);
|
||||
*
|
||||
* // One-character selection becomes empty (reversed).
|
||||
* expect(Selections.toCharacterMode([Selections.fromAnchorActive(0, 1, 0, 0)]), "to satisfy", [
|
||||
* expect.it("to be empty at coords", 0, 0),
|
||||
* ]);
|
||||
*
|
||||
* // One-character selection becomes empty (reversed, at line break).
|
||||
* expect(Selections.toCharacterMode([Selections.fromAnchorActive(1, 0, 0, 1)]), "to satisfy", [
|
||||
* expect.it("to be empty at coords", 0, 1),
|
||||
* ]);
|
||||
*
|
||||
* // Reversed selection stays as-is.
|
||||
* expect(Selections.toCharacterMode([Selections.fromAnchorActive(1, 1, 0, 0)]), "to satisfy", [
|
||||
* expect.it("to have anchor at coords", 1, 1).and("to have cursor at coords", 0, 0),
|
||||
* ]);
|
||||
*
|
||||
* // Empty selection stays as-is.
|
||||
* expect(Selections.toCharacterMode([Selections.empty(1, 1)]), "to satisfy", [
|
||||
* expect.it("to be empty at coords", 1, 1),
|
||||
* ]);
|
||||
* ```
|
||||
*
|
||||
* With:
|
||||
* ```
|
||||
* a
|
||||
* b
|
||||
* ```
|
||||
*/
|
||||
export function selectionsToCharacterMode(
|
||||
selections: readonly vscode.Selection[],
|
||||
document?: vscode.TextDocument,
|
||||
) {
|
||||
const characterModeSelections = [] as vscode.Selection[];
|
||||
|
||||
for (const selection of selections) {
|
||||
const selectionActive = selection.active,
|
||||
selectionActiveLine = selectionActive.line,
|
||||
selectionActiveCharacter = selectionActive.character,
|
||||
selectionAnchor = selection.anchor,
|
||||
selectionAnchorLine = selectionAnchor.line,
|
||||
selectionAnchorCharacter = selectionAnchor.character;
|
||||
let active = selectionActive,
|
||||
anchor = selectionAnchor,
|
||||
changed = false;
|
||||
|
||||
if (selectionAnchorLine === selectionActiveLine) {
|
||||
if (selectionAnchorCharacter + 1 === selectionActiveCharacter) {
|
||||
// Selection is one-character long: make it empty.
|
||||
active = selectionAnchor;
|
||||
changed = true;
|
||||
} else if (selectionAnchorCharacter - 1 === selectionActiveCharacter) {
|
||||
// Selection is reversed and one-character long: make it empty.
|
||||
anchor = selectionActive;
|
||||
changed = true;
|
||||
} else if (selectionAnchorCharacter < selectionActiveCharacter) {
|
||||
// Selection is strictly forward-facing: make it shorter.
|
||||
active = new vscode.Position(selectionActiveLine, selectionActiveCharacter - 1);
|
||||
changed = true;
|
||||
} else {
|
||||
// Selection is reversed or empty: do nothing.
|
||||
}
|
||||
} else if (selectionAnchorLine < selectionActiveLine) {
|
||||
// Selection is strictly forward-facing: make it shorter.
|
||||
if (selectionActiveCharacter > 0) {
|
||||
active = new vscode.Position(selectionActiveLine, selectionActiveCharacter - 1);
|
||||
changed = true;
|
||||
} else {
|
||||
// The active character is the first one, so we have to get some
|
||||
// information from the document.
|
||||
if (document === undefined) {
|
||||
document = Context.current.document;
|
||||
}
|
||||
|
||||
const activePrevLine = selectionActiveLine - 1,
|
||||
activePrevLineLength = document.lineAt(activePrevLine).text.length;
|
||||
|
||||
active = new vscode.Position(activePrevLine, activePrevLineLength);
|
||||
changed = true;
|
||||
}
|
||||
} else if (selectionAnchorLine === selectionActiveLine + 1
|
||||
&& selectionAnchorCharacter === 0
|
||||
&& selectionActiveCharacter === (document ?? (document = Context.current.document))
|
||||
.lineAt(selectionActiveLine).text.length) {
|
||||
// Selection is reversed and one-character long: make it empty.
|
||||
anchor = selectionActive;
|
||||
changed = true;
|
||||
} else {
|
||||
// Selection is reversed: do nothing.
|
||||
}
|
||||
|
||||
characterModeSelections.push(changed ? new vscode.Selection(anchor, active) : selection);
|
||||
}
|
||||
|
||||
return characterModeSelections;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverses the changes made by `toCharacterMode` by increasing by one the
|
||||
* length of every empty or forward-facing selection.
|
||||
*
|
||||
* This function should be used on the selections of a `vscode.TextEditor` if
|
||||
* the selection behavior is `Character`.
|
||||
*
|
||||
* ### Example
|
||||
* Selections remain empty in empty documents.
|
||||
*
|
||||
* ```js
|
||||
* expect(Selections.fromCharacterMode([Selections.empty(0, 0)]), "to satisfy", [
|
||||
* expect.it("to be empty at coords", 0, 0),
|
||||
* ]);
|
||||
* ```
|
||||
*
|
||||
* With:
|
||||
* ```
|
||||
* ```
|
||||
*
|
||||
* ### Example
|
||||
* Empty selections automatically become 1-character selections.
|
||||
*
|
||||
* ```js
|
||||
* expect(Selections.fromCharacterMode([Selections.empty(0, 0)]), "to satisfy", [
|
||||
* expect.it("to have anchor at coords", 0, 0).and("to have cursor at coords", 0, 1),
|
||||
* ]);
|
||||
*
|
||||
* // At the end of the line, it selects the line ending:
|
||||
* expect(Selections.fromCharacterMode([Selections.empty(0, 1)]), "to satisfy", [
|
||||
* expect.it("to have anchor at coords", 0, 1).and("to have cursor at coords", 1, 0),
|
||||
* ]);
|
||||
*
|
||||
* // But it does nothing at the end of the document:
|
||||
* expect(Selections.fromCharacterMode([Selections.empty(2, 0)]), "to satisfy", [
|
||||
* expect.it("to be empty at coords", 2, 0),
|
||||
* ]);
|
||||
* ```
|
||||
*
|
||||
* With:
|
||||
* ```
|
||||
* a
|
||||
* b
|
||||
*
|
||||
* ```
|
||||
*/
|
||||
export function selectionsFromCharacterMode(
|
||||
selections: readonly vscode.Selection[],
|
||||
document?: vscode.TextDocument,
|
||||
) {
|
||||
const caretModeSelections = [] as vscode.Selection[];
|
||||
|
||||
for (const selection of selections) {
|
||||
const selectionActive = selection.active,
|
||||
selectionActiveLine = selectionActive.line,
|
||||
selectionActiveCharacter = selectionActive.character,
|
||||
selectionAnchor = selection.anchor,
|
||||
selectionAnchorLine = selectionAnchor.line,
|
||||
selectionAnchorCharacter = selectionAnchor.character;
|
||||
let active = selectionActive,
|
||||
changed = false;
|
||||
|
||||
const isEmptyOrForwardFacing = selectionAnchorLine < selectionActiveLine
|
||||
|| (selectionAnchorLine === selectionActiveLine
|
||||
&& selectionAnchorCharacter <= selectionActiveCharacter);
|
||||
|
||||
if (isEmptyOrForwardFacing) {
|
||||
// Selection is empty or forward-facing: extend it if possible.
|
||||
if (document === undefined) {
|
||||
document = Context.current.document;
|
||||
}
|
||||
|
||||
const lineLength = document.lineAt(selectionActiveLine).text.length;
|
||||
|
||||
if (selectionActiveCharacter === lineLength) {
|
||||
// Character is at the end of the line.
|
||||
if (selectionActiveLine + 1 < document.lineCount) {
|
||||
// This is not the last line: we can extend the selection.
|
||||
active = new vscode.Position(selectionActiveLine + 1, 0);
|
||||
changed = true;
|
||||
} else {
|
||||
// This is the last line: we cannot do anything.
|
||||
}
|
||||
} else {
|
||||
// Character is not at the end of the line: we can extend the selection.
|
||||
active = new vscode.Position(selectionActiveLine, selectionActiveCharacter + 1);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
caretModeSelections.push(changed ? new vscode.Selection(selectionAnchor, active) : selection);
|
||||
}
|
||||
|
||||
return caretModeSelections;
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
import * as vscode from "vscode";
|
||||
|
||||
import { Context, edit, Positions, Selections } from "..";
|
||||
import { Context, edit } from "../context";
|
||||
import { Positions } from "../positions";
|
||||
import { Selections } from "../selections";
|
||||
import { TrackedSelection } from "../../utils/tracked-selection";
|
||||
|
||||
const enum Constants {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import * as vscode from "vscode";
|
||||
|
||||
import { Context, edit } from "..";
|
||||
import { Context, edit } from "../context";
|
||||
import { blankCharacters } from "../../utils/charset";
|
||||
|
||||
/**
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Context } from ".";
|
||||
import { Context } from "./context";
|
||||
|
||||
export * from "../utils/errors";
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import * as vscode from "vscode";
|
||||
|
||||
import { Context } from ".";
|
||||
import { Context } from "./context";
|
||||
|
||||
/**
|
||||
* Un-does the last action.
|
||||
|
@ -23,61 +23,7 @@ export * from "./search/pairs";
|
||||
export * from "./search/range";
|
||||
export * from "./search/word";
|
||||
export * from "./selections";
|
||||
|
||||
/**
|
||||
* Direction of an operation.
|
||||
*/
|
||||
export const enum Direction {
|
||||
/**
|
||||
* Forward direction (`1`).
|
||||
*/
|
||||
Forward = 1,
|
||||
|
||||
/**
|
||||
* Backward direction (`-1`).
|
||||
*/
|
||||
Backward = -1,
|
||||
}
|
||||
|
||||
/**
|
||||
* Behavior of a shift.
|
||||
*/
|
||||
export const enum Shift {
|
||||
/**
|
||||
* Jump to the position.
|
||||
*/
|
||||
Jump,
|
||||
|
||||
/**
|
||||
* Select to the position.
|
||||
*/
|
||||
Select,
|
||||
|
||||
/**
|
||||
* Extend to the position.
|
||||
*/
|
||||
Extend,
|
||||
}
|
||||
|
||||
/**
|
||||
* Selection behavior of an operation.
|
||||
*/
|
||||
export const enum SelectionBehavior {
|
||||
/**
|
||||
* VS Code-like caret selections.
|
||||
*/
|
||||
Caret = 1,
|
||||
/**
|
||||
* Kakoune-like character selections.
|
||||
*/
|
||||
Character = 2,
|
||||
}
|
||||
|
||||
export const Forward = Direction.Forward,
|
||||
Backward = Direction.Backward,
|
||||
Jump = Shift.Jump,
|
||||
Select = Shift.Select,
|
||||
Extend = Shift.Extend;
|
||||
export * from "./types";
|
||||
|
||||
/**
|
||||
* Returns the module exported by the extension with the given identifier.
|
||||
|
@ -1,6 +1,6 @@
|
||||
import * as vscode from "vscode";
|
||||
|
||||
import { Context } from ".";
|
||||
import { Context } from "./context";
|
||||
|
||||
/**
|
||||
* Returns the 0-based number of the first visible line in the current editor.
|
||||
|
@ -1,6 +1,7 @@
|
||||
import * as vscode from "vscode";
|
||||
|
||||
import { Context, keypress, prompt } from ".";
|
||||
import { Context } from "./context";
|
||||
import { keypress, prompt } from "./prompt";
|
||||
|
||||
export interface Menu {
|
||||
readonly items: Menu.Items;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Context } from ".";
|
||||
import { Context } from "./context";
|
||||
|
||||
/**
|
||||
* Switches to the mode with the given name.
|
||||
|
@ -1,6 +1,7 @@
|
||||
import * as vscode from "vscode";
|
||||
|
||||
import { Context, Direction } from ".";
|
||||
import { Context } from "./context";
|
||||
import { Direction } from "./types";
|
||||
|
||||
/**
|
||||
* Returns the position right after the given position, or `undefined` if
|
||||
|
@ -1,6 +1,7 @@
|
||||
import * as vscode from "vscode";
|
||||
|
||||
import { Context, Selections } from ".";
|
||||
import { Context } from "./context";
|
||||
import { Selections } from "./selections";
|
||||
import type { Input, SetInput } from "../commands";
|
||||
import { CancellationError } from "../utils/errors";
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import * as vscode from "vscode";
|
||||
|
||||
import { Context } from ".";
|
||||
import { Context } from "./context";
|
||||
import type { Register } from "../state/registers";
|
||||
|
||||
/**
|
||||
|
@ -1,6 +1,6 @@
|
||||
import * as vscode from "vscode";
|
||||
|
||||
import { Context } from ".";
|
||||
import { Context } from "./context";
|
||||
import type { CommandDescriptor } from "../commands";
|
||||
import { parseRegExpWithReplacement } from "../utils/regexp";
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
import * as vscode from "vscode";
|
||||
|
||||
import { Context, Direction, Positions } from "..";
|
||||
import { Context } from "../context";
|
||||
import { Positions } from "../positions";
|
||||
import { Direction } from "../types";
|
||||
import { canMatchLineFeed, execLast, matchesStaticStrings } from "../../utils/regexp";
|
||||
|
||||
/**
|
||||
|
@ -1,6 +1,8 @@
|
||||
import * as vscode from "vscode";
|
||||
|
||||
import { Context, lineByLine, Positions } from "..";
|
||||
import { lineByLine } from "./move";
|
||||
import { Context } from "../context";
|
||||
import { Positions } from "../positions";
|
||||
|
||||
/**
|
||||
* Returns the range of lines matching the given `RegExp` before and after
|
||||
|
@ -1,6 +1,8 @@
|
||||
import * as vscode from "vscode";
|
||||
|
||||
import { Context, Direction, Positions } from "..";
|
||||
import { Context } from "../context";
|
||||
import { Positions } from "../positions";
|
||||
import { Direction } from "../types";
|
||||
|
||||
/**
|
||||
* Moves the given position towards the given direction until the given string
|
||||
|
@ -1,6 +1,8 @@
|
||||
import * as vscode from "vscode";
|
||||
|
||||
import { Context, Direction, Positions } from "..";
|
||||
import { Context } from "../context";
|
||||
import { Positions } from "../positions";
|
||||
import { Direction } from "../types";
|
||||
|
||||
/**
|
||||
* Moves the given position towards the given direction as long as the given
|
||||
|
@ -1,6 +1,9 @@
|
||||
import * as vscode from "vscode";
|
||||
|
||||
import { Context, Direction, Positions, search } from "..";
|
||||
import { search } from "./index";
|
||||
import { Context } from "../context";
|
||||
import { Positions } from "../positions";
|
||||
import { Direction } from "../types";
|
||||
import { ArgumentError } from "../../utils/errors";
|
||||
import { anyRegExp, escapeForRegExp } from "../../utils/regexp";
|
||||
|
||||
|
@ -1,6 +1,10 @@
|
||||
import * as vscode from "vscode";
|
||||
|
||||
import { Context, Direction, Lines, moveWhile, Positions } from "..";
|
||||
import { moveWhile } from "./move";
|
||||
import { Context } from "../context";
|
||||
import { Lines } from "../lines";
|
||||
import { Positions } from "../positions";
|
||||
import { Direction } from "../types";
|
||||
import { CharSet, getCharSetFunction } from "../../utils/charset";
|
||||
import { CharCodes } from "../../utils/regexp";
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
import * as vscode from "vscode";
|
||||
|
||||
import { Context, Direction, SelectionBehavior, skipEmptyLines } from "..";
|
||||
import { skipEmptyLines } from "./move";
|
||||
import { Context } from "../context";
|
||||
import { Direction, SelectionBehavior } from "../types";
|
||||
import { CharSet, getCharSetFunction } from "../../utils/charset";
|
||||
|
||||
const enum WordCategory {
|
||||
|
@ -1,6 +1,9 @@
|
||||
import * as vscode from "vscode";
|
||||
|
||||
import { Context, Direction, Lines, NotASelectionError, Positions, SelectionBehavior, Shift } from ".";
|
||||
import { Context, selectionsFromCharacterMode, selectionsToCharacterMode } from "./context";
|
||||
import { NotASelectionError } from "./errors";
|
||||
import { Positions } from "./positions";
|
||||
import { Direction, SelectionBehavior, Shift } from "./types";
|
||||
import { execRange, splitRange } from "../utils/regexp";
|
||||
|
||||
/**
|
||||
@ -1768,222 +1771,8 @@ export namespace Selections {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms a list of caret-mode selections (that is, regular selections as
|
||||
* manipulated internally) into a list of character-mode selections (that is,
|
||||
* selections modified to include a block character in them).
|
||||
*
|
||||
* This function should be used before setting the selections of a
|
||||
* `vscode.TextEditor` if the selection behavior is `Character`.
|
||||
*
|
||||
* ### Example
|
||||
* Forward-facing, non-empty selections are reduced by one character.
|
||||
*
|
||||
* ```js
|
||||
* // One-character selection becomes empty.
|
||||
* expect(Selections.toCharacterMode([Selections.fromAnchorActive(0, 0, 0, 1)]), "to satisfy", [
|
||||
* expect.it("to be empty at coords", 0, 0),
|
||||
* ]);
|
||||
*
|
||||
* // One-character selection becomes empty (at line break).
|
||||
* expect(Selections.toCharacterMode([Selections.fromAnchorActive(0, 1, 1, 0)]), "to satisfy", [
|
||||
* expect.it("to be empty at coords", 0, 1),
|
||||
* ]);
|
||||
*
|
||||
* // Forward-facing selection becomes shorter.
|
||||
* expect(Selections.toCharacterMode([Selections.fromAnchorActive(0, 0, 1, 1)]), "to satisfy", [
|
||||
* expect.it("to have anchor at coords", 0, 0).and("to have cursor at coords", 1, 0),
|
||||
* ]);
|
||||
*
|
||||
* // One-character selection becomes empty (reversed).
|
||||
* expect(Selections.toCharacterMode([Selections.fromAnchorActive(0, 1, 0, 0)]), "to satisfy", [
|
||||
* expect.it("to be empty at coords", 0, 0),
|
||||
* ]);
|
||||
*
|
||||
* // One-character selection becomes empty (reversed, at line break).
|
||||
* expect(Selections.toCharacterMode([Selections.fromAnchorActive(1, 0, 0, 1)]), "to satisfy", [
|
||||
* expect.it("to be empty at coords", 0, 1),
|
||||
* ]);
|
||||
*
|
||||
* // Reversed selection stays as-is.
|
||||
* expect(Selections.toCharacterMode([Selections.fromAnchorActive(1, 1, 0, 0)]), "to satisfy", [
|
||||
* expect.it("to have anchor at coords", 1, 1).and("to have cursor at coords", 0, 0),
|
||||
* ]);
|
||||
*
|
||||
* // Empty selection stays as-is.
|
||||
* expect(Selections.toCharacterMode([Selections.empty(1, 1)]), "to satisfy", [
|
||||
* expect.it("to be empty at coords", 1, 1),
|
||||
* ]);
|
||||
* ```
|
||||
*
|
||||
* With:
|
||||
* ```
|
||||
* a
|
||||
* b
|
||||
* ```
|
||||
*/
|
||||
export function toCharacterMode(
|
||||
selections: readonly vscode.Selection[],
|
||||
document?: vscode.TextDocument,
|
||||
) {
|
||||
const characterModeSelections = [] as vscode.Selection[];
|
||||
|
||||
for (const selection of selections) {
|
||||
const selectionActive = selection.active,
|
||||
selectionActiveLine = selectionActive.line,
|
||||
selectionActiveCharacter = selectionActive.character,
|
||||
selectionAnchor = selection.anchor,
|
||||
selectionAnchorLine = selectionAnchor.line,
|
||||
selectionAnchorCharacter = selectionAnchor.character;
|
||||
let active = selectionActive,
|
||||
anchor = selectionAnchor,
|
||||
changed = false;
|
||||
|
||||
if (selectionAnchorLine === selectionActiveLine) {
|
||||
if (selectionAnchorCharacter + 1 === selectionActiveCharacter) {
|
||||
// Selection is one-character long: make it empty.
|
||||
active = selectionAnchor;
|
||||
changed = true;
|
||||
} else if (selectionAnchorCharacter - 1 === selectionActiveCharacter) {
|
||||
// Selection is reversed and one-character long: make it empty.
|
||||
anchor = selectionActive;
|
||||
changed = true;
|
||||
} else if (selectionAnchorCharacter < selectionActiveCharacter) {
|
||||
// Selection is strictly forward-facing: make it shorter.
|
||||
active = new vscode.Position(selectionActiveLine, selectionActiveCharacter - 1);
|
||||
changed = true;
|
||||
} else {
|
||||
// Selection is reversed or empty: do nothing.
|
||||
}
|
||||
} else if (selectionAnchorLine < selectionActiveLine) {
|
||||
// Selection is strictly forward-facing: make it shorter.
|
||||
if (selectionActiveCharacter > 0) {
|
||||
active = new vscode.Position(selectionActiveLine, selectionActiveCharacter - 1);
|
||||
changed = true;
|
||||
} else {
|
||||
// The active character is the first one, so we have to get some
|
||||
// information from the document.
|
||||
if (document === undefined) {
|
||||
document = Context.current.document;
|
||||
}
|
||||
|
||||
const activePrevLine = selectionActiveLine - 1,
|
||||
activePrevLineLength = document.lineAt(activePrevLine).text.length;
|
||||
|
||||
active = new vscode.Position(activePrevLine, activePrevLineLength);
|
||||
changed = true;
|
||||
}
|
||||
} else if (selectionAnchorLine === selectionActiveLine + 1
|
||||
&& selectionAnchorCharacter === 0
|
||||
&& selectionActiveCharacter === Lines.length(selectionActiveLine, document)) {
|
||||
// Selection is reversed and one-character long: make it empty.
|
||||
anchor = selectionActive;
|
||||
changed = true;
|
||||
} else {
|
||||
// Selection is reversed: do nothing.
|
||||
}
|
||||
|
||||
characterModeSelections.push(changed ? new vscode.Selection(anchor, active) : selection);
|
||||
}
|
||||
|
||||
return characterModeSelections;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverses the changes made by `toCharacterMode` by increasing by one the
|
||||
* length of every empty or forward-facing selection.
|
||||
*
|
||||
* This function should be used on the selections of a `vscode.TextEditor` if
|
||||
* the selection behavior is `Character`.
|
||||
*
|
||||
* ### Example
|
||||
* Selections remain empty in empty documents.
|
||||
*
|
||||
* ```js
|
||||
* expect(Selections.fromCharacterMode([Selections.empty(0, 0)]), "to satisfy", [
|
||||
* expect.it("to be empty at coords", 0, 0),
|
||||
* ]);
|
||||
* ```
|
||||
*
|
||||
* With:
|
||||
* ```
|
||||
* ```
|
||||
*
|
||||
* ### Example
|
||||
* Empty selections automatically become 1-character selections.
|
||||
*
|
||||
* ```js
|
||||
* expect(Selections.fromCharacterMode([Selections.empty(0, 0)]), "to satisfy", [
|
||||
* expect.it("to have anchor at coords", 0, 0).and("to have cursor at coords", 0, 1),
|
||||
* ]);
|
||||
*
|
||||
* // At the end of the line, it selects the line ending:
|
||||
* expect(Selections.fromCharacterMode([Selections.empty(0, 1)]), "to satisfy", [
|
||||
* expect.it("to have anchor at coords", 0, 1).and("to have cursor at coords", 1, 0),
|
||||
* ]);
|
||||
*
|
||||
* // But it does nothing at the end of the document:
|
||||
* expect(Selections.fromCharacterMode([Selections.empty(2, 0)]), "to satisfy", [
|
||||
* expect.it("to be empty at coords", 2, 0),
|
||||
* ]);
|
||||
* ```
|
||||
*
|
||||
* With:
|
||||
* ```
|
||||
* a
|
||||
* b
|
||||
*
|
||||
* ```
|
||||
*/
|
||||
export function fromCharacterMode(
|
||||
selections: readonly vscode.Selection[],
|
||||
document?: vscode.TextDocument,
|
||||
) {
|
||||
const caretModeSelections = [] as vscode.Selection[];
|
||||
|
||||
for (const selection of selections) {
|
||||
const selectionActive = selection.active,
|
||||
selectionActiveLine = selectionActive.line,
|
||||
selectionActiveCharacter = selectionActive.character,
|
||||
selectionAnchor = selection.anchor,
|
||||
selectionAnchorLine = selectionAnchor.line,
|
||||
selectionAnchorCharacter = selectionAnchor.character;
|
||||
let active = selectionActive,
|
||||
changed = false;
|
||||
|
||||
const isEmptyOrForwardFacing = selectionAnchorLine < selectionActiveLine
|
||||
|| (selectionAnchorLine === selectionActiveLine
|
||||
&& selectionAnchorCharacter <= selectionActiveCharacter);
|
||||
|
||||
if (isEmptyOrForwardFacing) {
|
||||
// Selection is empty or forward-facing: extend it if possible.
|
||||
if (document === undefined) {
|
||||
document = Context.current.document;
|
||||
}
|
||||
|
||||
const lineLength = document.lineAt(selectionActiveLine).text.length;
|
||||
|
||||
if (selectionActiveCharacter === lineLength) {
|
||||
// Character is at the end of the line.
|
||||
if (selectionActiveLine + 1 < document.lineCount) {
|
||||
// This is not the last line: we can extend the selection.
|
||||
active = new vscode.Position(selectionActiveLine + 1, 0);
|
||||
changed = true;
|
||||
} else {
|
||||
// This is the last line: we cannot do anything.
|
||||
}
|
||||
} else {
|
||||
// Character is not at the end of the line: we can extend the selection.
|
||||
active = new vscode.Position(selectionActiveLine, selectionActiveCharacter + 1);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
caretModeSelections.push(changed ? new vscode.Selection(selectionAnchor, active) : selection);
|
||||
}
|
||||
|
||||
return caretModeSelections;
|
||||
}
|
||||
export const fromCharacterMode = selectionsFromCharacterMode,
|
||||
toCharacterMode = selectionsToCharacterMode;
|
||||
}
|
||||
|
||||
function sortTopToBottom(a: vscode.Selection, b: vscode.Selection) {
|
||||
|
54
src/api/types.ts
Normal file
54
src/api/types.ts
Normal file
@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Direction of an operation.
|
||||
*/
|
||||
export const enum Direction {
|
||||
/**
|
||||
* Forward direction (`1`).
|
||||
*/
|
||||
Forward = 1,
|
||||
|
||||
/**
|
||||
* Backward direction (`-1`).
|
||||
*/
|
||||
Backward = -1,
|
||||
}
|
||||
|
||||
/**
|
||||
* Behavior of a shift.
|
||||
*/
|
||||
export const enum Shift {
|
||||
/**
|
||||
* Jump to the position.
|
||||
*/
|
||||
Jump,
|
||||
|
||||
/**
|
||||
* Select to the position.
|
||||
*/
|
||||
Select,
|
||||
|
||||
/**
|
||||
* Extend to the position.
|
||||
*/
|
||||
Extend,
|
||||
}
|
||||
|
||||
/**
|
||||
* Selection behavior of an operation.
|
||||
*/
|
||||
export const enum SelectionBehavior {
|
||||
/**
|
||||
* VS Code-like caret selections.
|
||||
*/
|
||||
Caret = 1,
|
||||
/**
|
||||
* Kakoune-like character selections.
|
||||
*/
|
||||
Character = 2,
|
||||
}
|
||||
|
||||
export const Forward = Direction.Forward,
|
||||
Backward = Direction.Backward,
|
||||
Jump = Shift.Jump,
|
||||
Select = Shift.Select,
|
||||
Extend = Shift.Extend;
|
194
test/suite/api.test.ts
generated
194
test/suite/api.test.ts
generated
@ -89,6 +89,103 @@ suite("API tests", function () {
|
||||
// No expected end document.
|
||||
});
|
||||
|
||||
test("function selectionsToCharacterMode", async function () {
|
||||
const editorState = extension.editors.getState(editor)!,
|
||||
context = new Context(editorState, cancellationToken),
|
||||
before = ExpectedDocument.parseIndented(14, String.raw`
|
||||
a
|
||||
b
|
||||
`);
|
||||
|
||||
await before.apply(editor);
|
||||
|
||||
await context.runAsync(async () => {
|
||||
// One-character selection becomes empty.
|
||||
expect(Selections.toCharacterMode([Selections.fromAnchorActive(0, 0, 0, 1)]), "to satisfy", [
|
||||
expect.it("to be empty at coords", 0, 0),
|
||||
]);
|
||||
|
||||
// One-character selection becomes empty (at line break).
|
||||
expect(Selections.toCharacterMode([Selections.fromAnchorActive(0, 1, 1, 0)]), "to satisfy", [
|
||||
expect.it("to be empty at coords", 0, 1),
|
||||
]);
|
||||
|
||||
// Forward-facing selection becomes shorter.
|
||||
expect(Selections.toCharacterMode([Selections.fromAnchorActive(0, 0, 1, 1)]), "to satisfy", [
|
||||
expect.it("to have anchor at coords", 0, 0).and("to have cursor at coords", 1, 0),
|
||||
]);
|
||||
|
||||
// One-character selection becomes empty (reversed).
|
||||
expect(Selections.toCharacterMode([Selections.fromAnchorActive(0, 1, 0, 0)]), "to satisfy", [
|
||||
expect.it("to be empty at coords", 0, 0),
|
||||
]);
|
||||
|
||||
// One-character selection becomes empty (reversed, at line break).
|
||||
expect(Selections.toCharacterMode([Selections.fromAnchorActive(1, 0, 0, 1)]), "to satisfy", [
|
||||
expect.it("to be empty at coords", 0, 1),
|
||||
]);
|
||||
|
||||
// Reversed selection stays as-is.
|
||||
expect(Selections.toCharacterMode([Selections.fromAnchorActive(1, 1, 0, 0)]), "to satisfy", [
|
||||
expect.it("to have anchor at coords", 1, 1).and("to have cursor at coords", 0, 0),
|
||||
]);
|
||||
|
||||
// Empty selection stays as-is.
|
||||
expect(Selections.toCharacterMode([Selections.empty(1, 1)]), "to satisfy", [
|
||||
expect.it("to be empty at coords", 1, 1),
|
||||
]);
|
||||
});
|
||||
|
||||
// No expected end document.
|
||||
});
|
||||
|
||||
test("function selectionsFromCharacterMode", async function () {
|
||||
const editorState = extension.editors.getState(editor)!,
|
||||
context = new Context(editorState, cancellationToken),
|
||||
before = ExpectedDocument.parseIndented(14, String.raw`
|
||||
`);
|
||||
|
||||
await before.apply(editor);
|
||||
|
||||
await context.runAsync(async () => {
|
||||
expect(Selections.fromCharacterMode([Selections.empty(0, 0)]), "to satisfy", [
|
||||
expect.it("to be empty at coords", 0, 0),
|
||||
]);
|
||||
});
|
||||
|
||||
// No expected end document.
|
||||
});
|
||||
|
||||
test("function selectionsFromCharacterMode#1", async function () {
|
||||
const editorState = extension.editors.getState(editor)!,
|
||||
context = new Context(editorState, cancellationToken),
|
||||
before = ExpectedDocument.parseIndented(14, String.raw`
|
||||
a
|
||||
b
|
||||
|
||||
`);
|
||||
|
||||
await before.apply(editor);
|
||||
|
||||
await context.runAsync(async () => {
|
||||
expect(Selections.fromCharacterMode([Selections.empty(0, 0)]), "to satisfy", [
|
||||
expect.it("to have anchor at coords", 0, 0).and("to have cursor at coords", 0, 1),
|
||||
]);
|
||||
|
||||
// At the end of the line, it selects the line ending:
|
||||
expect(Selections.fromCharacterMode([Selections.empty(0, 1)]), "to satisfy", [
|
||||
expect.it("to have anchor at coords", 0, 1).and("to have cursor at coords", 1, 0),
|
||||
]);
|
||||
|
||||
// But it does nothing at the end of the document:
|
||||
expect(Selections.fromCharacterMode([Selections.empty(2, 0)]), "to satisfy", [
|
||||
expect.it("to be empty at coords", 2, 0),
|
||||
]);
|
||||
});
|
||||
|
||||
// No expected end document.
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
suite("./src/api/functional.ts", function () {
|
||||
@ -1308,102 +1405,5 @@ suite("API tests", function () {
|
||||
// No expected end document.
|
||||
});
|
||||
|
||||
test("function toCharacterMode", async function () {
|
||||
const editorState = extension.editors.getState(editor)!,
|
||||
context = new Context(editorState, cancellationToken),
|
||||
before = ExpectedDocument.parseIndented(14, String.raw`
|
||||
a
|
||||
b
|
||||
`);
|
||||
|
||||
await before.apply(editor);
|
||||
|
||||
await context.runAsync(async () => {
|
||||
// One-character selection becomes empty.
|
||||
expect(Selections.toCharacterMode([Selections.fromAnchorActive(0, 0, 0, 1)]), "to satisfy", [
|
||||
expect.it("to be empty at coords", 0, 0),
|
||||
]);
|
||||
|
||||
// One-character selection becomes empty (at line break).
|
||||
expect(Selections.toCharacterMode([Selections.fromAnchorActive(0, 1, 1, 0)]), "to satisfy", [
|
||||
expect.it("to be empty at coords", 0, 1),
|
||||
]);
|
||||
|
||||
// Forward-facing selection becomes shorter.
|
||||
expect(Selections.toCharacterMode([Selections.fromAnchorActive(0, 0, 1, 1)]), "to satisfy", [
|
||||
expect.it("to have anchor at coords", 0, 0).and("to have cursor at coords", 1, 0),
|
||||
]);
|
||||
|
||||
// One-character selection becomes empty (reversed).
|
||||
expect(Selections.toCharacterMode([Selections.fromAnchorActive(0, 1, 0, 0)]), "to satisfy", [
|
||||
expect.it("to be empty at coords", 0, 0),
|
||||
]);
|
||||
|
||||
// One-character selection becomes empty (reversed, at line break).
|
||||
expect(Selections.toCharacterMode([Selections.fromAnchorActive(1, 0, 0, 1)]), "to satisfy", [
|
||||
expect.it("to be empty at coords", 0, 1),
|
||||
]);
|
||||
|
||||
// Reversed selection stays as-is.
|
||||
expect(Selections.toCharacterMode([Selections.fromAnchorActive(1, 1, 0, 0)]), "to satisfy", [
|
||||
expect.it("to have anchor at coords", 1, 1).and("to have cursor at coords", 0, 0),
|
||||
]);
|
||||
|
||||
// Empty selection stays as-is.
|
||||
expect(Selections.toCharacterMode([Selections.empty(1, 1)]), "to satisfy", [
|
||||
expect.it("to be empty at coords", 1, 1),
|
||||
]);
|
||||
});
|
||||
|
||||
// No expected end document.
|
||||
});
|
||||
|
||||
test("function fromCharacterMode", async function () {
|
||||
const editorState = extension.editors.getState(editor)!,
|
||||
context = new Context(editorState, cancellationToken),
|
||||
before = ExpectedDocument.parseIndented(14, String.raw`
|
||||
`);
|
||||
|
||||
await before.apply(editor);
|
||||
|
||||
await context.runAsync(async () => {
|
||||
expect(Selections.fromCharacterMode([Selections.empty(0, 0)]), "to satisfy", [
|
||||
expect.it("to be empty at coords", 0, 0),
|
||||
]);
|
||||
});
|
||||
|
||||
// No expected end document.
|
||||
});
|
||||
|
||||
test("function fromCharacterMode#1", async function () {
|
||||
const editorState = extension.editors.getState(editor)!,
|
||||
context = new Context(editorState, cancellationToken),
|
||||
before = ExpectedDocument.parseIndented(14, String.raw`
|
||||
a
|
||||
b
|
||||
|
||||
`);
|
||||
|
||||
await before.apply(editor);
|
||||
|
||||
await context.runAsync(async () => {
|
||||
expect(Selections.fromCharacterMode([Selections.empty(0, 0)]), "to satisfy", [
|
||||
expect.it("to have anchor at coords", 0, 0).and("to have cursor at coords", 0, 1),
|
||||
]);
|
||||
|
||||
// At the end of the line, it selects the line ending:
|
||||
expect(Selections.fromCharacterMode([Selections.empty(0, 1)]), "to satisfy", [
|
||||
expect.it("to have anchor at coords", 0, 1).and("to have cursor at coords", 1, 0),
|
||||
]);
|
||||
|
||||
// But it does nothing at the end of the document:
|
||||
expect(Selections.fromCharacterMode([Selections.empty(2, 0)]), "to satisfy", [
|
||||
expect.it("to be empty at coords", 2, 0),
|
||||
]);
|
||||
});
|
||||
|
||||
// No expected end document.
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user