Skip to content

Commit

Permalink
api: remove all circular dependencies
Browse files Browse the repository at this point in the history
  • Loading branch information
71 committed Nov 6, 2021
1 parent d17c6cd commit a3213bd
Show file tree
Hide file tree
Showing 25 changed files with 426 additions and 399 deletions.
12 changes: 5 additions & 7 deletions .dependency-cruiser.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)",
},
},
{
Expand All @@ -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)",
Expand Down
2 changes: 1 addition & 1 deletion src/api/clipboard.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as vscode from "vscode";

import { Context } from ".";
import { Context } from "./context";

/**
* Copies the given text to the clipboard.
Expand Down
226 changes: 222 additions & 4 deletions src/api/context.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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;
Expand All @@ -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[];
Expand All @@ -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;
Expand Down Expand Up @@ -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;
}
4 changes: 3 additions & 1 deletion src/api/edit/index.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion src/api/edit/linewise.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as vscode from "vscode";

import { Context, edit } from "..";
import { Context, edit } from "../context";
import { blankCharacters } from "../../utils/charset";

/**
Expand Down
2 changes: 1 addition & 1 deletion src/api/errors.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Context } from ".";
import { Context } from "./context";

export * from "../utils/errors";

Expand Down
2 changes: 1 addition & 1 deletion src/api/history.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as vscode from "vscode";

import { Context } from ".";
import { Context } from "./context";

/**
* Un-does the last action.
Expand Down
56 changes: 1 addition & 55 deletions src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Loading

0 comments on commit a3213bd

Please sign in to comment.