mirror of
https://github.com/Silverquark/dance.git
synced 2024-09-11 10:45:43 +03:00
api: no longer use namespaces
Namespaces are no longer being used, so APIs such as `replace.byIndex` are now `replaceByIndex`. This slightly improves the generated code and the editing experience.
This commit is contained in:
parent
6603df95ea
commit
ffe1db9f31
59
meta.ts
59
meta.ts
@ -13,16 +13,14 @@ const moduleCommentRe =
|
||||
"m");
|
||||
|
||||
const docCommentRe =
|
||||
new RegExp(String.raw`^( *)` // #1: indentation
|
||||
+ String.raw`\/\*\*\n` // start of doc comment
|
||||
+ String.raw`((?:\1 \*(?:\n| .+\n))+?)` // #2: doc comment
|
||||
+ String.raw`\1 \*\/\n` // end of doc comment
|
||||
+ String.raw`\1export (?:async )?function (\w+)` // #3: function name
|
||||
+ String.raw`\((.*|\n[\s\S]+?^\1)\)` // #4: parameters
|
||||
+ String.raw`(?:: )?(.+)[;{]$` // #5: return type (optional)
|
||||
+ "|" // or
|
||||
+ String.raw`^ *export namespace (\w+) {\n` // #6: namespace (alternative)
|
||||
+ String.raw`^( +)`, // #7: namespace indentation
|
||||
new RegExp(String.raw`^( *)` // #1: indentation
|
||||
+ String.raw`\/\*\*\n` // start of doc comment
|
||||
+ String.raw`((?:\1 \*(?:\n| .+\n))+?)` // #2: doc comment
|
||||
+ String.raw`\1 \*\/\n` // end of doc comment
|
||||
+ String.raw`\1export (?:async )?function (\w+)` // #3: function name
|
||||
+ String.raw`(?:<[^>)\n]+>)?` // generic arguments
|
||||
+ String.raw`\((.*|\n[\s\S]+?^\1)\)` // #4: parameters
|
||||
+ String.raw`(?:: )?(.+)[;{]$`, // #5: return type (optional)
|
||||
"gm");
|
||||
|
||||
function countNewLines(text: string) {
|
||||
@ -145,11 +143,8 @@ function parseDocComments(code: string, modulePath: string) {
|
||||
console.log("Parsing doc comments in module", moduleName);
|
||||
}
|
||||
|
||||
const modulePrefix = moduleName === "misc" ? "" : moduleName + ".";
|
||||
|
||||
const functions: Builder.ParsedFunction[] = [],
|
||||
namespaces: string[] = [];
|
||||
let previousIndentation = 0;
|
||||
const modulePrefix = moduleName === "misc" ? "" : moduleName + ".",
|
||||
functions: Builder.ParsedFunction[] = [];
|
||||
|
||||
for (let match = docCommentRe.exec(code); match !== null; match = docCommentRe.exec(code)) {
|
||||
const indentationString = match[1],
|
||||
@ -157,20 +152,10 @@ function parseDocComments(code: string, modulePath: string) {
|
||||
functionName = match[3],
|
||||
parametersString = match[4],
|
||||
returnTypeString = match[5],
|
||||
enteredNamespace = match[6],
|
||||
enteredNamespaceIndentation = match[7],
|
||||
startLine = countNewLines(code.slice(0, match.index)),
|
||||
endLine = startLine + countNewLines(match[0]);
|
||||
|
||||
if (enteredNamespace !== undefined) {
|
||||
namespaces.push(enteredNamespace);
|
||||
previousIndentation = enteredNamespaceIndentation.length;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
const indentation = indentationString.length,
|
||||
namespace = namespaces.length === 0 ? undefined : namespaces.join("."),
|
||||
returnType = returnTypeString.trim(),
|
||||
parameters = parametersString
|
||||
.split(/,(?![^:]+?[}>])/g)
|
||||
@ -203,11 +188,6 @@ function parseDocComments(code: string, modulePath: string) {
|
||||
.map((line) => line.slice(indentation).replace(/^ \* ?/g, ""))
|
||||
.join("\n");
|
||||
|
||||
if (previousIndentation > indentation) {
|
||||
namespaces.pop();
|
||||
previousIndentation = indentation;
|
||||
}
|
||||
|
||||
for (const parameter of parameters) {
|
||||
if (parameter[0].endsWith("?")) {
|
||||
// Optional parameters.
|
||||
@ -230,16 +210,16 @@ function parseDocComments(code: string, modulePath: string) {
|
||||
properties[k] = v?.replace(/\n {2}/g, " ").trim();
|
||||
return "";
|
||||
}),
|
||||
summary = /((?:.+(?:\n|$))+)/.exec(doc)![0].trim().replace(/\.$/, ""),
|
||||
summary = (/((?:.+(?:\n|$))+)/.exec(doc) === null ? console.log(functionName, JSON.stringify(doc)) : 0, /((?:.+(?:\n|$))+)/.exec(doc)![0].trim().replace(/\.$/, "")),
|
||||
examplesStrings = splitDocComment.slice(1),
|
||||
nameWithDot = functionName.replace(/_/g, ".");
|
||||
|
||||
let qualifiedName = modulePrefix;
|
||||
|
||||
if (namespace !== undefined) {
|
||||
qualifiedName += namespace + ".";
|
||||
if ("internal" in properties) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let qualifiedName = modulePrefix;
|
||||
|
||||
if (nameWithDot === moduleName) {
|
||||
qualifiedName = qualifiedName.replace(/\.$/, "");
|
||||
} else {
|
||||
@ -247,7 +227,6 @@ function parseDocComments(code: string, modulePath: string) {
|
||||
}
|
||||
|
||||
functions.push({
|
||||
namespace,
|
||||
name: functionName,
|
||||
nameWithDot,
|
||||
qualifiedName,
|
||||
@ -370,9 +349,8 @@ export class Builder {
|
||||
}
|
||||
}
|
||||
|
||||
export namespace Builder {
|
||||
export declare namespace Builder {
|
||||
export interface ParsedFunction {
|
||||
readonly namespace?: string;
|
||||
readonly name: string;
|
||||
readonly nameWithDot: string;
|
||||
readonly qualifiedName: string;
|
||||
@ -668,6 +646,11 @@ async function main() {
|
||||
console.error("File", fileToCheck, "includes forbidden access to editor.selections.");
|
||||
success = false;
|
||||
}
|
||||
|
||||
if (/^(export )?namespace/m.test(contentToCheck)) {
|
||||
console.error("File", fileToCheck, "includes a non-`declare` namespace.");
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -222,6 +222,12 @@ export class ContextWithoutActiveEditor {
|
||||
* The context of execution of a script.
|
||||
*/
|
||||
export class Context extends ContextWithoutActiveEditor {
|
||||
/**
|
||||
* The base {@link Context} class, which does not require an active
|
||||
* {@link vscode.TextEditor}.
|
||||
*/
|
||||
public static readonly WithoutActiveEditor = ContextWithoutActiveEditor;
|
||||
|
||||
/**
|
||||
* Returns the current execution context, or throws an error if called outside
|
||||
* of an execution context or if the execution context does not have an
|
||||
@ -254,6 +260,31 @@ export class Context extends ContextWithoutActiveEditor {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link Context} or {@link Context.WithoutActiveEditor} depending
|
||||
* on whether there is an active text editor.
|
||||
*/
|
||||
public static create(extension: Extension, command: CommandDescriptor) {
|
||||
const activeEditorState = extension.editors.active,
|
||||
cancellationToken = extension.cancellationToken;
|
||||
|
||||
return activeEditorState === undefined
|
||||
? new Context.WithoutActiveEditor(extension, cancellationToken, command)
|
||||
: new Context(activeEditorState, cancellationToken, command);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link Context} or throws an exception if there is no active text
|
||||
* editor.
|
||||
*/
|
||||
public static createWithActiveTextEditor(extension: Extension, command: CommandDescriptor) {
|
||||
const activeEditorState = extension.editors.active;
|
||||
|
||||
EditorRequiredError.throwUnlessAvailable(activeEditorState);
|
||||
|
||||
return new Context(activeEditorState, extension.cancellationToken, command);
|
||||
}
|
||||
|
||||
private _document: vscode.TextDocument;
|
||||
private _editor: vscode.TextEditor;
|
||||
private _mode: Mode;
|
||||
@ -457,39 +488,8 @@ export class Context extends ContextWithoutActiveEditor {
|
||||
}
|
||||
}
|
||||
|
||||
export namespace Context {
|
||||
/**
|
||||
* The base `Context` class, which does not require an active
|
||||
* `vscode.TextEditor`.
|
||||
*/
|
||||
export const WithoutActiveEditor = ContextWithoutActiveEditor;
|
||||
|
||||
export declare namespace Context {
|
||||
export type WithoutActiveEditor = ContextWithoutActiveEditor;
|
||||
|
||||
/**
|
||||
* Returns a `Context` or `Context.WithoutActiveEditor` depending on whether
|
||||
* there is an active text editor.
|
||||
*/
|
||||
export function create(extension: Extension, command: CommandDescriptor) {
|
||||
const activeEditorState = extension.editors.active,
|
||||
cancellationToken = extension.cancellationToken;
|
||||
|
||||
return activeEditorState === undefined
|
||||
? new Context.WithoutActiveEditor(extension, cancellationToken, command)
|
||||
: new Context(activeEditorState, cancellationToken, command);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a `Context` or throws an exception if there is no active text
|
||||
* editor.
|
||||
*/
|
||||
export function createWithActiveTextEditor(extension: Extension, command: CommandDescriptor) {
|
||||
const activeEditorState = extension.editors.active;
|
||||
|
||||
EditorRequiredError.throwUnlessAvailable(activeEditorState);
|
||||
|
||||
return new Context(activeEditorState, extension.cancellationToken, command);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -3,7 +3,7 @@ import * as vscode from "vscode";
|
||||
import { Context, edit } from "../context";
|
||||
import * as Positions from "../positions";
|
||||
import * as Selections from "../selections";
|
||||
import { TrackedSelection } from "../../utils/tracked-selection";
|
||||
import * as TrackedSelection from "../../utils/tracked-selection";
|
||||
|
||||
const enum Constants {
|
||||
PositionMask = 0b00_11_1,
|
||||
@ -166,16 +166,16 @@ export function insert(
|
||||
f: insert.Callback<insert.Result> | insert.Callback<insert.AsyncResult>,
|
||||
selections?: readonly vscode.Selection[],
|
||||
): Thenable<vscode.Selection[]> {
|
||||
return insert.byIndex(
|
||||
return insertByIndex(
|
||||
flags,
|
||||
(i, selection, document) => f(document.getText(selection), selection, i, document) as any,
|
||||
selections,
|
||||
);
|
||||
}
|
||||
|
||||
export namespace insert {
|
||||
export declare namespace insert {
|
||||
/**
|
||||
* Insertion flags for `insert`.
|
||||
* Insertion flags for {@link insert}.
|
||||
*/
|
||||
export const enum Flags {
|
||||
/**
|
||||
@ -219,358 +219,373 @@ export namespace insert {
|
||||
Extend = 0b10_00_1,
|
||||
}
|
||||
|
||||
export const Replace = Flags.Replace,
|
||||
Start = Flags.Start,
|
||||
End = Flags.End,
|
||||
Active = Flags.Active,
|
||||
Anchor = Flags.Anchor,
|
||||
Keep = Flags.Keep,
|
||||
Select = Flags.Select,
|
||||
Extend = Flags.Extend;
|
||||
|
||||
export function flagsAtEdge(edge?: "active" | "anchor" | "start" | "end") {
|
||||
switch (edge) {
|
||||
case undefined:
|
||||
return Flags.Replace;
|
||||
|
||||
case "active":
|
||||
return Flags.Active;
|
||||
case "anchor":
|
||||
return Flags.Anchor;
|
||||
case "start":
|
||||
return Flags.Start;
|
||||
case "end":
|
||||
return Flags.End;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The result of a callback passed to `insert` or `insert.byIndex`.
|
||||
* The result of a callback passed to {@link insert} or
|
||||
* {@link insertByIndex}.
|
||||
*/
|
||||
export type Result = string | undefined;
|
||||
|
||||
/**
|
||||
* The result of an async callback passed to `insert` or `insert.byIndex`.
|
||||
* The result of an async callback passed to {@link insert} or
|
||||
* {@link insertByIndex}.
|
||||
*/
|
||||
export type AsyncResult = Thenable<Result>;
|
||||
|
||||
/**
|
||||
* A callback passed to `insert`.
|
||||
* A callback passed to {@link insert}.
|
||||
*/
|
||||
export interface Callback<T> {
|
||||
(text: string, selection: vscode.Selection, index: number, document: vscode.TextDocument): T;
|
||||
}
|
||||
|
||||
/**
|
||||
* A callback passed to `insert.byIndex`.
|
||||
*/
|
||||
export interface ByIndexCallback<T> {
|
||||
(index: number, selection: vscode.Selection, document: vscode.TextDocument): T;
|
||||
export const Replace: Flags.Replace,
|
||||
Start: Flags.Start,
|
||||
End: Flags.End,
|
||||
Active: Flags.Active,
|
||||
Anchor: Flags.Anchor,
|
||||
Keep: Flags.Keep,
|
||||
Select: Flags.Select,
|
||||
Extend: Flags.Extend;
|
||||
}
|
||||
|
||||
for (const [k, v] of Object.entries({
|
||||
Replace: insert.Flags.Replace,
|
||||
Start: insert.Flags.Start,
|
||||
End: insert.Flags.End,
|
||||
Active: insert.Flags.Active,
|
||||
Anchor: insert.Flags.Anchor,
|
||||
Keep: insert.Flags.Keep,
|
||||
Select: insert.Flags.Select,
|
||||
Extend: insert.Flags.Extend,
|
||||
})) {
|
||||
Object.defineProperty(insert, k, { value: v });
|
||||
}
|
||||
|
||||
export function insertFlagsAtEdge(edge?: "active" | "anchor" | "start" | "end") {
|
||||
switch (edge) {
|
||||
case undefined:
|
||||
return insert.Flags.Replace;
|
||||
|
||||
case "active":
|
||||
return insert.Flags.Active;
|
||||
case "anchor":
|
||||
return insert.Flags.Anchor;
|
||||
case "start":
|
||||
return insert.Flags.Start;
|
||||
case "end":
|
||||
return insert.Flags.End;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts text next to the given selections according to the given function.
|
||||
*
|
||||
* @param f A mapping function called for each selection; given the index,
|
||||
* range and editor of each selection, it should return the new text content
|
||||
* of the selection, or `undefined` if it is to be removed. Also works for
|
||||
* `async` (i.e. `Promise`-returning) functions, in which case **all**
|
||||
* results must be promises.
|
||||
* @param selections If `undefined`, the selections of the active editor will
|
||||
* be used. Otherwise, must be a `vscode.Selection` array which will be
|
||||
* mapped in the active editor.
|
||||
*
|
||||
* ### Example
|
||||
* ```js
|
||||
* Selections.set(await insertByIndex(insert.Start, (i) => `${i + 1}`));
|
||||
* ```
|
||||
*
|
||||
* Before:
|
||||
* ```
|
||||
* a b c
|
||||
* ^ 0
|
||||
* ^ 1
|
||||
* ^ 2
|
||||
* ```
|
||||
*
|
||||
* After:
|
||||
* ```
|
||||
* 1a 2b 3c
|
||||
* ^ 0
|
||||
* ^ 1
|
||||
* ^ 2
|
||||
* ```
|
||||
*
|
||||
* ### Example
|
||||
* ```js
|
||||
* Selections.set(await insertByIndex(insert.Start | insert.Select, (i) => `${i + 1}`));
|
||||
* ```
|
||||
*
|
||||
* Before:
|
||||
* ```
|
||||
* a b c
|
||||
* ^ 0
|
||||
* ^ 1
|
||||
* ^ 2
|
||||
* ```
|
||||
*
|
||||
* After:
|
||||
* ```
|
||||
* 1a 2b 3c
|
||||
* ^ 0
|
||||
* ^ 1
|
||||
* ^ 2
|
||||
* ```
|
||||
*
|
||||
* ### Example
|
||||
* ```js
|
||||
* Selections.set(await insertByIndex(insert.Start | insert.Extend, (i) => `${i + 1}`));
|
||||
* ```
|
||||
*
|
||||
* Before:
|
||||
* ```
|
||||
* a b c
|
||||
* ^ 0
|
||||
* ^ 1
|
||||
* ^ 2
|
||||
* ```
|
||||
*
|
||||
* After:
|
||||
* ```
|
||||
* 1a 2b 3c
|
||||
* ^^ 0
|
||||
* ^^ 1
|
||||
* ^^ 2
|
||||
* ```
|
||||
*
|
||||
* ### Example
|
||||
* ```js
|
||||
* Selections.set(await insertByIndex(insert.End, (i) => `${i + 1}`));
|
||||
* ```
|
||||
*
|
||||
* Before:
|
||||
* ```
|
||||
* a b c
|
||||
* ^ 0
|
||||
* ^ 1
|
||||
* ^ 2
|
||||
* ```
|
||||
*
|
||||
* After:
|
||||
* ```
|
||||
* a1 b2 c3
|
||||
* ^ 0
|
||||
* ^ 1
|
||||
* ^ 2
|
||||
* ```
|
||||
*
|
||||
* ### Example
|
||||
* ```js
|
||||
* Selections.set(await insertByIndex(insert.End | insert.Select, (i) => `${i + 1}`));
|
||||
* ```
|
||||
*
|
||||
* Before:
|
||||
* ```
|
||||
* a b c
|
||||
* ^ 0
|
||||
* ^ 1
|
||||
* ^ 2
|
||||
* ```
|
||||
*
|
||||
* After:
|
||||
* ```
|
||||
* a1 b2 c3
|
||||
* ^ 0
|
||||
* ^ 1
|
||||
* ^ 2
|
||||
* ```
|
||||
*
|
||||
* ### Example
|
||||
* ```js
|
||||
* Selections.set(await insertByIndex(insert.End | insert.Extend, (i) => `${i + 1}`));
|
||||
* ```
|
||||
*
|
||||
* Before:
|
||||
* ```
|
||||
* a b c
|
||||
* ^ 0
|
||||
* ^ 1
|
||||
* ^ 2
|
||||
* ```
|
||||
*
|
||||
* After:
|
||||
* ```
|
||||
* a1 b2 c3
|
||||
* ^^ 0
|
||||
* ^^ 1
|
||||
* ^^ 2
|
||||
* ```
|
||||
*/
|
||||
export function insertByIndex(
|
||||
flags: insert.Flags,
|
||||
f: insertByIndex.Callback<insert.Result> | insertByIndex.Callback<insert.AsyncResult>,
|
||||
selections: readonly vscode.Selection[] = Context.current.selections,
|
||||
): Thenable<vscode.Selection[]> {
|
||||
if (selections.length === 0) {
|
||||
return Context.wrap(Promise.resolve([]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts text next to the given selections according to the given function.
|
||||
*
|
||||
* @param f A mapping function called for each selection; given the index,
|
||||
* range and editor of each selection, it should return the new text content
|
||||
* of the selection, or `undefined` if it is to be removed. Also works for
|
||||
* `async` (i.e. `Promise`-returning) functions, in which case **all**
|
||||
* results must be promises.
|
||||
* @param selections If `undefined`, the selections of the active editor will
|
||||
* be used. Otherwise, must be a `vscode.Selection` array which will be
|
||||
* mapped in the active editor.
|
||||
*
|
||||
* ### Example
|
||||
* ```js
|
||||
* Selections.set(await insert.byIndex(insert.Start, (i) => `${i + 1}`));
|
||||
* ```
|
||||
*
|
||||
* Before:
|
||||
* ```
|
||||
* a b c
|
||||
* ^ 0
|
||||
* ^ 1
|
||||
* ^ 2
|
||||
* ```
|
||||
*
|
||||
* After:
|
||||
* ```
|
||||
* 1a 2b 3c
|
||||
* ^ 0
|
||||
* ^ 1
|
||||
* ^ 2
|
||||
* ```
|
||||
*
|
||||
* ### Example
|
||||
* ```js
|
||||
* Selections.set(await insert.byIndex(insert.Start | insert.Select, (i) => `${i + 1}`));
|
||||
* ```
|
||||
*
|
||||
* Before:
|
||||
* ```
|
||||
* a b c
|
||||
* ^ 0
|
||||
* ^ 1
|
||||
* ^ 2
|
||||
* ```
|
||||
*
|
||||
* After:
|
||||
* ```
|
||||
* 1a 2b 3c
|
||||
* ^ 0
|
||||
* ^ 1
|
||||
* ^ 2
|
||||
* ```
|
||||
*
|
||||
* ### Example
|
||||
* ```js
|
||||
* Selections.set(await insert.byIndex(insert.Start | insert.Extend, (i) => `${i + 1}`));
|
||||
* ```
|
||||
*
|
||||
* Before:
|
||||
* ```
|
||||
* a b c
|
||||
* ^ 0
|
||||
* ^ 1
|
||||
* ^ 2
|
||||
* ```
|
||||
*
|
||||
* After:
|
||||
* ```
|
||||
* 1a 2b 3c
|
||||
* ^^ 0
|
||||
* ^^ 1
|
||||
* ^^ 2
|
||||
* ```
|
||||
*
|
||||
* ### Example
|
||||
* ```js
|
||||
* Selections.set(await insert.byIndex(insert.End, (i) => `${i + 1}`));
|
||||
* ```
|
||||
*
|
||||
* Before:
|
||||
* ```
|
||||
* a b c
|
||||
* ^ 0
|
||||
* ^ 1
|
||||
* ^ 2
|
||||
* ```
|
||||
*
|
||||
* After:
|
||||
* ```
|
||||
* a1 b2 c3
|
||||
* ^ 0
|
||||
* ^ 1
|
||||
* ^ 2
|
||||
* ```
|
||||
*
|
||||
* ### Example
|
||||
* ```js
|
||||
* Selections.set(await insert.byIndex(insert.End | insert.Select, (i) => `${i + 1}`));
|
||||
* ```
|
||||
*
|
||||
* Before:
|
||||
* ```
|
||||
* a b c
|
||||
* ^ 0
|
||||
* ^ 1
|
||||
* ^ 2
|
||||
* ```
|
||||
*
|
||||
* After:
|
||||
* ```
|
||||
* a1 b2 c3
|
||||
* ^ 0
|
||||
* ^ 1
|
||||
* ^ 2
|
||||
* ```
|
||||
*
|
||||
* ### Example
|
||||
* ```js
|
||||
* Selections.set(await insert.byIndex(insert.End | insert.Extend, (i) => `${i + 1}`));
|
||||
* ```
|
||||
*
|
||||
* Before:
|
||||
* ```
|
||||
* a b c
|
||||
* ^ 0
|
||||
* ^ 1
|
||||
* ^ 2
|
||||
* ```
|
||||
*
|
||||
* After:
|
||||
* ```
|
||||
* a1 b2 c3
|
||||
* ^^ 0
|
||||
* ^^ 1
|
||||
* ^^ 2
|
||||
* ```
|
||||
*/
|
||||
export function byIndex(
|
||||
flags: Flags,
|
||||
f: ByIndexCallback<Result> | ByIndexCallback<AsyncResult>,
|
||||
selections: readonly vscode.Selection[] = Context.current.selections,
|
||||
): Thenable<vscode.Selection[]> {
|
||||
if (selections.length === 0) {
|
||||
return Context.wrap(Promise.resolve([]));
|
||||
}
|
||||
const document = Context.current.document,
|
||||
firstResult = f(0, selections[0], document);
|
||||
|
||||
const document = Context.current.document,
|
||||
firstResult = f(0, selections[0], document);
|
||||
|
||||
if (typeof firstResult === "object") {
|
||||
// `f` returns promises.
|
||||
const promises = [firstResult];
|
||||
|
||||
for (let i = 1, len = selections.length; i < len; i++) {
|
||||
promises.push(f(i, selections[i], document) as AsyncResult);
|
||||
}
|
||||
|
||||
return Context.wrap(
|
||||
Promise
|
||||
.all(promises)
|
||||
.then((results) => mapResults(flags, document, selections, results)),
|
||||
);
|
||||
}
|
||||
|
||||
// `f` returns regular values.
|
||||
const allResults: Result[] = [firstResult];
|
||||
if (typeof firstResult === "object") {
|
||||
// `f` returns promises.
|
||||
const promises = [firstResult];
|
||||
|
||||
for (let i = 1, len = selections.length; i < len; i++) {
|
||||
allResults.push(f(i, selections[i], document) as Result);
|
||||
promises.push(f(i, selections[i], document) as insert.AsyncResult);
|
||||
}
|
||||
|
||||
return mapResults(flags, document, selections, allResults);
|
||||
return Context.wrap(
|
||||
Promise
|
||||
.all(promises)
|
||||
.then((results) => mapResults(flags, document, selections, results)),
|
||||
);
|
||||
}
|
||||
|
||||
export namespace byIndex {
|
||||
/**
|
||||
* Same as `insert.byIndex`, but also inserts strings that end with a
|
||||
* newline character on the next or previous line.
|
||||
*/
|
||||
export async function withFullLines(
|
||||
flags: Flags,
|
||||
f: ByIndexCallback<Result> | ByIndexCallback<AsyncResult>,
|
||||
selections: readonly vscode.Selection[] = Context.current.selections,
|
||||
) {
|
||||
const document = Context.current.document,
|
||||
allResults = await Promise.all(selections.map((sel, i) => f(i, sel, document)));
|
||||
// `f` returns regular values.
|
||||
const allResults: insert.Result[] = [firstResult];
|
||||
|
||||
// Separate full-line results from all results.
|
||||
const results: Result[] = [],
|
||||
resultsSelections: vscode.Selection[] = [],
|
||||
fullLineResults: Result[] = [],
|
||||
fullLineResultsSelections: vscode.Selection[] = [],
|
||||
isFullLines: boolean[] = [];
|
||||
for (let i = 1, len = selections.length; i < len; i++) {
|
||||
allResults.push(f(i, selections[i], document) as insert.Result);
|
||||
}
|
||||
|
||||
for (let i = 0; i < allResults.length; i++) {
|
||||
const result = allResults[i];
|
||||
return mapResults(flags, document, selections, allResults);
|
||||
}
|
||||
|
||||
if (result === undefined) {
|
||||
continue;
|
||||
}
|
||||
export declare namespace insertByIndex {
|
||||
/**
|
||||
* A callback passed to {@link insertByIndex}.
|
||||
*/
|
||||
export interface Callback<T> {
|
||||
(index: number, selection: vscode.Selection, document: vscode.TextDocument): T;
|
||||
}
|
||||
}
|
||||
|
||||
if (result.endsWith("\n")) {
|
||||
fullLineResults.push(result);
|
||||
fullLineResultsSelections.push(selections[i]);
|
||||
isFullLines.push(true);
|
||||
} else {
|
||||
results.push(result);
|
||||
resultsSelections.push(selections[i]);
|
||||
isFullLines.push(false);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Same as {@link insertByIndex}, but also inserts strings that end with a
|
||||
* newline character on the next or previous line.
|
||||
*/
|
||||
export async function insertByIndexWithFullLines(
|
||||
flags: insert.Flags,
|
||||
f: insertByIndex.Callback<insert.Result> | insertByIndex.Callback<insert.AsyncResult>,
|
||||
selections: readonly vscode.Selection[] = Context.current.selections,
|
||||
) {
|
||||
const document = Context.current.document,
|
||||
allResults = await Promise.all(selections.map((sel, i) => f(i, sel, document)));
|
||||
|
||||
if (fullLineResults.length === 0) {
|
||||
return await mapResults(flags, document, resultsSelections, results);
|
||||
}
|
||||
// Separate full-line results from all results.
|
||||
const results: insert.Result[] = [],
|
||||
resultsSelections: vscode.Selection[] = [],
|
||||
fullLineResults: insert.Result[] = [],
|
||||
fullLineResultsSelections: vscode.Selection[] = [],
|
||||
isFullLines: boolean[] = [];
|
||||
|
||||
let savedSelections = new TrackedSelection.Set(
|
||||
TrackedSelection.fromArray(fullLineResultsSelections, document),
|
||||
document,
|
||||
);
|
||||
for (let i = 0; i < allResults.length; i++) {
|
||||
const result = allResults[i];
|
||||
|
||||
// Insert non-full lines.
|
||||
const normalSelections = await mapResults(flags, document, resultsSelections, results);
|
||||
if (result === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Insert full lines.
|
||||
const fullLineSelections = savedSelections.restore();
|
||||
if (result.endsWith("\n")) {
|
||||
fullLineResults.push(result);
|
||||
fullLineResultsSelections.push(selections[i]);
|
||||
isFullLines.push(true);
|
||||
} else {
|
||||
results.push(result);
|
||||
resultsSelections.push(selections[i]);
|
||||
isFullLines.push(false);
|
||||
}
|
||||
}
|
||||
|
||||
savedSelections.dispose();
|
||||
if (fullLineResults.length === 0) {
|
||||
return await mapResults(flags, document, resultsSelections, results);
|
||||
}
|
||||
|
||||
const nextFullLineSelections: vscode.Selection[] = [],
|
||||
insertionPositions: vscode.Position[] = [];
|
||||
let savedSelections = new TrackedSelection.Set(
|
||||
TrackedSelection.fromArray(fullLineResultsSelections, document),
|
||||
document,
|
||||
);
|
||||
|
||||
if ((flags & Constants.PositionMask) === Flags.Start) {
|
||||
for (const selection of fullLineSelections) {
|
||||
const insertionPosition = Positions.lineStart(selection.start.line);
|
||||
// Insert non-full lines.
|
||||
const normalSelections = await mapResults(flags, document, resultsSelections, results);
|
||||
|
||||
insertionPositions.push(insertionPosition);
|
||||
// Insert full lines.
|
||||
const fullLineSelections = savedSelections.restore();
|
||||
|
||||
if ((flags & Constants.BehaviorMask) === Flags.Extend) {
|
||||
nextFullLineSelections.push(
|
||||
Selections.fromStartEnd(
|
||||
insertionPosition, selection.end, selection.isReversed, document),
|
||||
);
|
||||
} else if ((flags & Constants.BehaviorMask) === Flags.Select) {
|
||||
nextFullLineSelections.push(Selections.empty(insertionPosition));
|
||||
} else {
|
||||
// Keep selection as is.
|
||||
nextFullLineSelections.push(selection);
|
||||
}
|
||||
}
|
||||
savedSelections.dispose();
|
||||
|
||||
const nextFullLineSelections: vscode.Selection[] = [],
|
||||
insertionPositions: vscode.Position[] = [];
|
||||
|
||||
if ((flags & Constants.PositionMask) === insert.Flags.Start) {
|
||||
for (const selection of fullLineSelections) {
|
||||
const insertionPosition = Positions.lineStart(selection.start.line);
|
||||
|
||||
insertionPositions.push(insertionPosition);
|
||||
|
||||
if ((flags & Constants.BehaviorMask) === insert.Flags.Extend) {
|
||||
nextFullLineSelections.push(
|
||||
Selections.fromStartEnd(
|
||||
insertionPosition, selection.end, selection.isReversed, document),
|
||||
);
|
||||
} else if ((flags & Constants.BehaviorMask) === insert.Flags.Select) {
|
||||
nextFullLineSelections.push(Selections.empty(insertionPosition));
|
||||
} else {
|
||||
for (const selection of fullLineSelections) {
|
||||
const insertionPosition = Positions.lineStart(Selections.endLine(selection) + 1);
|
||||
|
||||
insertionPositions.push(insertionPosition);
|
||||
|
||||
if ((flags & Constants.BehaviorMask) === Flags.Extend) {
|
||||
nextFullLineSelections.push(
|
||||
Selections.fromStartEnd(
|
||||
selection.start, insertionPosition, selection.isReversed, document),
|
||||
);
|
||||
} else if ((flags & Constants.BehaviorMask) === Flags.Select) {
|
||||
nextFullLineSelections.push(Selections.empty(insertionPosition));
|
||||
} else {
|
||||
// Keep selection as is.
|
||||
nextFullLineSelections.push(selection);
|
||||
}
|
||||
}
|
||||
// Keep selection as is.
|
||||
nextFullLineSelections.push(selection);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (const selection of fullLineSelections) {
|
||||
const insertionPosition = Positions.lineStart(Selections.endLine(selection) + 1);
|
||||
|
||||
savedSelections = new TrackedSelection.Set(
|
||||
TrackedSelection.fromArray(nextFullLineSelections, document),
|
||||
document,
|
||||
(flags & Constants.BehaviorMask) === Flags.Keep
|
||||
? TrackedSelection.Flags.Strict
|
||||
: TrackedSelection.Flags.Inclusive,
|
||||
);
|
||||
insertionPositions.push(insertionPosition);
|
||||
|
||||
await edit((editBuilder) => {
|
||||
for (let i = 0; i < insertionPositions.length; i++) {
|
||||
editBuilder.replace(insertionPositions[i], fullLineResults[i]!);
|
||||
}
|
||||
});
|
||||
|
||||
const finalFullLineSelections = savedSelections.restore();
|
||||
|
||||
savedSelections.dispose();
|
||||
|
||||
// Merge back selections.
|
||||
const allSelections: vscode.Selection[] = [];
|
||||
|
||||
for (let i = 0, normalIdx = 0, fullLineIdx = 0; i < isFullLines.length; i++) {
|
||||
if (isFullLines[i]) {
|
||||
allSelections.push(finalFullLineSelections[fullLineIdx++]);
|
||||
} else {
|
||||
allSelections.push(normalSelections[normalIdx++]);
|
||||
}
|
||||
if ((flags & Constants.BehaviorMask) === insert.Flags.Extend) {
|
||||
nextFullLineSelections.push(
|
||||
Selections.fromStartEnd(
|
||||
selection.start, insertionPosition, selection.isReversed, document),
|
||||
);
|
||||
} else if ((flags & Constants.BehaviorMask) === insert.Flags.Select) {
|
||||
nextFullLineSelections.push(Selections.empty(insertionPosition));
|
||||
} else {
|
||||
// Keep selection as is.
|
||||
nextFullLineSelections.push(selection);
|
||||
}
|
||||
|
||||
return allSelections;
|
||||
}
|
||||
}
|
||||
|
||||
savedSelections = new TrackedSelection.Set(
|
||||
TrackedSelection.fromArray(nextFullLineSelections, document),
|
||||
document,
|
||||
(flags & Constants.BehaviorMask) === insert.Flags.Keep
|
||||
? TrackedSelection.Flags.Strict
|
||||
: TrackedSelection.Flags.Inclusive,
|
||||
);
|
||||
|
||||
await edit((editBuilder) => {
|
||||
for (let i = 0; i < insertionPositions.length; i++) {
|
||||
editBuilder.replace(insertionPositions[i], fullLineResults[i]!);
|
||||
}
|
||||
});
|
||||
|
||||
const finalFullLineSelections = savedSelections.restore();
|
||||
|
||||
savedSelections.dispose();
|
||||
|
||||
// Merge back selections.
|
||||
const allSelections: vscode.Selection[] = [];
|
||||
|
||||
for (let i = 0, normalIdx = 0, fullLineIdx = 0; i < isFullLines.length; i++) {
|
||||
if (isFullLines[i]) {
|
||||
allSelections.push(finalFullLineSelections[fullLineIdx++]);
|
||||
} else {
|
||||
allSelections.push(normalSelections[normalIdx++]);
|
||||
}
|
||||
}
|
||||
|
||||
return allSelections;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -613,70 +628,74 @@ export function replace(
|
||||
return insert(insert.Flags.Replace, f, selections);
|
||||
}
|
||||
|
||||
export namespace replace {
|
||||
export declare namespace replace {
|
||||
/**
|
||||
* The result of a callback passed to `replace` or `replace.byIndex`.
|
||||
* The result of a callback passed to {@link replace} or
|
||||
* {@link replaceByIndex}.
|
||||
*/
|
||||
export type Result = string | undefined;
|
||||
|
||||
/**
|
||||
* The result of an async callback passed to `replace` or `replace.byIndex`.
|
||||
* The result of an async callback passed to {@link replace} or
|
||||
* {@link replaceByIndex}.
|
||||
*/
|
||||
export type AsyncResult = Thenable<Result>;
|
||||
|
||||
/**
|
||||
* A callback passed to `replace`.
|
||||
* A callback passed to {@link replace}.
|
||||
*/
|
||||
export interface Callback<T> {
|
||||
(text: string, selection: vscode.Selection, index: number, document: vscode.TextDocument): T;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the given selections according to the given function.
|
||||
*
|
||||
* @param f A mapping function called for each selection; given the index,
|
||||
* range and editor of each selection, it should return the new text content
|
||||
* of the selection, or `undefined` if it is to be removed. Also works for
|
||||
* `async` (i.e. `Promise`-returning) functions, in which case **all**
|
||||
* results must be promises.
|
||||
* @param selections If `undefined`, the selections of the active editor will
|
||||
* be used. Otherwise, must be a `vscode.Selection` array which will be
|
||||
* mapped in the active editor.
|
||||
*
|
||||
* ### Example
|
||||
* ```js
|
||||
* await replaceByIndex((i) => `${i + 1}`);
|
||||
* ```
|
||||
*
|
||||
* Before:
|
||||
* ```
|
||||
* a b c
|
||||
* ^ 0
|
||||
* ^ 1
|
||||
* ^ 2
|
||||
* ```
|
||||
*
|
||||
* After:
|
||||
* ```
|
||||
* 1 2 3
|
||||
* ^ 0
|
||||
* ^ 1
|
||||
* ^ 2
|
||||
* ```
|
||||
*/
|
||||
export function replaceByIndex(
|
||||
f: replaceByIndex.Callback<replace.Result> | replaceByIndex.Callback<replace.AsyncResult>,
|
||||
selections?: readonly vscode.Selection[],
|
||||
): Thenable<vscode.Selection[]> {
|
||||
return insertByIndex(insert.Flags.Replace, f, selections);
|
||||
}
|
||||
|
||||
export declare namespace replaceByIndex {
|
||||
/**
|
||||
* A callback passed to `replace.byIndex`.
|
||||
* A callback passed to {@link replaceByIndex}.
|
||||
*/
|
||||
export interface ByIndexCallback<T> {
|
||||
export interface Callback<T> {
|
||||
(index: number, selection: vscode.Selection, document: vscode.TextDocument): T;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the given selections according to the given function.
|
||||
*
|
||||
* @param f A mapping function called for each selection; given the index,
|
||||
* range and editor of each selection, it should return the new text content
|
||||
* of the selection, or `undefined` if it is to be removed. Also works for
|
||||
* `async` (i.e. `Promise`-returning) functions, in which case **all**
|
||||
* results must be promises.
|
||||
* @param selections If `undefined`, the selections of the active editor will
|
||||
* be used. Otherwise, must be a `vscode.Selection` array which will be
|
||||
* mapped in the active editor.
|
||||
*
|
||||
* ### Example
|
||||
* ```js
|
||||
* await replace.byIndex((i) => `${i + 1}`);
|
||||
* ```
|
||||
*
|
||||
* Before:
|
||||
* ```
|
||||
* a b c
|
||||
* ^ 0
|
||||
* ^ 1
|
||||
* ^ 2
|
||||
* ```
|
||||
*
|
||||
* After:
|
||||
* ```
|
||||
* 1 2 3
|
||||
* ^ 0
|
||||
* ^ 1
|
||||
* ^ 2
|
||||
* ```
|
||||
*/
|
||||
export function byIndex(
|
||||
f: ByIndexCallback<Result> | ByIndexCallback<AsyncResult>,
|
||||
selections?: readonly vscode.Selection[],
|
||||
): Thenable<vscode.Selection[]> {
|
||||
return insert.byIndex(insert.Flags.Replace, f, selections);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -708,84 +727,80 @@ export namespace replace {
|
||||
* ```
|
||||
*/
|
||||
export function rotate(by: number, selections?: readonly vscode.Selection[]) {
|
||||
return rotate
|
||||
.contentsOnly(by, selections)
|
||||
.then((selections) => rotate.selectionsOnly(by, selections));
|
||||
return rotateContents(by, selections).then((selections) => rotateSelections(by, selections));
|
||||
}
|
||||
|
||||
export namespace rotate {
|
||||
/**
|
||||
* Rotates the contents of the given selections by the given offset.
|
||||
*
|
||||
* @see rotate
|
||||
*
|
||||
* ### Example
|
||||
* ```js
|
||||
* await rotate.contentsOnly(1);
|
||||
* ```
|
||||
*
|
||||
* Before:
|
||||
* ```
|
||||
* a b c
|
||||
* ^ 0
|
||||
* ^ 1
|
||||
* ^ 2
|
||||
* ```
|
||||
*
|
||||
* After:
|
||||
* ```
|
||||
* b c a
|
||||
* ^ 0
|
||||
* ^ 1
|
||||
* ^ 2
|
||||
* ```
|
||||
*/
|
||||
export function contentsOnly(
|
||||
by: number,
|
||||
selections: readonly vscode.Selection[] = Context.current.selections,
|
||||
) {
|
||||
const len = selections.length;
|
||||
/**
|
||||
* Rotates the contents of the given selections by the given offset.
|
||||
*
|
||||
* @see {@link rotate}
|
||||
*
|
||||
* ### Example
|
||||
* ```js
|
||||
* await rotateContents(1);
|
||||
* ```
|
||||
*
|
||||
* Before:
|
||||
* ```
|
||||
* a b c
|
||||
* ^ 0
|
||||
* ^ 1
|
||||
* ^ 2
|
||||
* ```
|
||||
*
|
||||
* After:
|
||||
* ```
|
||||
* b c a
|
||||
* ^ 0
|
||||
* ^ 1
|
||||
* ^ 2
|
||||
* ```
|
||||
*/
|
||||
export function rotateContents(
|
||||
by: number,
|
||||
selections: readonly vscode.Selection[] = Context.current.selections,
|
||||
) {
|
||||
const len = selections.length;
|
||||
|
||||
// Handle negative values for `by`:
|
||||
by = (by % len) + len;
|
||||
// Handle negative values for `by`:
|
||||
by = (by % len) + len;
|
||||
|
||||
if (by === len) {
|
||||
return Context.wrap(Promise.resolve(selections.slice()));
|
||||
}
|
||||
|
||||
return replace.byIndex(
|
||||
(i, _, document) => document.getText(selections[(i + by) % len]),
|
||||
selections,
|
||||
);
|
||||
if (by === len) {
|
||||
return Context.wrap(Promise.resolve(selections.slice()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Rotates the given selections (but not their contents) by the given offset.
|
||||
*
|
||||
* @see rotate
|
||||
*
|
||||
* ### Example
|
||||
* ```js
|
||||
* rotate.selectionsOnly(1);
|
||||
* ```
|
||||
*
|
||||
* Before:
|
||||
* ```
|
||||
* a b c
|
||||
* ^ 0
|
||||
* ^ 1
|
||||
* ^ 2
|
||||
* ```
|
||||
*
|
||||
* After:
|
||||
* ```
|
||||
* a b c
|
||||
* ^ 1
|
||||
* ^ 2
|
||||
* ^ 0
|
||||
* ```
|
||||
*/
|
||||
export function selectionsOnly(by: number, selections?: readonly vscode.Selection[]) {
|
||||
Selections.set(Selections.rotate(by, selections));
|
||||
}
|
||||
return replaceByIndex(
|
||||
(i, _, document) => document.getText(selections[(i + by) % len]),
|
||||
selections,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rotates the given selections (but not their contents) by the given offset.
|
||||
*
|
||||
* @see {@link rotate}
|
||||
*
|
||||
* ### Example
|
||||
* ```js
|
||||
* rotateSelections(1);
|
||||
* ```
|
||||
*
|
||||
* Before:
|
||||
* ```
|
||||
* a b c
|
||||
* ^ 0
|
||||
* ^ 1
|
||||
* ^ 2
|
||||
* ```
|
||||
*
|
||||
* After:
|
||||
* ```
|
||||
* a b c
|
||||
* ^ 1
|
||||
* ^ 2
|
||||
* ^ 0
|
||||
* ```
|
||||
*/
|
||||
export function rotateSelections(by: number, selections?: readonly vscode.Selection[]) {
|
||||
Selections.set(Selections.rotate(by, selections));
|
||||
}
|
||||
|
@ -17,7 +17,6 @@ export * from "./search/lines";
|
||||
export * from "./search/move";
|
||||
export * from "./search/move-to";
|
||||
export * from "./search/pairs";
|
||||
export * from "./search/range";
|
||||
export * from "./search/word";
|
||||
export * from "./types";
|
||||
|
||||
@ -27,6 +26,11 @@ export * from "./types";
|
||||
export * as Lines from "./lines";
|
||||
export { firstVisibleLine, middleVisibleLine, lastVisibleLine } from "./lines";
|
||||
|
||||
/**
|
||||
* Operations on ranges of text objects.
|
||||
*/
|
||||
export * as Range from "./search/range";
|
||||
|
||||
/**
|
||||
* Operations on `vscode.Position`s.
|
||||
*/
|
||||
|
@ -2,6 +2,6 @@
|
||||
/**
|
||||
* API for generating VS Code-compatible keybindings.
|
||||
*/
|
||||
export namespace Keybindings {
|
||||
export declare namespace Keybindings {
|
||||
|
||||
}
|
||||
|
@ -148,61 +148,57 @@ export function column(
|
||||
return new vscode.Position(line.line, text.length + diffAddedByTabs(text, editor));
|
||||
}
|
||||
|
||||
export namespace column {
|
||||
/**
|
||||
* Returns the `vscode.Position`-compatible position for the given position.
|
||||
* Reverses the diff added by `column`.
|
||||
*/
|
||||
export function character(
|
||||
position: vscode.Position,
|
||||
editor?: Pick<vscode.TextEditor, "document" | "options">,
|
||||
roundUp?: boolean,
|
||||
): vscode.Position;
|
||||
/**
|
||||
* Returns the {@link vscode.Position}-compatible position for the given
|
||||
* position. Reverses the diff added by {@link column}.
|
||||
*/
|
||||
export function character(
|
||||
position: vscode.Position,
|
||||
editor?: Pick<vscode.TextEditor, "document" | "options">,
|
||||
roundUp?: boolean,
|
||||
): vscode.Position;
|
||||
|
||||
/**
|
||||
* Returns the `vscode.Position`-compatible character for the given column.
|
||||
* Reverses the diff added by `column`.
|
||||
*/
|
||||
export function character(
|
||||
line: number,
|
||||
character: number,
|
||||
editor?: Pick<vscode.TextEditor, "document" | "options">,
|
||||
roundUp?: boolean,
|
||||
): number;
|
||||
/**
|
||||
* Returns the {@link vscode.Position}-compatible character for the given
|
||||
* column. Reverses the diff added by {@link column}.
|
||||
*/
|
||||
export function character(
|
||||
line: number,
|
||||
character: number,
|
||||
editor?: Pick<vscode.TextEditor, "document" | "options">,
|
||||
roundUp?: boolean,
|
||||
): number;
|
||||
|
||||
export function character(
|
||||
lineOrPosition: number | vscode.Position,
|
||||
characterOrEditor?: number | Pick<vscode.TextEditor, "document" | "options">,
|
||||
editorOrRoundUp?: Pick<vscode.TextEditor, "document" | "options"> | boolean,
|
||||
roundUp?: boolean,
|
||||
) {
|
||||
if (typeof lineOrPosition === "number") {
|
||||
// Second overload.
|
||||
const line = lineOrPosition,
|
||||
character = characterOrEditor as number,
|
||||
editor = editorOrRoundUp as vscode.TextEditor ?? Context.current.editor;
|
||||
export function character(
|
||||
lineOrPosition: number | vscode.Position,
|
||||
characterOrEditor?: number | Pick<vscode.TextEditor, "document" | "options">,
|
||||
editorOrRoundUp?: Pick<vscode.TextEditor, "document" | "options"> | boolean,
|
||||
roundUp?: boolean,
|
||||
) {
|
||||
if (typeof lineOrPosition === "number") {
|
||||
// Second overload.
|
||||
const line = lineOrPosition,
|
||||
character = characterOrEditor as number,
|
||||
editor = editorOrRoundUp as vscode.TextEditor ?? Context.current.editor;
|
||||
|
||||
return getCharacter(editor.document.lineAt(line).text, character, editor, roundUp ?? false);
|
||||
}
|
||||
|
||||
// First overload.
|
||||
const position = lineOrPosition,
|
||||
editor = characterOrEditor as vscode.TextEditor ?? Context.current.editor;
|
||||
|
||||
roundUp = editorOrRoundUp as boolean ?? false;
|
||||
|
||||
const text = editor.document.lineAt(position.line).text;
|
||||
|
||||
return new vscode.Position(
|
||||
position.line, getCharacter(text, position.character, editor, roundUp));
|
||||
return getCharacter(editor.document.lineAt(line).text, character, editor, roundUp ?? false);
|
||||
}
|
||||
|
||||
// First overload.
|
||||
const position = lineOrPosition,
|
||||
editor = characterOrEditor as vscode.TextEditor ?? Context.current.editor;
|
||||
|
||||
roundUp = editorOrRoundUp as boolean ?? false;
|
||||
|
||||
const text = editor.document.lineAt(position.line).text;
|
||||
|
||||
return new vscode.Position(
|
||||
position.line, getCharacter(text, position.character, editor, roundUp));
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as `Lines.length`, but also increases the count according to tab
|
||||
* Same as {@link length}, but also increases the count according to tab
|
||||
* characters so that the result matches the rendered view.
|
||||
*
|
||||
* @see Lines.length
|
||||
*/
|
||||
export function columns(
|
||||
line: number | vscode.Position,
|
||||
|
124
src/api/menu.ts
124
src/api/menu.ts
@ -1,13 +1,13 @@
|
||||
import * as vscode from "vscode";
|
||||
|
||||
import { Context } from "./context";
|
||||
import { keypress, prompt } from "./prompt";
|
||||
import { keypress, promptLocked, promptOne } from "./prompt";
|
||||
|
||||
export interface Menu {
|
||||
readonly items: Menu.Items;
|
||||
}
|
||||
|
||||
export namespace Menu {
|
||||
export declare namespace Menu {
|
||||
export interface Items {
|
||||
[keys: string]: Item;
|
||||
}
|
||||
@ -100,7 +100,7 @@ export async function showMenu(
|
||||
) {
|
||||
const entries = Object.entries(menu.items);
|
||||
const items = entries.map((x) => [x[0], x[1].text] as const);
|
||||
const choice = await prompt.one(items);
|
||||
const choice = await promptOne(items);
|
||||
|
||||
if (typeof choice === "string") {
|
||||
if (prefix !== undefined) {
|
||||
@ -118,66 +118,64 @@ export async function showMenu(
|
||||
);
|
||||
}
|
||||
|
||||
export namespace showMenu {
|
||||
/**
|
||||
* Shows the menu with the given name.
|
||||
*/
|
||||
export function byName(
|
||||
menuName: string,
|
||||
additionalArgs: readonly any[] = [],
|
||||
prefix?: string,
|
||||
) {
|
||||
return showMenu(findMenu(menuName), additionalArgs, prefix);
|
||||
}
|
||||
/**
|
||||
* Shows the menu with the given name.
|
||||
*/
|
||||
export function showMenuByName(
|
||||
menuName: string,
|
||||
additionalArgs: readonly any[] = [],
|
||||
prefix?: string,
|
||||
) {
|
||||
return showMenu(findMenu(menuName), additionalArgs, prefix);
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as `showMenu`, but only displays the menu after a specified delay.
|
||||
*/
|
||||
export async function withDelay(
|
||||
delayMs: number,
|
||||
menu: Menu,
|
||||
additionalArgs: readonly any[] = [],
|
||||
prefix?: string,
|
||||
) {
|
||||
const cancellationTokenSource = new vscode.CancellationTokenSource(),
|
||||
currentContext = Context.current;
|
||||
/**
|
||||
* Same as {@link showMenu}, but only displays the menu after a specified delay.
|
||||
*/
|
||||
export async function showMenuAfterDelay(
|
||||
delayMs: number,
|
||||
menu: Menu,
|
||||
additionalArgs: readonly any[] = [],
|
||||
prefix?: string,
|
||||
) {
|
||||
const cancellationTokenSource = new vscode.CancellationTokenSource(),
|
||||
currentContext = Context.current;
|
||||
|
||||
currentContext.cancellationToken.onCancellationRequested(() =>
|
||||
cancellationTokenSource.cancel());
|
||||
currentContext.cancellationToken.onCancellationRequested(() =>
|
||||
cancellationTokenSource.cancel());
|
||||
|
||||
const keypressContext = currentContext.withCancellationToken(cancellationTokenSource.token),
|
||||
timeout = setTimeout(() => cancellationTokenSource.cancel(), delayMs);
|
||||
const keypressContext = currentContext.withCancellationToken(cancellationTokenSource.token),
|
||||
timeout = setTimeout(() => cancellationTokenSource.cancel(), delayMs);
|
||||
|
||||
try {
|
||||
const key = await keypress(keypressContext);
|
||||
try {
|
||||
const key = await keypress(keypressContext);
|
||||
|
||||
clearTimeout(timeout);
|
||||
clearTimeout(timeout);
|
||||
|
||||
for (const itemKeys in menu.items) {
|
||||
if (!itemKeys.includes(key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const pickedItem = menu.items[itemKeys],
|
||||
args = mergeArgs(pickedItem.args, additionalArgs);
|
||||
|
||||
return Context.WithoutActiveEditor.wrap(
|
||||
vscode.commands.executeCommand(pickedItem.command, ...args),
|
||||
);
|
||||
for (const itemKeys in menu.items) {
|
||||
if (!itemKeys.includes(key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (prefix !== undefined) {
|
||||
await vscode.commands.executeCommand("default:type", { text: prefix + key });
|
||||
}
|
||||
} catch (e) {
|
||||
if (!currentContext.cancellationToken.isCancellationRequested) {
|
||||
return showMenu(menu, additionalArgs, prefix);
|
||||
}
|
||||
const pickedItem = menu.items[itemKeys],
|
||||
args = mergeArgs(pickedItem.args, additionalArgs);
|
||||
|
||||
throw e;
|
||||
} finally {
|
||||
cancellationTokenSource.dispose();
|
||||
return Context.WithoutActiveEditor.wrap(
|
||||
vscode.commands.executeCommand(pickedItem.command, ...args),
|
||||
);
|
||||
}
|
||||
|
||||
if (prefix !== undefined) {
|
||||
await vscode.commands.executeCommand("default:type", { text: prefix + key });
|
||||
}
|
||||
} catch (e) {
|
||||
if (!currentContext.cancellationToken.isCancellationRequested) {
|
||||
return showMenu(menu, additionalArgs, prefix);
|
||||
}
|
||||
|
||||
throw e;
|
||||
} finally {
|
||||
cancellationTokenSource.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@ -194,19 +192,17 @@ export async function showLockedMenu(
|
||||
vscode.commands.executeCommand(
|
||||
item.command, ...mergeArgs(item.args, additionalArgs))] as const);
|
||||
|
||||
await prompt.one.locked(items);
|
||||
await promptLocked(items);
|
||||
}
|
||||
|
||||
export namespace showLockedMenu {
|
||||
/**
|
||||
* Shows the menu with the given name.
|
||||
*/
|
||||
export function byName(
|
||||
menuName: string,
|
||||
additionalArgs: readonly any[] = [],
|
||||
) {
|
||||
return showLockedMenu(findMenu(menuName), additionalArgs);
|
||||
}
|
||||
/**
|
||||
* Shows the menu with the given name.
|
||||
*/
|
||||
export function showLockedMenyByName(
|
||||
menuName: string,
|
||||
additionalArgs: readonly any[] = [],
|
||||
) {
|
||||
return showLockedMenu(findMenu(menuName), additionalArgs);
|
||||
}
|
||||
|
||||
function mergeArgs(args: readonly any[] | undefined, additionalArgs: readonly any[]) {
|
||||
|
@ -80,23 +80,21 @@ export function offset(
|
||||
return document.positionAt(document.offsetAt(position) + by);
|
||||
}
|
||||
|
||||
export namespace offset {
|
||||
/**
|
||||
* Same as `offset`, but clamps to document edges.
|
||||
*/
|
||||
export function orEdge(
|
||||
position: vscode.Position,
|
||||
by: number,
|
||||
document?: vscode.TextDocument,
|
||||
) {
|
||||
const result = offset(position, by, document);
|
||||
/**
|
||||
* Same as {@link offset}, but clamps to document edges.
|
||||
*/
|
||||
export function offsetOrEdge(
|
||||
position: vscode.Position,
|
||||
by: number,
|
||||
document?: vscode.TextDocument,
|
||||
) {
|
||||
const result = offset(position, by, document);
|
||||
|
||||
if (result === undefined) {
|
||||
return by < 0 ? zero : last(document);
|
||||
}
|
||||
|
||||
return result;
|
||||
if (result === undefined) {
|
||||
return by < 0 ? zero : last(document);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -150,8 +148,8 @@ export function lineBreak(line: number, document = Context.current.document) {
|
||||
|
||||
/**
|
||||
* Returns the last position of the current document when going in the given
|
||||
* direction. If `Backward`, this is `Positions.zero`. If `Forward`, this is
|
||||
* `Positions.last(document)`.
|
||||
* direction. If {@link Direction.Backward}, this is {@link zero}. If
|
||||
* {@link Direction.Forward}, this is {@link last(document)}.
|
||||
*/
|
||||
export function edge(direction: Direction, document?: vscode.TextDocument) {
|
||||
return direction === Direction.Backward ? zero : last(document);
|
||||
|
@ -5,7 +5,7 @@ import { set as setSelections } from "./selections";
|
||||
import type { Input, SetInput } from "../commands";
|
||||
import { ArgumentError, CancellationError } from "../utils/errors";
|
||||
|
||||
const actionEvent = new vscode.EventEmitter<Parameters<typeof prompt.notifyActionRequested>[0]>();
|
||||
const actionEvent = new vscode.EventEmitter<Parameters<typeof notifyPromptActionRequested>[0]>();
|
||||
|
||||
/**
|
||||
* Displays a prompt to the user.
|
||||
@ -167,299 +167,302 @@ export function prompt(
|
||||
return context.wrap(promise);
|
||||
}
|
||||
|
||||
export namespace prompt {
|
||||
type RegExpFlag = "m" | "u" | "s" | "y" | "i" | "g";
|
||||
type RegExpFlags = RegExpFlag
|
||||
| `${RegExpFlag}${RegExpFlag}`
|
||||
| `${RegExpFlag}${RegExpFlag}${RegExpFlag}`
|
||||
| `${RegExpFlag}${RegExpFlag}${RegExpFlag}${RegExpFlag}`;
|
||||
|
||||
export declare namespace prompt {
|
||||
/**
|
||||
* Options for spawning a `prompt`.
|
||||
* Options for spawning a {@link prompt}.
|
||||
*/
|
||||
export interface Options extends vscode.InputBoxOptions {
|
||||
readonly history?: string[];
|
||||
readonly historySize?: number;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `vscode.InputBoxOptions` that only validate if a number in a given
|
||||
* range is entered.
|
||||
*/
|
||||
export function numberOpts(
|
||||
opts: { integer?: boolean; range?: [number, number] } = {},
|
||||
): vscode.InputBoxOptions {
|
||||
return {
|
||||
validateInput(input) {
|
||||
const n = +input;
|
||||
/**
|
||||
* Returns {@link vscode.InputBoxOptions} that only validate if a number in a
|
||||
* given range is entered.
|
||||
*/
|
||||
export function promptNumberOpts(
|
||||
opts: { integer?: boolean; range?: [number, number] } = {},
|
||||
): vscode.InputBoxOptions {
|
||||
return {
|
||||
validateInput(input) {
|
||||
const n = +input;
|
||||
|
||||
if (isNaN(n)) {
|
||||
return "Invalid number.";
|
||||
}
|
||||
|
||||
if (opts.range && (n < opts.range[0] || n > opts.range[1])) {
|
||||
return `Number out of range ${JSON.stringify(opts.range)}.`;
|
||||
}
|
||||
|
||||
if (opts.integer && (n | 0) !== n) {
|
||||
return `Number must be an integer.`;
|
||||
}
|
||||
|
||||
return;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Equivalent to `+await prompt(numberOpts(), context)`.
|
||||
*/
|
||||
export function number(
|
||||
opts: Parameters<typeof numberOpts>[0],
|
||||
context = Context.WithoutActiveEditor.current,
|
||||
) {
|
||||
return prompt(numberOpts(opts), context).then((x) => +x);
|
||||
}
|
||||
|
||||
/**
|
||||
* Last used inputs for `regexp` prompts.
|
||||
*/
|
||||
export const regexpHistory: string[] = [];
|
||||
|
||||
/**
|
||||
* Returns `vscode.InputBoxOptions` that only validate if a valid ECMAScript
|
||||
* regular expression is entered.
|
||||
*/
|
||||
export function regexpOpts(flags: RegExpFlags): prompt.Options {
|
||||
return {
|
||||
prompt: "Regular expression",
|
||||
validateInput(input) {
|
||||
if (input.length === 0) {
|
||||
return "RegExp cannot be empty";
|
||||
}
|
||||
|
||||
try {
|
||||
new RegExp(input, flags);
|
||||
|
||||
return undefined;
|
||||
} catch {
|
||||
return "invalid RegExp";
|
||||
}
|
||||
},
|
||||
|
||||
history: regexpHistory,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Equivalent to `new RegExp(await prompt(regexpOpts(flags), context), flags)`.
|
||||
*/
|
||||
export function regexp(
|
||||
flags: RegExpFlags,
|
||||
context = Context.WithoutActiveEditor.current,
|
||||
) {
|
||||
return prompt(regexpOpts(flags), context).then((x) => new RegExp(x, flags));
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompts the user for a result interactively.
|
||||
*/
|
||||
export function interactive<T>(
|
||||
compute: (input: string) => T | Thenable<T>,
|
||||
reset: () => void,
|
||||
options: vscode.InputBoxOptions = {},
|
||||
interactive: boolean = true,
|
||||
): Thenable<T> {
|
||||
let result: T;
|
||||
const validateInput = options.validateInput;
|
||||
|
||||
if (!interactive) {
|
||||
return prompt(options).then((value) => compute(value));
|
||||
}
|
||||
|
||||
return prompt({
|
||||
...options,
|
||||
async validateInput(input) {
|
||||
const validationError = await validateInput?.(input);
|
||||
|
||||
if (validationError) {
|
||||
return validationError;
|
||||
}
|
||||
|
||||
try {
|
||||
result = await compute(input);
|
||||
return;
|
||||
} catch (e) {
|
||||
return `${e}`;
|
||||
}
|
||||
},
|
||||
}).then(
|
||||
() => result,
|
||||
(err) => {
|
||||
reset();
|
||||
throw err;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export async function manipulateSelectionsInteractively<I, R>(
|
||||
_: Context,
|
||||
input: Input<I>,
|
||||
setInput: SetInput<R>,
|
||||
interactive: boolean,
|
||||
options: prompt.Options,
|
||||
f: (input: string | I, selections: readonly vscode.Selection[]) => Thenable<R>,
|
||||
) {
|
||||
const selections = _.selections;
|
||||
|
||||
function execute(input: string | I) {
|
||||
return _.runAsync(() => f(input, selections));
|
||||
}
|
||||
|
||||
function undo() {
|
||||
setSelections(selections);
|
||||
}
|
||||
|
||||
if (input === undefined) {
|
||||
setInput(await prompt.interactive(execute, undo, options, interactive));
|
||||
} else {
|
||||
await execute(input);
|
||||
}
|
||||
}
|
||||
|
||||
export type ListPair = readonly [string, string];
|
||||
|
||||
/**
|
||||
* Prompts the user to choose one item among a list of items, and returns the
|
||||
* index of the item that was picked.
|
||||
*/
|
||||
export function one(
|
||||
items: readonly ListPair[],
|
||||
init?: (quickPick: vscode.QuickPick<vscode.QuickPickItem>) => void,
|
||||
options?: Readonly<{ defaultPickName?: string, defaultPick?: string }>,
|
||||
context = Context.WithoutActiveEditor.current,
|
||||
) {
|
||||
if (options?.defaultPick != null) {
|
||||
const defaultPick = options.defaultPick,
|
||||
index = items.findIndex((pair) => pair[0] === defaultPick);
|
||||
|
||||
if (index === -1) {
|
||||
const pickName = options.defaultPickName ?? "options.defaultPick",
|
||||
choices = items.map((pair) => '"' + pair[0] + '"').join(", ");
|
||||
|
||||
return Promise.reject(new ArgumentError(`"${pickName}" must be one of ${choices}`));
|
||||
if (isNaN(n)) {
|
||||
return "Invalid number.";
|
||||
}
|
||||
|
||||
return Promise.resolve(index);
|
||||
}
|
||||
if (opts.range && (n < opts.range[0] || n > opts.range[1])) {
|
||||
return `Number out of range ${JSON.stringify(opts.range)}.`;
|
||||
}
|
||||
|
||||
return promptInList(false, items, init ?? (() => {}), context.cancellationToken);
|
||||
if (opts.integer && (n | 0) !== n) {
|
||||
return `Number must be an integer.`;
|
||||
}
|
||||
|
||||
return;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Equivalent to `+await prompt(numberOpts(), context)`.
|
||||
*/
|
||||
export function promptNumber(
|
||||
opts: Parameters<typeof promptNumberOpts>[0],
|
||||
context = Context.WithoutActiveEditor.current,
|
||||
) {
|
||||
return prompt(promptNumberOpts(opts), context).then((x) => +x);
|
||||
}
|
||||
|
||||
/**
|
||||
* Last used inputs for {@link promptRegexp}.
|
||||
*/
|
||||
export const regexpHistory: string[] = [];
|
||||
|
||||
/**
|
||||
* Returns {@link vscode.InputBoxOptions} that only validate if a valid
|
||||
* ECMAScript regular expression is entered.
|
||||
*/
|
||||
export function promptRegexpOpts(flags: promptRegexp.Flags): prompt.Options {
|
||||
return {
|
||||
prompt: "Regular expression",
|
||||
validateInput(input) {
|
||||
if (input.length === 0) {
|
||||
return "RegExp cannot be empty";
|
||||
}
|
||||
|
||||
try {
|
||||
new RegExp(input, flags);
|
||||
|
||||
return undefined;
|
||||
} catch {
|
||||
return "invalid RegExp";
|
||||
}
|
||||
},
|
||||
|
||||
history: regexpHistory,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Equivalent to `new RegExp(await prompt(regexpOpts(flags), context), flags)`.
|
||||
*/
|
||||
export function promptRegexp(
|
||||
flags: promptRegexp.Flags,
|
||||
context = Context.WithoutActiveEditor.current,
|
||||
) {
|
||||
return prompt(promptRegexpOpts(flags), context).then((x) => new RegExp(x, flags));
|
||||
}
|
||||
|
||||
export declare namespace promptRegexp {
|
||||
export type Flag = "m" | "u" | "s" | "y" | "i" | "g";
|
||||
export type Flags = Flag
|
||||
| `${Flag}${Flag}`
|
||||
| `${Flag}${Flag}${Flag}`
|
||||
| `${Flag}${Flag}${Flag}${Flag}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompts the user for a result interactively.
|
||||
*/
|
||||
export function promptInteractively<T>(
|
||||
compute: (input: string) => T | Thenable<T>,
|
||||
reset: () => void,
|
||||
options: vscode.InputBoxOptions = {},
|
||||
interactive: boolean = true,
|
||||
): Thenable<T> {
|
||||
let result: T;
|
||||
const validateInput = options.validateInput;
|
||||
|
||||
if (!interactive) {
|
||||
return prompt(options).then((value) => compute(value));
|
||||
}
|
||||
|
||||
export namespace one {
|
||||
/**
|
||||
* Prompts the user for actions in a menu, only hiding it when a
|
||||
* cancellation is requested or `Escape` pressed.
|
||||
*/
|
||||
export function locked(
|
||||
items: readonly (readonly [string, string, () => void])[],
|
||||
init?: (quickPick: vscode.QuickPick<vscode.QuickPickItem>) => void,
|
||||
cancellationToken = Context.WithoutActiveEditor.current.cancellationToken,
|
||||
) {
|
||||
const itemsKeys = items.map(([k, _]) => k.includes(", ") ? k.split(", ") : [...k]);
|
||||
return prompt({
|
||||
...options,
|
||||
async validateInput(input) {
|
||||
const validationError = await validateInput?.(input);
|
||||
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const quickPick = vscode.window.createQuickPick(),
|
||||
quickPickItems = [] as vscode.QuickPickItem[];
|
||||
if (validationError) {
|
||||
return validationError;
|
||||
}
|
||||
|
||||
let isCaseSignificant = false;
|
||||
try {
|
||||
result = await compute(input);
|
||||
return;
|
||||
} catch (e) {
|
||||
return `${e}`;
|
||||
}
|
||||
},
|
||||
}).then(
|
||||
() => result,
|
||||
(err) => {
|
||||
reset();
|
||||
throw err;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const [label, description] = items[i];
|
||||
/**
|
||||
* Calls `f` and updates the `input` with `setInput` when the prompt created
|
||||
* with the given `options` is interacted with.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export async function manipulateSelectionsInteractively<I, R>(
|
||||
_: Context,
|
||||
input: Input<I>,
|
||||
setInput: SetInput<R>,
|
||||
interactive: boolean,
|
||||
options: prompt.Options,
|
||||
f: (input: string | I, selections: readonly vscode.Selection[]) => Thenable<R>,
|
||||
) {
|
||||
const selections = _.selections;
|
||||
|
||||
quickPickItems.push({ label, description });
|
||||
isCaseSignificant = isCaseSignificant || label.toLowerCase() !== label;
|
||||
function execute(input: string | I) {
|
||||
return _.runAsync(() => f(input, selections));
|
||||
}
|
||||
|
||||
function undo() {
|
||||
setSelections(selections);
|
||||
}
|
||||
|
||||
if (input === undefined) {
|
||||
setInput(await promptInteractively(execute, undo, options, interactive));
|
||||
} else {
|
||||
await execute(input);
|
||||
}
|
||||
}
|
||||
|
||||
export type ListPair = readonly [string, string];
|
||||
|
||||
/**
|
||||
* Prompts the user to choose one item among a list of items, and returns the
|
||||
* index of the item that was picked.
|
||||
*/
|
||||
export function promptOne(
|
||||
items: readonly ListPair[],
|
||||
init?: (quickPick: vscode.QuickPick<vscode.QuickPickItem>) => void,
|
||||
options?: Readonly<{ defaultPickName?: string, defaultPick?: string }>,
|
||||
context = Context.WithoutActiveEditor.current,
|
||||
) {
|
||||
if (options?.defaultPick != null) {
|
||||
const defaultPick = options.defaultPick,
|
||||
index = items.findIndex((pair) => pair[0] === defaultPick);
|
||||
|
||||
if (index === -1) {
|
||||
const pickName = options.defaultPickName ?? "options.defaultPick",
|
||||
choices = items.map((pair) => '"' + pair[0] + '"').join(", ");
|
||||
|
||||
return Promise.reject(new ArgumentError(`"${pickName}" must be one of ${choices}`));
|
||||
}
|
||||
|
||||
return Promise.resolve(index);
|
||||
}
|
||||
|
||||
return promptInList(false, items, init ?? (() => {}), context.cancellationToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompts the user for actions in a menu, only hiding it when a
|
||||
* cancellation is requested or `Escape` pressed.
|
||||
*/
|
||||
export function promptLocked(
|
||||
items: readonly (readonly [string, string, () => void])[],
|
||||
init?: (quickPick: vscode.QuickPick<vscode.QuickPickItem>) => void,
|
||||
cancellationToken = Context.WithoutActiveEditor.current.cancellationToken,
|
||||
) {
|
||||
const itemsKeys = items.map(([k, _]) => k.includes(", ") ? k.split(", ") : [...k]);
|
||||
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const quickPick = vscode.window.createQuickPick(),
|
||||
quickPickItems = [] as vscode.QuickPickItem[];
|
||||
|
||||
let isCaseSignificant = false;
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const [label, description] = items[i];
|
||||
|
||||
quickPickItems.push({ label, description });
|
||||
isCaseSignificant = isCaseSignificant || label.toLowerCase() !== label;
|
||||
}
|
||||
|
||||
quickPick.items = quickPickItems;
|
||||
quickPick.placeholder = "Press one of the below keys.";
|
||||
|
||||
const subscriptions = [
|
||||
quickPick.onDidChangeValue((rawKey) => {
|
||||
quickPick.value = "";
|
||||
|
||||
// This causes the menu to disappear and reappear for a frame, but
|
||||
// without this the shown items don't get refreshed after the value
|
||||
// change above.
|
||||
quickPick.items = quickPickItems;
|
||||
|
||||
let key = rawKey;
|
||||
|
||||
if (!isCaseSignificant) {
|
||||
key = key.toLowerCase();
|
||||
}
|
||||
|
||||
quickPick.items = quickPickItems;
|
||||
quickPick.placeholder = "Press one of the below keys.";
|
||||
const index = itemsKeys.findIndex((x) => x.includes(key));
|
||||
|
||||
const subscriptions = [
|
||||
quickPick.onDidChangeValue((rawKey) => {
|
||||
quickPick.value = "";
|
||||
if (index !== -1) {
|
||||
items[index][2]();
|
||||
}
|
||||
}),
|
||||
|
||||
// This causes the menu to disappear and reappear for a frame, but
|
||||
// without this the shown items don't get refreshed after the value
|
||||
// change above.
|
||||
quickPick.items = quickPickItems;
|
||||
quickPick.onDidHide(() => {
|
||||
subscriptions.splice(0).forEach((s) => s.dispose());
|
||||
|
||||
let key = rawKey;
|
||||
resolve();
|
||||
}),
|
||||
|
||||
if (!isCaseSignificant) {
|
||||
key = key.toLowerCase();
|
||||
}
|
||||
quickPick.onDidAccept(() => {
|
||||
subscriptions.splice(0).forEach((s) => s.dispose());
|
||||
|
||||
const index = itemsKeys.findIndex((x) => x.includes(key));
|
||||
const picked = quickPick.selectedItems[0];
|
||||
|
||||
if (index !== -1) {
|
||||
items[index][2]();
|
||||
}
|
||||
}),
|
||||
try {
|
||||
items.find((x) => x[1] === picked.description)![2]();
|
||||
} finally {
|
||||
resolve();
|
||||
}
|
||||
}),
|
||||
|
||||
quickPick.onDidHide(() => {
|
||||
subscriptions.splice(0).forEach((s) => s.dispose());
|
||||
cancellationToken?.onCancellationRequested(() => {
|
||||
subscriptions.splice(0).forEach((s) => s.dispose());
|
||||
|
||||
resolve();
|
||||
}),
|
||||
reject(new CancellationError(CancellationError.Reason.CancellationToken));
|
||||
}),
|
||||
|
||||
quickPick.onDidAccept(() => {
|
||||
subscriptions.splice(0).forEach((s) => s.dispose());
|
||||
quickPick,
|
||||
];
|
||||
|
||||
const picked = quickPick.selectedItems[0];
|
||||
init?.(quickPick);
|
||||
|
||||
try {
|
||||
items.find((x) => x[1] === picked.description)![2]();
|
||||
} finally {
|
||||
resolve();
|
||||
}
|
||||
}),
|
||||
quickPick.show();
|
||||
});
|
||||
}
|
||||
|
||||
cancellationToken?.onCancellationRequested(() => {
|
||||
subscriptions.splice(0).forEach((s) => s.dispose());
|
||||
/**
|
||||
* Prompts the user to choose many items among a list of items, and returns a
|
||||
* list of indices of picked items.
|
||||
*/
|
||||
export function promptMany(
|
||||
items: readonly ListPair[],
|
||||
init?: (quickPick: vscode.QuickPick<vscode.QuickPickItem>) => void,
|
||||
context = Context.WithoutActiveEditor.current,
|
||||
) {
|
||||
return promptInList(true, items, init ?? (() => {}), context.cancellationToken);
|
||||
}
|
||||
|
||||
reject(new CancellationError(CancellationError.Reason.CancellationToken));
|
||||
}),
|
||||
|
||||
quickPick,
|
||||
];
|
||||
|
||||
init?.(quickPick);
|
||||
|
||||
quickPick.show();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompts the user to choose many items among a list of items, and returns a
|
||||
* list of indices of picked items.
|
||||
*/
|
||||
export function many(
|
||||
items: readonly ListPair[],
|
||||
init?: (quickPick: vscode.QuickPick<vscode.QuickPickItem>) => void,
|
||||
context = Context.WithoutActiveEditor.current,
|
||||
) {
|
||||
return promptInList(true, items, init ?? (() => {}), context.cancellationToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies an active prompt, if any, that an action has been requested.
|
||||
*/
|
||||
export function notifyActionRequested(action: "next" | "previous" | "clear") {
|
||||
actionEvent.fire(action);
|
||||
}
|
||||
/**
|
||||
* Notifies an active prompt, if any, that an action has been requested.
|
||||
*/
|
||||
export function notifyPromptActionRequested(action: "next" | "previous" | "clear") {
|
||||
actionEvent.fire(action);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -502,21 +505,19 @@ export function keypress(context = Context.current): Promise<string> {
|
||||
);
|
||||
}
|
||||
|
||||
export namespace keypress {
|
||||
/**
|
||||
* Awaits a keypress describing a register and returns the specified register.
|
||||
*/
|
||||
export async function forRegister(context = Context.current) {
|
||||
const firstKey = await keypress(context);
|
||||
/**
|
||||
* Awaits a keypress describing a register and returns the specified register.
|
||||
*/
|
||||
export async function keypressForRegister(context = Context.current) {
|
||||
const firstKey = await keypress(context);
|
||||
|
||||
if (firstKey !== " ") {
|
||||
return context.extension.registers.get(firstKey);
|
||||
}
|
||||
|
||||
const secondKey = await keypress(context);
|
||||
|
||||
return context.extension.registers.forDocument(context.document).get(secondKey);
|
||||
if (firstKey !== " ") {
|
||||
return context.extension.registers.get(firstKey);
|
||||
}
|
||||
|
||||
const secondKey = await keypress(context);
|
||||
|
||||
return context.extension.registers.forDocument(context.document).get(secondKey);
|
||||
}
|
||||
|
||||
function promptInList(
|
||||
|
296
src/api/run.ts
296
src/api/run.ts
@ -24,10 +24,10 @@ export function run(strings: string | readonly string[], context: object = {}) {
|
||||
const functions: ((...args: any[]) => Thenable<unknown>)[] = [];
|
||||
|
||||
for (const code of strings) {
|
||||
functions.push(run.compileFunction(code, Object.keys(context)));
|
||||
functions.push(compileFunction(code, Object.keys(context)));
|
||||
}
|
||||
|
||||
const parameterValues = run.parameterValues();
|
||||
const parameterValues = runParameterValues();
|
||||
|
||||
if (isSingleStringArgument) {
|
||||
return Context.WithoutActiveEditor.wrap(
|
||||
@ -63,134 +63,132 @@ function ensureCacheIsPopulated() {
|
||||
cachedParameters.push(vscode);
|
||||
}
|
||||
|
||||
export namespace run {
|
||||
/**
|
||||
* Sets the globals available within {@link run} expressions.
|
||||
*/
|
||||
export function setGlobals(globals: object) {
|
||||
cachedParameterNames.length = 0;
|
||||
cachedParameters.length = 0;
|
||||
/**
|
||||
* Sets the globals available within {@link run} expressions.
|
||||
*/
|
||||
export function setRunGlobals(globals: object) {
|
||||
cachedParameterNames.length = 0;
|
||||
cachedParameters.length = 0;
|
||||
|
||||
globalsObject = globals;
|
||||
globalsObject = globals;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the parameter names given to dynamically run functions.
|
||||
*/
|
||||
export function runParameterNames() {
|
||||
ensureCacheIsPopulated();
|
||||
|
||||
return cachedParameterNames as readonly string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the parameter values given to dynamically run functions.
|
||||
*/
|
||||
export function runParameterValues() {
|
||||
ensureCacheIsPopulated();
|
||||
|
||||
return cachedParameters as readonly unknown[];
|
||||
}
|
||||
|
||||
let canRunArbitraryCode = true;
|
||||
|
||||
/**
|
||||
* Disables usage of the {@link compileFunction} and {@link run} functions,
|
||||
* preventing the execution of arbitrary user inputs.
|
||||
*
|
||||
* For security purposes, execution cannot be re-enabled after calling this
|
||||
* function.
|
||||
*/
|
||||
export function disableRunFunction() {
|
||||
canRunArbitraryCode = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether {@link compileFunction} and {@link run} can be used.
|
||||
*/
|
||||
export function runIsEnabled() {
|
||||
return canRunArbitraryCode;
|
||||
}
|
||||
|
||||
interface CompiledFunction {
|
||||
(...args: any[]): Thenable<unknown>;
|
||||
}
|
||||
|
||||
const AsyncFunction: new (...names: string[]) => CompiledFunction =
|
||||
async function () {}.constructor as any,
|
||||
functionCache = new Map<string, CachedFunction>();
|
||||
|
||||
type CachedFunction = [funct: CompiledFunction, lastAccessTimestamp: number];
|
||||
|
||||
/**
|
||||
* A few common inputs.
|
||||
*/
|
||||
const safeExpressions = [
|
||||
/^(\$\$?|[in]|\d+) *([=!]==?|[<>]=?|&{1,2}|\|{1,2}) *(\$\$?|[in]|\d+)$/,
|
||||
/^i( + 1)?$/,
|
||||
/^`\${await register\(["']\w+["'], *[i0-9]\)}` !== ["']false["']$/,
|
||||
];
|
||||
|
||||
/**
|
||||
* Compiles the given JavaScript code into a function.
|
||||
*/
|
||||
export function compileFunction(code: string, additionalParameterNames: readonly string[] = []) {
|
||||
if (!canRunArbitraryCode && !safeExpressions.some((re) => re.test(code))) {
|
||||
throw new Error("execution of arbitrary code is disabled");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the parameter names given to dynamically run functions.
|
||||
*/
|
||||
export function parameterNames() {
|
||||
ensureCacheIsPopulated();
|
||||
const cacheId = additionalParameterNames.join(";") + code,
|
||||
cached = functionCache.get(cacheId);
|
||||
|
||||
return cachedParameterNames as readonly string[];
|
||||
if (cached !== undefined) {
|
||||
cached[1] = Date.now();
|
||||
|
||||
return cached[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the parameter values given to dynamically run functions.
|
||||
*/
|
||||
export function parameterValues() {
|
||||
ensureCacheIsPopulated();
|
||||
let func: CompiledFunction;
|
||||
|
||||
return cachedParameters as readonly unknown[];
|
||||
try {
|
||||
// Wrap code in block to allow shadowing of parameters.
|
||||
func = new AsyncFunction(...runParameterNames(), ...additionalParameterNames, `{\n${code}\n}`);
|
||||
} catch (e) {
|
||||
throw new Error(`cannot parse function body: ${code}: ${e}`);
|
||||
}
|
||||
|
||||
let canRunArbitraryCode = true;
|
||||
functionCache.set(cacheId, [func, Date.now()]);
|
||||
|
||||
/**
|
||||
* Disables usage of the `compileFunction` and `run` functions, preventing the
|
||||
* execution of arbitrary user inputs.
|
||||
*
|
||||
* For security purposes, execution cannot be re-enabled after calling this
|
||||
* function.
|
||||
*/
|
||||
export function disable() {
|
||||
canRunArbitraryCode = false;
|
||||
return func;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all functions that were not used in the last n milliseconds from
|
||||
* the cache.
|
||||
*/
|
||||
export function clearCompiledFunctionsCache(olderThanMs: number): void;
|
||||
|
||||
/**
|
||||
* Removes all functions that were not used in the last 5 minutes from the
|
||||
* cache.
|
||||
*/
|
||||
export function clearCompiledFunctionsCache(): void;
|
||||
|
||||
export function clearCompiledFunctionsCache(olderThanMs = 1000 * 60 * 5) {
|
||||
if (olderThanMs === 0) {
|
||||
return functionCache.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether `compileFunction` and `run` can be used.
|
||||
*/
|
||||
export function isEnabled() {
|
||||
return canRunArbitraryCode;
|
||||
}
|
||||
const olderThan = Date.now() - olderThanMs,
|
||||
toDelete = [] as string[];
|
||||
|
||||
interface CompiledFunction {
|
||||
(...args: any[]): Thenable<unknown>;
|
||||
}
|
||||
|
||||
const AsyncFunction: new (...names: string[]) => CompiledFunction =
|
||||
async function () {}.constructor as any,
|
||||
functionCache = new Map<string, CachedFunction>();
|
||||
|
||||
type CachedFunction = [funct: CompiledFunction, lastAccessTimestamp: number];
|
||||
|
||||
/**
|
||||
* A few common inputs.
|
||||
*/
|
||||
const safeExpressions = [
|
||||
/^(\$\$?|[in]|\d+) *([=!]==?|[<>]=?|&{1,2}|\|{1,2}) *(\$\$?|[in]|\d+)$/,
|
||||
/^i( + 1)?$/,
|
||||
/^`\${await register\(["']\w+["'], *[i0-9]\)}` !== ["']false["']$/,
|
||||
];
|
||||
|
||||
/**
|
||||
* Compiles the given JavaScript code into a function.
|
||||
*/
|
||||
export function compileFunction(code: string, additionalParameterNames: readonly string[] = []) {
|
||||
if (!canRunArbitraryCode && !safeExpressions.some((re) => re.test(code))) {
|
||||
throw new Error("execution of arbitrary code is disabled");
|
||||
for (const [code, value] of functionCache) {
|
||||
if (value[1] < olderThan) {
|
||||
toDelete.push(code);
|
||||
}
|
||||
|
||||
const cacheId = additionalParameterNames.join(";") + code,
|
||||
cached = functionCache.get(cacheId);
|
||||
|
||||
if (cached !== undefined) {
|
||||
cached[1] = Date.now();
|
||||
|
||||
return cached[0];
|
||||
}
|
||||
|
||||
let func: CompiledFunction;
|
||||
|
||||
try {
|
||||
// Wrap code in block to allow shadowing of parameters.
|
||||
func = new AsyncFunction(...parameterNames(), ...additionalParameterNames, `{\n${code}\n}`);
|
||||
} catch (e) {
|
||||
throw new Error(`cannot parse function body: ${code}: ${e}`);
|
||||
}
|
||||
|
||||
functionCache.set(cacheId, [func, Date.now()]);
|
||||
|
||||
return func;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all functions that were not used in the last n milliseconds from
|
||||
* the cache.
|
||||
*/
|
||||
export function clearCache(olderThanMs: number): void;
|
||||
|
||||
/**
|
||||
* Removes all functions that were not used in the last 5 minutes from the
|
||||
* cache.
|
||||
*/
|
||||
export function clearCache(): void;
|
||||
|
||||
export function clearCache(olderThanMs = 1000 * 60 * 5) {
|
||||
if (olderThanMs === 0) {
|
||||
return functionCache.clear();
|
||||
}
|
||||
|
||||
const olderThan = Date.now() - olderThanMs,
|
||||
toDelete = [] as string[];
|
||||
|
||||
for (const [code, value] of functionCache) {
|
||||
if (value[1] < olderThan) {
|
||||
toDelete.push(code);
|
||||
}
|
||||
}
|
||||
|
||||
for (const code of toDelete) {
|
||||
functionCache.delete(code);
|
||||
}
|
||||
for (const code of toDelete) {
|
||||
functionCache.delete(code);
|
||||
}
|
||||
}
|
||||
|
||||
@ -315,14 +313,14 @@ export async function commands(...commands: readonly command.Any[]): Promise<any
|
||||
return results;
|
||||
}
|
||||
|
||||
export namespace command {
|
||||
export declare namespace command {
|
||||
/**
|
||||
* A tuple given to `command`.
|
||||
* A tuple given to {@link command}.
|
||||
*/
|
||||
export type Tuple = readonly [commandId: string, ...args: any[]];
|
||||
|
||||
/**
|
||||
* An object given to `command`.
|
||||
* An object given to {@link command}.
|
||||
*/
|
||||
export interface Command {
|
||||
readonly command: string;
|
||||
@ -390,17 +388,15 @@ export function execute(
|
||||
return Context.WithoutActiveEditor.wrap(promise);
|
||||
}
|
||||
|
||||
export namespace execute {
|
||||
/**
|
||||
* Disables usage of the `execute` function, preventing the execution of
|
||||
* arbitrary user commands.
|
||||
*
|
||||
* For security purposes, execution cannot be re-enabled after calling this
|
||||
* function.
|
||||
*/
|
||||
export function disable() {
|
||||
canExecuteArbitraryCommands = false;
|
||||
}
|
||||
/**
|
||||
* Disables usage of the {@link execute} function, preventing the execution of
|
||||
* arbitrary user commands.
|
||||
*
|
||||
* For security purposes, execution cannot be re-enabled after calling this
|
||||
* function.
|
||||
*/
|
||||
export function disableExecuteFunction() {
|
||||
canExecuteArbitraryCommands = false;
|
||||
}
|
||||
|
||||
function getShell() {
|
||||
@ -464,28 +460,26 @@ export function switchRun(string: string, context: { $: string } & Record<string
|
||||
return run("return " + string, context);
|
||||
}
|
||||
|
||||
export namespace switchRun {
|
||||
/**
|
||||
* Validates the given input string. If it is invalid, an exception will be
|
||||
* thrown.
|
||||
*/
|
||||
export function validate(string: string) {
|
||||
if (string.trim().length === 0) {
|
||||
throw new Error("the given string cannot be empty");
|
||||
}
|
||||
|
||||
if (string[0] === "/") {
|
||||
parseRegExpWithReplacement(string);
|
||||
return;
|
||||
}
|
||||
|
||||
if (string[0] === "#") {
|
||||
if (string.slice(1).trim().length === 0) {
|
||||
throw new Error("the given shell command cannot be empty");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
run.compileFunction("return " + string);
|
||||
/**
|
||||
* Validates the given input string for use in {@link switchRun}. If it is
|
||||
* invalid, an exception will be thrown.
|
||||
*/
|
||||
export function validateForSwitchRun(string: string) {
|
||||
if (string.trim().length === 0) {
|
||||
throw new Error("the given string cannot be empty");
|
||||
}
|
||||
|
||||
if (string[0] === "/") {
|
||||
parseRegExpWithReplacement(string);
|
||||
return;
|
||||
}
|
||||
|
||||
if (string[0] === "#") {
|
||||
if (string.slice(1).trim().length === 0) {
|
||||
throw new Error("the given shell command cannot be empty");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
compileFunction("return " + string);
|
||||
}
|
||||
|
@ -8,7 +8,8 @@ import { canMatchLineFeed, execLast, matchesStaticStrings } from "../../utils/re
|
||||
/**
|
||||
* Searches backward or forward for a pattern starting at the given position.
|
||||
*
|
||||
* @see search.backward,search.forward
|
||||
* @see {@link searchBackward}
|
||||
* @see {@link searchForward}
|
||||
*/
|
||||
export function search(
|
||||
direction: Direction,
|
||||
@ -17,135 +18,135 @@ export function search(
|
||||
end?: vscode.Position,
|
||||
) {
|
||||
return direction === Direction.Backward
|
||||
? search.backward(re, origin, end)
|
||||
: search.forward(re, origin, end);
|
||||
? searchBackward(re, origin, end)
|
||||
: searchForward(re, origin, end);
|
||||
}
|
||||
|
||||
export namespace search {
|
||||
export declare namespace search {
|
||||
/**
|
||||
* The type of the result of a search: a `[startPosition, match]` pair if the
|
||||
* search succeeded, and `undefined` otherwise.
|
||||
*/
|
||||
export type Result = [vscode.Position, RegExpMatchArray] | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches backward for a pattern starting at the given position.
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* ```ts
|
||||
* const [p1, [t1]] = search.backward(/\w/, new vscode.Position(0, 1))!;
|
||||
*
|
||||
* assert.deepStrictEqual(p1, new vscode.Position(0, 0));
|
||||
* assert.strictEqual(t1, "a");
|
||||
*
|
||||
* const [p2, [t2]] = search.backward(/\w/, new vscode.Position(0, 2))!;
|
||||
*
|
||||
* assert.deepStrictEqual(p2, new vscode.Position(0, 1));
|
||||
* assert.strictEqual(t2, "b");
|
||||
*
|
||||
* const [p3, [t3]] = search.backward(/\w+/, new vscode.Position(0, 2))!;
|
||||
*
|
||||
* assert.deepStrictEqual(p3, new vscode.Position(0, 0));
|
||||
* assert.strictEqual(t3, "ab");
|
||||
*
|
||||
* assert.strictEqual(
|
||||
* search.backward(/\w/, new vscode.Position(0, 0)),
|
||||
* undefined,
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* With:
|
||||
* ```
|
||||
* abc
|
||||
* ```
|
||||
*/
|
||||
export function backward(re: RegExp, origin: vscode.Position, end?: vscode.Position): Result {
|
||||
end ??= Positions.zero;
|
||||
/**
|
||||
* Searches backward for a pattern starting at the given position.
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* ```ts
|
||||
* const [p1, [t1]] = searchBackward(/\w/, new vscode.Position(0, 1))!;
|
||||
*
|
||||
* assert.deepStrictEqual(p1, new vscode.Position(0, 0));
|
||||
* assert.strictEqual(t1, "a");
|
||||
*
|
||||
* const [p2, [t2]] = searchBackward(/\w/, new vscode.Position(0, 2))!;
|
||||
*
|
||||
* assert.deepStrictEqual(p2, new vscode.Position(0, 1));
|
||||
* assert.strictEqual(t2, "b");
|
||||
*
|
||||
* const [p3, [t3]] = searchBackward(/\w+/, new vscode.Position(0, 2))!;
|
||||
*
|
||||
* assert.deepStrictEqual(p3, new vscode.Position(0, 0));
|
||||
* assert.strictEqual(t3, "ab");
|
||||
*
|
||||
* assert.strictEqual(
|
||||
* searchBackward(/\w/, new vscode.Position(0, 0)),
|
||||
* undefined,
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* With:
|
||||
* ```
|
||||
* abc
|
||||
* ```
|
||||
*/
|
||||
export function searchBackward(re: RegExp, origin: vscode.Position, end?: vscode.Position): search.Result {
|
||||
end ??= Positions.zero;
|
||||
|
||||
const document = Context.current.document,
|
||||
searchStart = document.offsetAt(end),
|
||||
searchEnd = document.offsetAt(origin),
|
||||
possibleSearchLength = searchEnd - searchStart;
|
||||
const document = Context.current.document,
|
||||
searchStart = document.offsetAt(end),
|
||||
searchEnd = document.offsetAt(origin),
|
||||
possibleSearchLength = searchEnd - searchStart;
|
||||
|
||||
if (possibleSearchLength < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (possibleSearchLength > 2_000) {
|
||||
const staticMatches = matchesStaticStrings(re);
|
||||
|
||||
if (staticMatches !== undefined) {
|
||||
return searchOneOfBackward(re, staticMatches, origin, end, document);
|
||||
}
|
||||
|
||||
if (!canMatchLineFeed(re)) {
|
||||
return searchSingleLineRegExpBackward(re, origin, end, document);
|
||||
}
|
||||
}
|
||||
|
||||
return searchNaiveBackward(re, origin, end, document);
|
||||
if (possibleSearchLength < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches forward for a pattern starting at the given position.
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* ```ts
|
||||
* const [p1, [t1]] = search.forward(/\w/, new vscode.Position(0, 0))!;
|
||||
*
|
||||
* assert.deepStrictEqual(p1, new vscode.Position(0, 0));
|
||||
* assert.strictEqual(t1, "a");
|
||||
*
|
||||
* const [p2, [t2]] = search.forward(/\w/, new vscode.Position(0, 1))!;
|
||||
*
|
||||
* assert.deepStrictEqual(p2, new vscode.Position(0, 1));
|
||||
* assert.strictEqual(t2, "b");
|
||||
*
|
||||
* const [p3, [t3]] = search.forward(/\w+/, new vscode.Position(0, 1))!;
|
||||
*
|
||||
* assert.deepStrictEqual(p3, new vscode.Position(0, 1));
|
||||
* assert.strictEqual(t3, "bc");
|
||||
*
|
||||
* assert.strictEqual(
|
||||
* search.forward(/\w/, new vscode.Position(0, 3)),
|
||||
* undefined,
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* With:
|
||||
* ```
|
||||
* abc
|
||||
* ```
|
||||
*/
|
||||
export function forward(re: RegExp, origin: vscode.Position, end?: vscode.Position): Result {
|
||||
const document = Context.current.document;
|
||||
if (possibleSearchLength > 2_000) {
|
||||
const staticMatches = matchesStaticStrings(re);
|
||||
|
||||
end ??= Positions.last(document);
|
||||
|
||||
const searchStart = document.offsetAt(origin),
|
||||
searchEnd = document.offsetAt(end),
|
||||
possibleSearchLength = searchEnd - searchStart;
|
||||
|
||||
if (possibleSearchLength < 0) {
|
||||
return;
|
||||
if (staticMatches !== undefined) {
|
||||
return searchOneOfBackward(re, staticMatches, origin, end, document);
|
||||
}
|
||||
|
||||
if (possibleSearchLength > 2_000) {
|
||||
const staticMatches = matchesStaticStrings(re);
|
||||
|
||||
if (staticMatches !== undefined) {
|
||||
return searchOneOfForward(re, staticMatches, origin, end, document);
|
||||
}
|
||||
|
||||
if (!canMatchLineFeed(re)) {
|
||||
return searchSingleLineRegExpForward(re, origin, end, document);
|
||||
}
|
||||
if (!canMatchLineFeed(re)) {
|
||||
return searchSingleLineRegExpBackward(re, origin, end, document);
|
||||
}
|
||||
|
||||
return searchNaiveForward(re, origin, end, document);
|
||||
}
|
||||
|
||||
return searchNaiveBackward(re, origin, end, document);
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches forward for a pattern starting at the given position.
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* ```ts
|
||||
* const [p1, [t1]] = searchForward(/\w/, new vscode.Position(0, 0))!;
|
||||
*
|
||||
* assert.deepStrictEqual(p1, new vscode.Position(0, 0));
|
||||
* assert.strictEqual(t1, "a");
|
||||
*
|
||||
* const [p2, [t2]] = searchForward(/\w/, new vscode.Position(0, 1))!;
|
||||
*
|
||||
* assert.deepStrictEqual(p2, new vscode.Position(0, 1));
|
||||
* assert.strictEqual(t2, "b");
|
||||
*
|
||||
* const [p3, [t3]] = searchForward(/\w+/, new vscode.Position(0, 1))!;
|
||||
*
|
||||
* assert.deepStrictEqual(p3, new vscode.Position(0, 1));
|
||||
* assert.strictEqual(t3, "bc");
|
||||
*
|
||||
* assert.strictEqual(
|
||||
* searchForward(/\w/, new vscode.Position(0, 3)),
|
||||
* undefined,
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* With:
|
||||
* ```
|
||||
* abc
|
||||
* ```
|
||||
*/
|
||||
export function searchForward(re: RegExp, origin: vscode.Position, end?: vscode.Position): search.Result {
|
||||
const document = Context.current.document;
|
||||
|
||||
end ??= Positions.last(document);
|
||||
|
||||
const searchStart = document.offsetAt(origin),
|
||||
searchEnd = document.offsetAt(end),
|
||||
possibleSearchLength = searchEnd - searchStart;
|
||||
|
||||
if (possibleSearchLength < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (possibleSearchLength > 2_000) {
|
||||
const staticMatches = matchesStaticStrings(re);
|
||||
|
||||
if (staticMatches !== undefined) {
|
||||
return searchOneOfForward(re, staticMatches, origin, end, document);
|
||||
}
|
||||
|
||||
if (!canMatchLineFeed(re)) {
|
||||
return searchSingleLineRegExpForward(re, origin, end, document);
|
||||
}
|
||||
}
|
||||
|
||||
return searchNaiveForward(re, origin, end, document);
|
||||
}
|
||||
|
||||
function maxLines(strings: readonly string[]) {
|
||||
|
@ -1,11 +1,11 @@
|
||||
import * as vscode from "vscode";
|
||||
|
||||
import { lineByLine } from "./move";
|
||||
import { lineByLineBackward, lineByLineForward } from "./move";
|
||||
import { Context } from "../context";
|
||||
import * as Positions from "../positions";
|
||||
|
||||
/**
|
||||
* Returns the range of lines matching the given `RegExp` before and after
|
||||
* Returns the range of lines matching the given {@link RegExp} before and after
|
||||
* the given origin position.
|
||||
*/
|
||||
export function matchingLines(
|
||||
@ -13,42 +13,40 @@ export function matchingLines(
|
||||
origin: number | vscode.Position,
|
||||
document = Context.current.document,
|
||||
) {
|
||||
const start = matchingLines.backward(re, origin, document),
|
||||
end = matchingLines.forward(re, origin, document);
|
||||
const start = matchingLinesBackward(re, origin, document),
|
||||
end = matchingLinesForward(re, origin, document);
|
||||
|
||||
return new vscode.Range(start, end);
|
||||
}
|
||||
|
||||
export namespace matchingLines {
|
||||
/**
|
||||
* Returns the position of the first line matching the given `RegExp`,
|
||||
* starting at the `origin` line (included).
|
||||
*/
|
||||
export function backward(
|
||||
re: RegExp,
|
||||
origin: number | vscode.Position,
|
||||
document = Context.current.document,
|
||||
) {
|
||||
return lineByLine.backward(
|
||||
(text, position) => re.test(text) ? position : undefined,
|
||||
typeof origin === "number" ? Positions.lineStart(origin) : origin,
|
||||
document,
|
||||
) ?? Positions.zero;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the position of the last line matching the given `RegExp`, starting
|
||||
* at the `origin` line (included).
|
||||
*/
|
||||
export function forward(
|
||||
re: RegExp,
|
||||
origin: number | vscode.Position,
|
||||
document = Context.current.document,
|
||||
) {
|
||||
return lineByLine.forward(
|
||||
(text, position) => re.test(text) ? position : undefined,
|
||||
typeof origin === "number" ? Positions.lineStart(origin) : origin,
|
||||
document,
|
||||
) ?? Positions.lineStart(document.lineCount - 1);
|
||||
}
|
||||
/**
|
||||
* Returns the position of the first line matching the given {@link RegExp},
|
||||
* starting at the `origin` line (included).
|
||||
*/
|
||||
export function matchingLinesBackward(
|
||||
re: RegExp,
|
||||
origin: number | vscode.Position,
|
||||
document = Context.current.document,
|
||||
) {
|
||||
return lineByLineBackward(
|
||||
(text, position) => re.test(text) ? position : undefined,
|
||||
typeof origin === "number" ? Positions.lineStart(origin) : origin,
|
||||
document,
|
||||
) ?? Positions.zero;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the position of the last line matching the given {@link RegExp},
|
||||
* starting at the `origin` line (included).
|
||||
*/
|
||||
export function matchingLinesForward(
|
||||
re: RegExp,
|
||||
origin: number | vscode.Position,
|
||||
document = Context.current.document,
|
||||
) {
|
||||
return lineByLineForward(
|
||||
(text, position) => re.test(text) ? position : undefined,
|
||||
typeof origin === "number" ? Positions.lineStart(origin) : origin,
|
||||
document,
|
||||
) ?? Positions.lineStart(document.lineCount - 1);
|
||||
}
|
||||
|
@ -56,46 +56,40 @@ export function moveTo(
|
||||
}
|
||||
}
|
||||
|
||||
export namespace moveTo {
|
||||
/**
|
||||
* Same as `moveTo`, but also ensures that the result is excluded by
|
||||
* translating the resulting position by `input.length` when going backward.
|
||||
*
|
||||
* @see moveTo
|
||||
*/
|
||||
export function excluded(
|
||||
direction: Direction,
|
||||
string: string,
|
||||
origin: vscode.Position,
|
||||
document?: vscode.TextDocument,
|
||||
) {
|
||||
const result = moveTo(direction, string, origin, document);
|
||||
/**
|
||||
* Same as {@link moveTo}, but also ensures that the result is excluded by
|
||||
* translating the resulting position by `input.length` when going backward.
|
||||
*/
|
||||
export function moveToExcluded(
|
||||
direction: Direction,
|
||||
string: string,
|
||||
origin: vscode.Position,
|
||||
document?: vscode.TextDocument,
|
||||
) {
|
||||
const result = moveTo(direction, string, origin, document);
|
||||
|
||||
if (result !== undefined && direction === Direction.Backward) {
|
||||
return Positions.offset(result, string.length, document);
|
||||
}
|
||||
|
||||
return result;
|
||||
if (result !== undefined && direction === Direction.Backward) {
|
||||
return Positions.offset(result, string.length, document);
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as `moveTo`, but also ensures that the result is included by
|
||||
* translating the resulting position by `input.length` when going forward.
|
||||
*
|
||||
* @see moveTo
|
||||
*/
|
||||
export function included(
|
||||
direction: Direction,
|
||||
string: string,
|
||||
origin: vscode.Position,
|
||||
document?: vscode.TextDocument,
|
||||
) {
|
||||
const result = moveTo(direction, string, origin, document);
|
||||
|
||||
if (result !== undefined && direction === Direction.Forward) {
|
||||
return Positions.offset(result, string.length, document);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as {@link moveTo}, but also ensures that the result is included by
|
||||
* translating the resulting position by `input.length` when going forward.
|
||||
*/
|
||||
export function moveToIncluded(
|
||||
direction: Direction,
|
||||
string: string,
|
||||
origin: vscode.Position,
|
||||
document?: vscode.TextDocument,
|
||||
) {
|
||||
const result = moveTo(direction, string, origin, document);
|
||||
|
||||
if (result !== undefined && direction === Direction.Forward) {
|
||||
return Positions.offset(result, string.length, document);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
||||
import * as vscode from "vscode";
|
||||
|
||||
import { moveWhile } from "./move";
|
||||
import { moveWhileByCharCode, moveWhileByCharCodeBackward, moveWhileByCharCodeForward, moveWhileReachedDocumentEdge } from "./move";
|
||||
import { Context } from "../context";
|
||||
import { isEmpty as lineIsEmpty } from "../lines";
|
||||
import * as Positions from "../positions";
|
||||
@ -8,300 +8,330 @@ import { Direction } from "../types";
|
||||
import { CharSet, getCharSetFunction } from "../../utils/charset";
|
||||
import { CharCodes } from "../../utils/regexp";
|
||||
|
||||
export namespace Range {
|
||||
/**
|
||||
* A function that, given a position, returns the start of the object to which
|
||||
* the position belongs.
|
||||
*/
|
||||
export interface SeekStart {
|
||||
(position: vscode.Position, inner: boolean, document: vscode.TextDocument): vscode.Position;
|
||||
}
|
||||
|
||||
/**
|
||||
* A function that, given a position, returns the end of the object to which
|
||||
* the position belongs.
|
||||
*
|
||||
* If the whole object is being sought, the start position of the object will
|
||||
* also be given.
|
||||
*/
|
||||
export interface SeekEnd {
|
||||
(position: vscode.Position, inner: boolean,
|
||||
document: vscode.TextDocument, start?: vscode.Position): vscode.Position;
|
||||
}
|
||||
|
||||
/**
|
||||
* A function that, given a position, returns the range of the object to which
|
||||
* the position belongs.
|
||||
*/
|
||||
export interface Seek {
|
||||
(position: vscode.Position, inner: boolean, document: vscode.TextDocument): vscode.Selection;
|
||||
|
||||
readonly start: SeekStart;
|
||||
readonly end: SeekEnd;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the range of the argument at the given position.
|
||||
*/
|
||||
export function argument(
|
||||
position: vscode.Position,
|
||||
inner: boolean,
|
||||
document = Context.current.document,
|
||||
) {
|
||||
return new vscode.Selection(
|
||||
argument.start(position, inner, document),
|
||||
argument.end(position, inner, document),
|
||||
);
|
||||
}
|
||||
|
||||
export namespace argument {
|
||||
/**
|
||||
* Returns the start position of the argument at the given position.
|
||||
*/
|
||||
export function start(
|
||||
position: vscode.Position,
|
||||
inner: boolean,
|
||||
document = Context.current.document,
|
||||
) {
|
||||
return toArgumentEdge(position, inner, Direction.Backward, document);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the end position of the argument at the given position.
|
||||
*/
|
||||
export function end(
|
||||
position: vscode.Position,
|
||||
inner: boolean,
|
||||
document = Context.current.document,
|
||||
) {
|
||||
return toArgumentEdge(position, inner, Direction.Forward, document);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the range of lines with the same indent as the line at the given
|
||||
* position.
|
||||
*/
|
||||
export function indent(
|
||||
position: vscode.Position,
|
||||
inner: boolean,
|
||||
document = Context.current.document,
|
||||
) {
|
||||
// When selecting a whole indent object, scanning separately to start and
|
||||
// then to end will lead to wrong results like two different indentation
|
||||
// levels and skipping over blank lines more than needed. We can mitigate
|
||||
// this by finding the start first and then scan from there to find the end
|
||||
// of indent block.
|
||||
const start = indent.start(position, inner, document),
|
||||
end = indent.end(start, inner, document);
|
||||
|
||||
return new vscode.Selection(start, end);
|
||||
}
|
||||
|
||||
export namespace indent {
|
||||
/**
|
||||
* Returns the start position of the indent block at the given position.
|
||||
*/
|
||||
export function start(
|
||||
position: vscode.Position,
|
||||
inner: boolean,
|
||||
document = Context.current.document,
|
||||
) {
|
||||
return toIndentEdge(position, inner, Direction.Backward, document);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the end position of the indent block at the given position.
|
||||
*/
|
||||
export function end(
|
||||
position: vscode.Position,
|
||||
inner: boolean,
|
||||
document = Context.current.document,
|
||||
start?: vscode.Position,
|
||||
) {
|
||||
return toIndentEdge(start ?? position, inner, Direction.Forward, document);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the range of the paragraph that wraps the given position.
|
||||
*/
|
||||
export function paragraph(
|
||||
position: vscode.Position,
|
||||
inner: boolean,
|
||||
document = Context.current.document,
|
||||
) {
|
||||
let start: vscode.Position;
|
||||
|
||||
if (position.line + 1 < document.lineCount
|
||||
&& lineIsEmpty(position.line, document) && !lineIsEmpty(position.line + 1, document)) {
|
||||
// Special case: if current line is empty, check next line and select
|
||||
// the NEXT paragraph if next line is not empty.
|
||||
start = Positions.lineStart(position.line + 1);
|
||||
} else {
|
||||
start = toParagraphStart(position, document);
|
||||
}
|
||||
|
||||
const end = toParagraphEnd(start, inner, document);
|
||||
|
||||
return new vscode.Selection(start, end);
|
||||
}
|
||||
|
||||
export namespace paragraph {
|
||||
/**
|
||||
* Returns the start position of the paragraph that wraps the given
|
||||
* position.
|
||||
*/
|
||||
export function start(
|
||||
position: vscode.Position,
|
||||
_inner: boolean,
|
||||
document = Context.current.document,
|
||||
) {
|
||||
if (position.line > 0 && lineIsEmpty(position.line, document)) {
|
||||
position = Positions.lineStart(position.line - 1); // Re-anchor to the previous line.
|
||||
}
|
||||
|
||||
return toParagraphStart(position, document);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the end position of the paragraph that wraps given position.
|
||||
*/
|
||||
export function end(
|
||||
position: vscode.Position,
|
||||
inner: boolean,
|
||||
document = Context.current.document,
|
||||
start?: vscode.Position,
|
||||
) {
|
||||
if (start !== undefined) {
|
||||
// It's much easier to check from start.
|
||||
position = start;
|
||||
}
|
||||
|
||||
return toParagraphEnd(position, inner, document);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the range of the sentence that wraps the given position.
|
||||
*/
|
||||
export function sentence(
|
||||
position: vscode.Position,
|
||||
inner: boolean,
|
||||
document = Context.current.document,
|
||||
) {
|
||||
const beforeBlank = toBeforeBlank(position, document, /* canSkipToPrevious= */ false),
|
||||
start = toSentenceStart(beforeBlank, document),
|
||||
end = sentence.end(start, inner, document);
|
||||
|
||||
return new vscode.Selection(start, end);
|
||||
}
|
||||
|
||||
export namespace sentence {
|
||||
/**
|
||||
* Returns the start position of the sentence that wraps the given position.
|
||||
*/
|
||||
export function start(
|
||||
position: vscode.Position,
|
||||
_inner: boolean,
|
||||
document = Context.current.document,
|
||||
) {
|
||||
// Special case to allow jumping to the previous sentence when position is
|
||||
// at current sentence start / leading blank chars.
|
||||
const beforeBlank = toBeforeBlank(position, document, /* canSkipToPrevious= */ true);
|
||||
|
||||
return toSentenceStart(beforeBlank, document);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the end position of the sentence that wraps given position.
|
||||
*/
|
||||
export function end(
|
||||
position: vscode.Position,
|
||||
inner: boolean,
|
||||
document = Context.current.document,
|
||||
start?: vscode.Position,
|
||||
) {
|
||||
if (start !== undefined) {
|
||||
// It is impossible to determine if active is at leading or trailing or
|
||||
// in-sentence blank characters by just looking ahead. Therefore, we
|
||||
// search from the sentence start, which may be slightly less efficient
|
||||
// but always accurate.
|
||||
position = start;
|
||||
}
|
||||
|
||||
if (lineIsEmpty(position.line, document)) {
|
||||
// We're on an empty line which does not belong to last sentence or this
|
||||
// sentence. If next line is also empty, we should just stay here.
|
||||
// However, start scanning from the next line if it is not empty.
|
||||
if (position.line + 1 >= document.lineCount || lineIsEmpty(position.line + 1, document)) {
|
||||
return position;
|
||||
} else {
|
||||
position = Positions.lineStart(position.line + 1);
|
||||
}
|
||||
}
|
||||
|
||||
const isBlank = getCharSetFunction(CharSet.Blank, document);
|
||||
|
||||
let hadLf = false;
|
||||
const innerEnd = moveWhile.byCharCode.forward(
|
||||
(charCode) => {
|
||||
if (charCode === CharCodes.LF) {
|
||||
if (hadLf) {
|
||||
return false;
|
||||
}
|
||||
|
||||
hadLf = true;
|
||||
} else {
|
||||
hadLf = false;
|
||||
|
||||
if (punctCharCodes.indexOf(charCode) >= 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
position,
|
||||
document,
|
||||
);
|
||||
|
||||
if (moveWhile.reachedDocumentEdge) {
|
||||
return innerEnd;
|
||||
}
|
||||
|
||||
// If a sentence ends with two LFs in a row, then the first LF is part of
|
||||
// the inner & outer sentence while the second LF should be excluded.
|
||||
if (hadLf) {
|
||||
if (inner) {
|
||||
return Positions.previous(innerEnd, document)!;
|
||||
}
|
||||
|
||||
return innerEnd;
|
||||
}
|
||||
|
||||
if (inner) {
|
||||
return innerEnd;
|
||||
}
|
||||
|
||||
// If a sentence ends with punct char, then any blank characters after it
|
||||
// but BEFORE any line breaks belongs to the outer sentence.
|
||||
let col = innerEnd.character + 1;
|
||||
const text = document.lineAt(innerEnd.line).text;
|
||||
|
||||
while (col < text.length && isBlank(text.charCodeAt(col))) {
|
||||
col++;
|
||||
}
|
||||
|
||||
if (col >= text.length) {
|
||||
return Positions.lineBreak(innerEnd.line, document);
|
||||
}
|
||||
|
||||
return new vscode.Position(innerEnd.line, col);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* A function that, given a position, returns the start of the object to which
|
||||
* the position belongs.
|
||||
*/
|
||||
export interface SeekStart {
|
||||
(position: vscode.Position, inner: boolean, document: vscode.TextDocument): vscode.Position;
|
||||
}
|
||||
|
||||
/**
|
||||
* A function that, given a position, returns the end of the object to which
|
||||
* the position belongs.
|
||||
*
|
||||
* If the whole object is being sought, the start position of the object will
|
||||
* also be given.
|
||||
*/
|
||||
export interface SeekEnd {
|
||||
(position: vscode.Position, inner: boolean,
|
||||
document: vscode.TextDocument, start?: vscode.Position): vscode.Position;
|
||||
}
|
||||
|
||||
/**
|
||||
* A function that, given a position, returns the range of the object to which
|
||||
* the position belongs.
|
||||
*/
|
||||
export interface Seek {
|
||||
(position: vscode.Position, inner: boolean, document: vscode.TextDocument): vscode.Selection;
|
||||
|
||||
readonly start: SeekStart;
|
||||
readonly end: SeekEnd;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the range of the argument at the given position.
|
||||
*/
|
||||
export function argument(
|
||||
position: vscode.Position,
|
||||
inner: boolean,
|
||||
document = Context.current.document,
|
||||
) {
|
||||
return new vscode.Selection(
|
||||
argumentStart(position, inner, document),
|
||||
argumentEnd(position, inner, document),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the start position of the argument at the given position.
|
||||
*/
|
||||
export function argumentStart(
|
||||
position: vscode.Position,
|
||||
inner: boolean,
|
||||
document = Context.current.document,
|
||||
) {
|
||||
return toArgumentEdge(position, inner, Direction.Backward, document);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the end position of the argument at the given position.
|
||||
*/
|
||||
export function argumentEnd(
|
||||
position: vscode.Position,
|
||||
inner: boolean,
|
||||
document = Context.current.document,
|
||||
) {
|
||||
return toArgumentEdge(position, inner, Direction.Forward, document);
|
||||
}
|
||||
|
||||
export declare namespace argument {
|
||||
export const start: typeof argumentStart;
|
||||
export const end: typeof argumentEnd;
|
||||
}
|
||||
|
||||
Object.defineProperties(argument, {
|
||||
start: { value: argumentStart },
|
||||
end: { value: argumentEnd },
|
||||
});
|
||||
|
||||
/**
|
||||
* Returns the range of lines with the same indent as the line at the given
|
||||
* position.
|
||||
*/
|
||||
export function indent(
|
||||
position: vscode.Position,
|
||||
inner: boolean,
|
||||
document = Context.current.document,
|
||||
) {
|
||||
// When selecting a whole indent object, scanning separately to start and
|
||||
// then to end will lead to wrong results like two different indentation
|
||||
// levels and skipping over blank lines more than needed. We can mitigate
|
||||
// this by finding the start first and then scan from there to find the end
|
||||
// of indent block.
|
||||
const start = indentStart(position, inner, document),
|
||||
end = indentEnd(start, inner, document);
|
||||
|
||||
return new vscode.Selection(start, end);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the start position of the indent block at the given position.
|
||||
*/
|
||||
export function indentStart(
|
||||
position: vscode.Position,
|
||||
inner: boolean,
|
||||
document = Context.current.document,
|
||||
) {
|
||||
return toIndentEdge(position, inner, Direction.Backward, document);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the end position of the indent block at the given position.
|
||||
*/
|
||||
export function indentEnd(
|
||||
position: vscode.Position,
|
||||
inner: boolean,
|
||||
document = Context.current.document,
|
||||
start?: vscode.Position,
|
||||
) {
|
||||
return toIndentEdge(start ?? position, inner, Direction.Forward, document);
|
||||
}
|
||||
|
||||
export declare namespace indent {
|
||||
export const start: typeof indentStart;
|
||||
export const end: typeof indentEnd;
|
||||
}
|
||||
|
||||
Object.defineProperties(indent, {
|
||||
start: { value: indentStart },
|
||||
end: { value: indentEnd },
|
||||
});
|
||||
|
||||
/**
|
||||
* Returns the range of the paragraph that wraps the given position.
|
||||
*/
|
||||
export function paragraph(
|
||||
position: vscode.Position,
|
||||
inner: boolean,
|
||||
document = Context.current.document,
|
||||
) {
|
||||
let start: vscode.Position;
|
||||
|
||||
if (position.line + 1 < document.lineCount
|
||||
&& lineIsEmpty(position.line, document) && !lineIsEmpty(position.line + 1, document)) {
|
||||
// Special case: if current line is empty, check next line and select
|
||||
// the NEXT paragraph if next line is not empty.
|
||||
start = Positions.lineStart(position.line + 1);
|
||||
} else {
|
||||
start = toParagraphStart(position, document);
|
||||
}
|
||||
|
||||
const end = toParagraphEnd(start, inner, document);
|
||||
|
||||
return new vscode.Selection(start, end);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the start position of the paragraph that wraps the given
|
||||
* position.
|
||||
*/
|
||||
export function paragraphStart(
|
||||
position: vscode.Position,
|
||||
_inner: boolean,
|
||||
document = Context.current.document,
|
||||
) {
|
||||
if (position.line > 0 && lineIsEmpty(position.line, document)) {
|
||||
position = Positions.lineStart(position.line - 1); // Re-anchor to the previous line.
|
||||
}
|
||||
|
||||
return toParagraphStart(position, document);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the end position of the paragraph that wraps given position.
|
||||
*/
|
||||
export function paragraphEnd(
|
||||
position: vscode.Position,
|
||||
inner: boolean,
|
||||
document = Context.current.document,
|
||||
start?: vscode.Position,
|
||||
) {
|
||||
if (start !== undefined) {
|
||||
// It's much easier to check from start.
|
||||
position = start;
|
||||
}
|
||||
|
||||
return toParagraphEnd(position, inner, document);
|
||||
}
|
||||
|
||||
export declare namespace paragraph {
|
||||
export const start: typeof paragraphStart;
|
||||
export const end: typeof paragraphEnd;
|
||||
}
|
||||
|
||||
Object.defineProperties(paragraph, {
|
||||
start: { value: paragraphStart },
|
||||
end: { value: paragraphEnd },
|
||||
});
|
||||
|
||||
/**
|
||||
* Returns the range of the sentence that wraps the given position.
|
||||
*/
|
||||
export function sentence(
|
||||
position: vscode.Position,
|
||||
inner: boolean,
|
||||
document = Context.current.document,
|
||||
) {
|
||||
const beforeBlank = toBeforeBlank(position, document, /* canSkipToPrevious= */ false),
|
||||
start = toSentenceStart(beforeBlank, document),
|
||||
end = sentenceEnd(start, inner, document);
|
||||
|
||||
return new vscode.Selection(start, end);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the start position of the sentence that wraps the given position.
|
||||
*/
|
||||
export function sentenceStart(
|
||||
position: vscode.Position,
|
||||
_inner: boolean,
|
||||
document = Context.current.document,
|
||||
) {
|
||||
// Special case to allow jumping to the previous sentence when position is
|
||||
// at current sentence start / leading blank chars.
|
||||
const beforeBlank = toBeforeBlank(position, document, /* canSkipToPrevious= */ true);
|
||||
|
||||
return toSentenceStart(beforeBlank, document);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the end position of the sentence that wraps given position.
|
||||
*/
|
||||
export function sentenceEnd(
|
||||
position: vscode.Position,
|
||||
inner: boolean,
|
||||
document = Context.current.document,
|
||||
start?: vscode.Position,
|
||||
) {
|
||||
if (start !== undefined) {
|
||||
// It is impossible to determine if active is at leading or trailing or
|
||||
// in-sentence blank characters by just looking ahead. Therefore, we
|
||||
// search from the sentence start, which may be slightly less efficient
|
||||
// but always accurate.
|
||||
position = start;
|
||||
}
|
||||
|
||||
if (lineIsEmpty(position.line, document)) {
|
||||
// We're on an empty line which does not belong to last sentence or this
|
||||
// sentence. If next line is also empty, we should just stay here.
|
||||
// However, start scanning from the next line if it is not empty.
|
||||
if (position.line + 1 >= document.lineCount || lineIsEmpty(position.line + 1, document)) {
|
||||
return position;
|
||||
} else {
|
||||
position = Positions.lineStart(position.line + 1);
|
||||
}
|
||||
}
|
||||
|
||||
const isBlank = getCharSetFunction(CharSet.Blank, document);
|
||||
|
||||
let hadLf = false;
|
||||
const innerEnd = moveWhileByCharCodeForward(
|
||||
(charCode) => {
|
||||
if (charCode === CharCodes.LF) {
|
||||
if (hadLf) {
|
||||
return false;
|
||||
}
|
||||
|
||||
hadLf = true;
|
||||
} else {
|
||||
hadLf = false;
|
||||
|
||||
if (punctCharCodes.indexOf(charCode) >= 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
position,
|
||||
document,
|
||||
);
|
||||
|
||||
if (moveWhileReachedDocumentEdge()) {
|
||||
return innerEnd;
|
||||
}
|
||||
|
||||
// If a sentence ends with two LFs in a row, then the first LF is part of
|
||||
// the inner & outer sentence while the second LF should be excluded.
|
||||
if (hadLf) {
|
||||
if (inner) {
|
||||
return Positions.previous(innerEnd, document)!;
|
||||
}
|
||||
|
||||
return innerEnd;
|
||||
}
|
||||
|
||||
if (inner) {
|
||||
return innerEnd;
|
||||
}
|
||||
|
||||
// If a sentence ends with punct char, then any blank characters after it
|
||||
// but BEFORE any line breaks belongs to the outer sentence.
|
||||
let col = innerEnd.character + 1;
|
||||
const text = document.lineAt(innerEnd.line).text;
|
||||
|
||||
while (col < text.length && isBlank(text.charCodeAt(col))) {
|
||||
col++;
|
||||
}
|
||||
|
||||
if (col >= text.length) {
|
||||
return Positions.lineBreak(innerEnd.line, document);
|
||||
}
|
||||
|
||||
return new vscode.Position(innerEnd.line, col);
|
||||
}
|
||||
|
||||
export declare namespace sentence {
|
||||
export const start: typeof sentenceStart;
|
||||
export const end: typeof sentenceEnd;
|
||||
}
|
||||
|
||||
Object.defineProperties(sentence, {
|
||||
start: { value: sentenceStart },
|
||||
end: { value: sentenceEnd },
|
||||
});
|
||||
|
||||
const punctCharCodes = new Uint32Array(Array.from(".!?¡§¶¿;՞。", (ch) => ch.charCodeAt(0)));
|
||||
// ^
|
||||
// I bet that's the first time you see a Greek question mark used as an actual
|
||||
@ -318,7 +348,7 @@ function toArgumentEdge(
|
||||
let bbalance = 0,
|
||||
pbalance = 0;
|
||||
|
||||
const afterSkip = moveWhile.byCharCode(
|
||||
const afterSkip = moveWhileByCharCode(
|
||||
direction,
|
||||
(charCode) => {
|
||||
if (charCode === paren && pbalance === 0 && bbalance === 0) {
|
||||
@ -344,7 +374,7 @@ function toArgumentEdge(
|
||||
|
||||
let end: vscode.Position;
|
||||
|
||||
if (moveWhile.reachedDocumentEdge) {
|
||||
if (moveWhileReachedDocumentEdge()) {
|
||||
end = afterSkip;
|
||||
} else {
|
||||
const charCode = document.lineAt(afterSkip.line).text.charCodeAt(afterSkip.character);
|
||||
@ -370,7 +400,7 @@ function toArgumentEdge(
|
||||
const isBlank = getCharSetFunction(CharSet.Blank, document);
|
||||
|
||||
// Exclude any surrounding whitespaces.
|
||||
return moveWhile.byCharCode(-direction, isBlank, end, document);
|
||||
return moveWhileByCharCode(-direction, isBlank, end, document);
|
||||
}
|
||||
|
||||
function toIndentEdge(
|
||||
@ -496,7 +526,7 @@ function toBeforeBlank(
|
||||
let jumpedOverBlankLine = false,
|
||||
hadLf = true;
|
||||
|
||||
const beforeBlank = moveWhile.byCharCode.backward(
|
||||
const beforeBlank = moveWhileByCharCodeBackward(
|
||||
(charCode) => {
|
||||
if (charCode === CharCodes.LF) {
|
||||
if (hadLf) {
|
||||
@ -518,7 +548,7 @@ function toBeforeBlank(
|
||||
document,
|
||||
);
|
||||
|
||||
if (moveWhile.reachedDocumentEdge) {
|
||||
if (moveWhileReachedDocumentEdge()) {
|
||||
return position;
|
||||
}
|
||||
|
||||
@ -576,7 +606,7 @@ function toSentenceStart(
|
||||
let first = true,
|
||||
hadLf = false;
|
||||
|
||||
const afterSkip = moveWhile.byCharCode.backward(
|
||||
const afterSkip = moveWhileByCharCodeBackward(
|
||||
(charCode) => {
|
||||
if (charCode === CharCodes.LF) {
|
||||
first = false;
|
||||
@ -610,10 +640,10 @@ function toSentenceStart(
|
||||
|
||||
// If we hit two LFs or document start, the current sentence starts at the
|
||||
// first non-blank character after that.
|
||||
if (hadLf || moveWhile.reachedDocumentEdge) {
|
||||
const start = moveWhile.byCharCode.forward(isBlank, afterSkip, document);
|
||||
if (hadLf || moveWhileReachedDocumentEdge()) {
|
||||
const start = moveWhileByCharCodeForward(isBlank, afterSkip, document);
|
||||
|
||||
if (moveWhile.reachedDocumentEdge) {
|
||||
if (moveWhileReachedDocumentEdge()) {
|
||||
return Positions.zero;
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import * as vscode from "vscode";
|
||||
|
||||
import { skipEmptyLines } from "./move";
|
||||
import { skipEmptyLines, skipEmptyLinesReachedDocumentEdge } from "./move";
|
||||
import { Context } from "../context";
|
||||
import { Direction, SelectionBehavior } from "../types";
|
||||
import { CharSet, getCharSetFunction } from "../../utils/charset";
|
||||
@ -53,7 +53,7 @@ export function wordBoundary(
|
||||
if (isAtLineBoundary) {
|
||||
const afterEmptyLines = skipEmptyLines(direction, active.line + direction, document);
|
||||
|
||||
if (skipEmptyLines.reachedDocumentEdge) {
|
||||
if (skipEmptyLinesReachedDocumentEdge()) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,7 @@ import { NotASelectionError } from "./errors";
|
||||
import * as Positions from "./positions";
|
||||
import { Direction, SelectionBehavior, Shift } from "./types";
|
||||
import { execRange, splitRange } from "../utils/regexp";
|
||||
import { TrackedSelection } from "../utils/tracked-selection";
|
||||
import * as TrackedSelection from "../utils/tracked-selection";
|
||||
|
||||
export { fromCharacterMode, toCharacterMode };
|
||||
|
||||
@ -113,107 +113,109 @@ export function filter(
|
||||
predicate: filter.Predicate<boolean> | filter.Predicate<Thenable<boolean>>,
|
||||
selections?: readonly vscode.Selection[],
|
||||
) {
|
||||
return filter.byIndex(
|
||||
return filterByIndex(
|
||||
(i, selection, document) => predicate(document.getText(selection), selection, i) as any,
|
||||
selections,
|
||||
) as any;
|
||||
}
|
||||
|
||||
export namespace filter {
|
||||
export declare namespace filter {
|
||||
/**
|
||||
* A predicate passed to `filter`.
|
||||
* A predicate passed to {@link filter}.
|
||||
*/
|
||||
export interface Predicate<T extends boolean | Thenable<boolean>> {
|
||||
(text: string, selection: vscode.Selection, index: number): T;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A predicate passed to `filter.byIndex`.
|
||||
*/
|
||||
export interface ByIndexPredicate<T extends boolean | Thenable<boolean>> {
|
||||
(index: number, selection: vscode.Selection, document: vscode.TextDocument): T;
|
||||
/**
|
||||
* Removes selections that do not match the given predicate.
|
||||
*
|
||||
* @param selections The `vscode.Selection` array to filter from, or
|
||||
* `undefined` to filter the selections of the active text editor.
|
||||
*/
|
||||
export function filterByIndex(
|
||||
predicate: filterByIndex.Predicate<boolean>,
|
||||
selections?: readonly vscode.Selection[],
|
||||
): vscode.Selection[];
|
||||
|
||||
/**
|
||||
* Removes selections that do not match the given async predicate.
|
||||
*
|
||||
* @param selections The `vscode.Selection` array to filter from, or
|
||||
* `undefined` to filter the selections of the active text editor.
|
||||
*/
|
||||
export function filterByIndex(
|
||||
predicate: filterByIndex.Predicate<Thenable<boolean>>,
|
||||
selections?: readonly vscode.Selection[],
|
||||
): Thenable<vscode.Selection[]>;
|
||||
|
||||
export function filterByIndex(
|
||||
predicate: filterByIndex.Predicate<boolean> | filterByIndex.Predicate<Thenable<boolean>>,
|
||||
selections?: readonly vscode.Selection[],
|
||||
) {
|
||||
const context = Context.current,
|
||||
document = context.document;
|
||||
|
||||
if (selections === undefined) {
|
||||
selections = context.selections;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes selections that do not match the given predicate.
|
||||
*
|
||||
* @param selections The `vscode.Selection` array to filter from, or
|
||||
* `undefined` to filter the selections of the active text editor.
|
||||
*/
|
||||
export function byIndex(
|
||||
predicate: ByIndexPredicate<boolean>,
|
||||
selections?: readonly vscode.Selection[],
|
||||
): vscode.Selection[];
|
||||
const firstSelection = selections[0],
|
||||
firstResult = predicate(0, firstSelection, document);
|
||||
|
||||
/**
|
||||
* Removes selections that do not match the given async predicate.
|
||||
*
|
||||
* @param selections The `vscode.Selection` array to filter from, or
|
||||
* `undefined` to filter the selections of the active text editor.
|
||||
*/
|
||||
export function byIndex(
|
||||
predicate: ByIndexPredicate<Thenable<boolean>>,
|
||||
selections?: readonly vscode.Selection[],
|
||||
): Thenable<vscode.Selection[]>;
|
||||
|
||||
export function byIndex(
|
||||
predicate: ByIndexPredicate<boolean> | ByIndexPredicate<Thenable<boolean>>,
|
||||
selections?: readonly vscode.Selection[],
|
||||
) {
|
||||
const context = Context.current,
|
||||
document = context.document;
|
||||
|
||||
if (selections === undefined) {
|
||||
selections = context.selections;
|
||||
if (typeof firstResult === "boolean") {
|
||||
if (selections.length === 1) {
|
||||
return firstResult ? [firstResult] : [];
|
||||
}
|
||||
|
||||
const firstSelection = selections[0],
|
||||
firstResult = predicate(0, firstSelection, document);
|
||||
const resultingSelections = firstResult ? [firstSelection] : [];
|
||||
|
||||
if (typeof firstResult === "boolean") {
|
||||
if (selections.length === 1) {
|
||||
return firstResult ? [firstResult] : [];
|
||||
for (let i = 1; i < selections.length; i++) {
|
||||
const selection = selections[i];
|
||||
|
||||
if (predicate(i, selection, document) as boolean) {
|
||||
resultingSelections.push(selection);
|
||||
}
|
||||
}
|
||||
|
||||
const resultingSelections = firstResult ? [firstSelection] : [];
|
||||
return resultingSelections;
|
||||
} else {
|
||||
if (selections.length === 1) {
|
||||
return context.then(firstResult, (value) => value ? [firstSelection] : []);
|
||||
}
|
||||
|
||||
for (let i = 1; i < selections.length; i++) {
|
||||
const selection = selections[i];
|
||||
const promises = [firstResult];
|
||||
|
||||
if (predicate(i, selection, document) as boolean) {
|
||||
resultingSelections.push(selection);
|
||||
for (let i = 1; i < selections.length; i++) {
|
||||
const selection = selections[i];
|
||||
|
||||
promises.push(predicate(i, selection, document) as Thenable<boolean>);
|
||||
}
|
||||
|
||||
const savedSelections = selections.slice(); // In case the original
|
||||
// selections are mutated.
|
||||
|
||||
return context.then(Promise.all(promises), (results) => {
|
||||
const resultingSelections = [];
|
||||
|
||||
for (let i = 0; i < results.length; i++) {
|
||||
if (results[i]) {
|
||||
resultingSelections.push(savedSelections[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return resultingSelections;
|
||||
} else {
|
||||
if (selections.length === 1) {
|
||||
return context.then(firstResult, (value) => value ? [firstSelection] : []);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const promises = [firstResult];
|
||||
|
||||
for (let i = 1; i < selections.length; i++) {
|
||||
const selection = selections[i];
|
||||
|
||||
promises.push(predicate(i, selection, document) as Thenable<boolean>);
|
||||
}
|
||||
|
||||
const savedSelections = selections.slice(); // In case the original
|
||||
// selections are mutated.
|
||||
|
||||
return context.then(Promise.all(promises), (results) => {
|
||||
const resultingSelections = [];
|
||||
|
||||
for (let i = 0; i < results.length; i++) {
|
||||
if (results[i]) {
|
||||
resultingSelections.push(savedSelections[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return resultingSelections;
|
||||
});
|
||||
}
|
||||
export declare namespace filterByIndex {
|
||||
/**
|
||||
* A predicate passed to {@link filterByIndex}.
|
||||
*/
|
||||
export interface Predicate<T extends boolean | Thenable<boolean>> {
|
||||
(index: number, selection: vscode.Selection, document: vscode.TextDocument): T;
|
||||
}
|
||||
}
|
||||
|
||||
@ -277,108 +279,110 @@ export function map<T>(
|
||||
f: map.Mapper<T | undefined> | map.Mapper<Thenable<T | undefined>>,
|
||||
selections?: readonly vscode.Selection[],
|
||||
) {
|
||||
return map.byIndex(
|
||||
return mapByIndex(
|
||||
(i, selection, document) => f(document.getText(selection), selection, i),
|
||||
selections,
|
||||
) as any;
|
||||
}
|
||||
|
||||
export namespace map {
|
||||
export declare namespace map {
|
||||
/**
|
||||
* A mapper function passed to `map`.
|
||||
* A mapper function passed to {@link map}.
|
||||
*/
|
||||
export interface Mapper<T> {
|
||||
(text: string, selection: vscode.Selection, index: number): T;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A mapper function passed to `map.byIndex`.
|
||||
*/
|
||||
export interface ByIndexMapper<T> {
|
||||
(index: number, selection: vscode.Selection, document: vscode.TextDocument): T | undefined;
|
||||
/**
|
||||
* Applies a function to all the given selections, and returns the array of
|
||||
* all of its non-`undefined` results.
|
||||
*
|
||||
* @param selections The `vscode.Selection` array to map from, or `undefined`
|
||||
* to map the selections of the active text editor.
|
||||
*/
|
||||
export function mapByIndex<T>(
|
||||
f: mapByIndex.Mapper<T | undefined>,
|
||||
selections?: readonly vscode.Selection[],
|
||||
): T[];
|
||||
|
||||
/**
|
||||
* Applies an async function to all the given selections, and returns the
|
||||
* array of all of its non-`undefined` results.
|
||||
*
|
||||
* @param selections The `vscode.Selection` array to map from, or `undefined`
|
||||
* to map the selections of the active text editor.
|
||||
*/
|
||||
export function mapByIndex<T>(
|
||||
f: mapByIndex.Mapper<Thenable<T | undefined>>,
|
||||
selections?: readonly vscode.Selection[],
|
||||
): Thenable<T[]>;
|
||||
|
||||
export function mapByIndex<T>(
|
||||
f: mapByIndex.Mapper<T | undefined> | mapByIndex.Mapper<Thenable<T | undefined>>,
|
||||
selections?: readonly vscode.Selection[],
|
||||
) {
|
||||
const context = Context.current,
|
||||
document = context.document;
|
||||
|
||||
if (selections === undefined) {
|
||||
selections = context.selections;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a function to all the given selections, and returns the array of
|
||||
* all of its non-`undefined` results.
|
||||
*
|
||||
* @param selections The `vscode.Selection` array to map from, or `undefined`
|
||||
* to map the selections of the active text editor.
|
||||
*/
|
||||
export function byIndex<T>(
|
||||
f: ByIndexMapper<T | undefined>,
|
||||
selections?: readonly vscode.Selection[],
|
||||
): T[];
|
||||
const firstSelection = selections[0],
|
||||
firstResult = f(0, firstSelection, document);
|
||||
|
||||
/**
|
||||
* Applies an async function to all the given selections, and returns the
|
||||
* array of all of its non-`undefined` results.
|
||||
*
|
||||
* @param selections The `vscode.Selection` array to map from, or `undefined`
|
||||
* to map the selections of the active text editor.
|
||||
*/
|
||||
export function byIndex<T>(
|
||||
f: ByIndexMapper<Thenable<T | undefined>>,
|
||||
selections?: readonly vscode.Selection[],
|
||||
): Thenable<T[]>;
|
||||
if (firstResult === undefined || typeof (firstResult as Thenable<T>)?.then !== "function") {
|
||||
const results = firstResult !== undefined ? [firstResult as T] : [];
|
||||
|
||||
export function byIndex<T>(
|
||||
f: ByIndexMapper<T | undefined> | ByIndexMapper<Thenable<T | undefined>>,
|
||||
selections?: readonly vscode.Selection[],
|
||||
) {
|
||||
const context = Context.current,
|
||||
document = context.document;
|
||||
for (let i = 1; i < selections.length; i++) {
|
||||
const selection = selections[i],
|
||||
value = f(i, selection, document) as T | undefined;
|
||||
|
||||
if (selections === undefined) {
|
||||
selections = context.selections;
|
||||
if (value !== undefined) {
|
||||
results.push(value);
|
||||
}
|
||||
}
|
||||
|
||||
const firstSelection = selections[0],
|
||||
firstResult = f(0, firstSelection, document);
|
||||
|
||||
if (firstResult === undefined || typeof (firstResult as Thenable<T>)?.then !== "function") {
|
||||
const results = firstResult !== undefined ? [firstResult as T] : [];
|
||||
|
||||
for (let i = 1; i < selections.length; i++) {
|
||||
const selection = selections[i],
|
||||
value = f(i, selection, document) as T | undefined;
|
||||
|
||||
if (value !== undefined) {
|
||||
results.push(value);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
} else {
|
||||
if (selections.length === 1) {
|
||||
return context.then(firstResult as Thenable<T | undefined>, (result) => {
|
||||
return result !== undefined ? [result] : [];
|
||||
});
|
||||
}
|
||||
|
||||
const promises = [firstResult as Thenable<T | undefined>];
|
||||
|
||||
for (let i = 1; i < selections.length; i++) {
|
||||
const selection = selections[i],
|
||||
promise = f(i, selection, document) as Thenable<T | undefined>;
|
||||
|
||||
promises.push(promise);
|
||||
}
|
||||
|
||||
return context.then(Promise.all(promises), (results) => {
|
||||
const filteredResults = [];
|
||||
|
||||
for (let i = 0; i < results.length; i++) {
|
||||
const result = results[i];
|
||||
|
||||
if (result !== undefined) {
|
||||
filteredResults.push(result);
|
||||
}
|
||||
}
|
||||
|
||||
return filteredResults;
|
||||
return results;
|
||||
} else {
|
||||
if (selections.length === 1) {
|
||||
return context.then(firstResult as Thenable<T | undefined>, (result) => {
|
||||
return result !== undefined ? [result] : [];
|
||||
});
|
||||
}
|
||||
|
||||
const promises = [firstResult as Thenable<T | undefined>];
|
||||
|
||||
for (let i = 1; i < selections.length; i++) {
|
||||
const selection = selections[i],
|
||||
promise = f(i, selection, document) as Thenable<T | undefined>;
|
||||
|
||||
promises.push(promise);
|
||||
}
|
||||
|
||||
return context.then(Promise.all(promises), (results) => {
|
||||
const filteredResults = [];
|
||||
|
||||
for (let i = 0; i < results.length; i++) {
|
||||
const result = results[i];
|
||||
|
||||
if (result !== undefined) {
|
||||
filteredResults.push(result);
|
||||
}
|
||||
}
|
||||
|
||||
return filteredResults;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export declare namespace mapByIndex {
|
||||
/**
|
||||
* A mapper function passed to {@link mapByIndex}.
|
||||
*/
|
||||
export interface Mapper<T> {
|
||||
(index: number, selection: vscode.Selection, document: vscode.TextDocument): T | undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@ -509,80 +513,88 @@ function mapFallbackSelections(values: (vscode.Selection | readonly [vscode.Sele
|
||||
return [];
|
||||
}
|
||||
|
||||
export namespace update {
|
||||
/**
|
||||
* Sets the selections of the current editor after transforming them according
|
||||
* to the given function.
|
||||
*/
|
||||
export function byIndex(
|
||||
f: map.ByIndexMapper<vscode.Selection | undefined>,
|
||||
context?: Context,
|
||||
): vscode.Selection[];
|
||||
/**
|
||||
* Sets the selections of the current editor after transforming them according
|
||||
* to the given function.
|
||||
*/
|
||||
export function updateByIndex(
|
||||
f: mapByIndex.Mapper<vscode.Selection | undefined>,
|
||||
context?: Context,
|
||||
): vscode.Selection[];
|
||||
|
||||
/**
|
||||
* Sets the selections of the current editor after transforming them according
|
||||
* to the given async function.
|
||||
*/
|
||||
export function byIndex(
|
||||
f: map.ByIndexMapper<Thenable<vscode.Selection | undefined>>,
|
||||
context?: Context,
|
||||
): Thenable<vscode.Selection[]>;
|
||||
/**
|
||||
* Sets the selections of the current editor after transforming them according
|
||||
* to the given async function.
|
||||
*/
|
||||
export function updateByIndex(
|
||||
f: mapByIndex.Mapper<Thenable<vscode.Selection | undefined>>,
|
||||
context?: Context,
|
||||
): Thenable<vscode.Selection[]>;
|
||||
|
||||
export function byIndex(
|
||||
f: map.ByIndexMapper<vscode.Selection | undefined>
|
||||
| map.ByIndexMapper<Thenable<vscode.Selection | undefined>>,
|
||||
context?: Context,
|
||||
): any {
|
||||
const selections = map.byIndex(f as any, context?.selections);
|
||||
export function updateByIndex(
|
||||
f: mapByIndex.Mapper<vscode.Selection | undefined>
|
||||
| mapByIndex.Mapper<Thenable<vscode.Selection | undefined>>,
|
||||
context?: Context,
|
||||
): any {
|
||||
const selections = mapByIndex(f as any, context?.selections);
|
||||
|
||||
if (Array.isArray(selections)) {
|
||||
return set(selections, context);
|
||||
}
|
||||
|
||||
return (selections as Thenable<vscode.Selection[]>).then((xs) => set(xs, context));
|
||||
if (Array.isArray(selections)) {
|
||||
return set(selections, context);
|
||||
}
|
||||
|
||||
return (selections as Thenable<vscode.Selection[]>).then((xs) => set(xs, context));
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as {@link update}, but additionally lets `f` return a fallback
|
||||
* selection. If no selection remains after the end of the update, fallback
|
||||
* selections will be used instead.
|
||||
*/
|
||||
export function updateWithFallback<T extends updateWithFallback.SelectionOrFallback
|
||||
| Thenable<updateWithFallback.SelectionOrFallback>>(
|
||||
f: map.Mapper<T>,
|
||||
): T extends Thenable<updateWithFallback.SelectionOrFallback>
|
||||
? Thenable<vscode.Selection[]>
|
||||
: vscode.Selection[] {
|
||||
const selections = map(f as any);
|
||||
|
||||
if (Array.isArray(selections)) {
|
||||
return set(mapFallbackSelections(selections)) as any;
|
||||
}
|
||||
|
||||
return (selections as Thenable<(vscode.Selection | readonly [vscode.Selection])[]>)
|
||||
.then((values) => set(mapFallbackSelections(values))) as any;
|
||||
}
|
||||
|
||||
export declare namespace updateWithFallback {
|
||||
/**
|
||||
* A possible return value for a function passed to `withFallback`. An
|
||||
* array with a single selection corresponds to a fallback selection.
|
||||
* A possible return value for a function passed to
|
||||
* {@link updateWithFallback}. An array with a single selection corresponds to
|
||||
* a fallback selection.
|
||||
*/
|
||||
export type SelectionOrFallback = vscode.Selection | readonly [vscode.Selection] | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as `updateSelections`, but additionally lets `f` return a fallback
|
||||
* selection. If no selection remains after the end of the update, fallback
|
||||
* selections will be used instead.
|
||||
*/
|
||||
export function withFallback<T extends SelectionOrFallback | Thenable<SelectionOrFallback>>(
|
||||
f: map.Mapper<T>,
|
||||
): T extends Thenable<SelectionOrFallback> ? Thenable<vscode.Selection[]> : vscode.Selection[] {
|
||||
const selections = map(f as any);
|
||||
/**
|
||||
* Same as {@link updateWithFallback}, but does not pass the text of each
|
||||
* selection.
|
||||
*/
|
||||
export function updateWithFallbackByIndex<
|
||||
T extends updateWithFallback.SelectionOrFallback
|
||||
| Thenable<updateWithFallback.SelectionOrFallback>
|
||||
>(
|
||||
f: mapByIndex.Mapper<T>,
|
||||
): T extends Thenable<updateWithFallback.SelectionOrFallback>
|
||||
? Thenable<vscode.Selection[]>
|
||||
: vscode.Selection[] {
|
||||
const selections = mapByIndex(f as any);
|
||||
|
||||
if (Array.isArray(selections)) {
|
||||
return set(mapFallbackSelections(selections)) as any;
|
||||
}
|
||||
|
||||
return (selections as Thenable<(vscode.Selection | readonly [vscode.Selection])[]>)
|
||||
.then((values) => set(mapFallbackSelections(values))) as any;
|
||||
if (Array.isArray(selections)) {
|
||||
return set(mapFallbackSelections(selections)) as any;
|
||||
}
|
||||
|
||||
export namespace withFallback {
|
||||
/**
|
||||
* Same as `withFallback`, but does not pass the text of each selection.
|
||||
*/
|
||||
export function byIndex<T extends SelectionOrFallback | Thenable<SelectionOrFallback>>(
|
||||
f: map.ByIndexMapper<T>,
|
||||
): T extends Thenable<SelectionOrFallback> ? Thenable<vscode.Selection[]> : vscode.Selection[] {
|
||||
const selections = map.byIndex(f as any);
|
||||
|
||||
if (Array.isArray(selections)) {
|
||||
return set(mapFallbackSelections(selections)) as any;
|
||||
}
|
||||
|
||||
return (selections as Thenable<(vscode.Selection | readonly [vscode.Selection])[]>)
|
||||
.then((values) => set(mapFallbackSelections(values))) as any;
|
||||
}
|
||||
}
|
||||
return (selections as Thenable<(vscode.Selection | readonly [vscode.Selection])[]>)
|
||||
.then((values) => set(mapFallbackSelections(values))) as any;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,7 +1,7 @@
|
||||
import * as vscode from "vscode";
|
||||
|
||||
import type { Argument, InputOr, RegisterOr } from ".";
|
||||
import { insert as apiInsert, Context, deindentLines, edit, indentLines, joinLines, keypress, Positions, replace, Selections, Shift } from "../api";
|
||||
import { insert as apiInsert, Context, deindentLines, edit, indentLines, insertByIndex, insertByIndexWithFullLines, insertFlagsAtEdge, joinLines, keypress, Positions, replace, replaceByIndex, Selections, Shift } from "../api";
|
||||
import type { Register } from "../state/registers";
|
||||
import { LengthMismatchError } from "../utils/errors";
|
||||
|
||||
@ -65,8 +65,8 @@ export async function insert(
|
||||
|
||||
if (select || shift === Shift.Select) {
|
||||
const textToInsert = contents.join(""),
|
||||
insert = handleNewLine ? apiInsert.byIndex.withFullLines : apiInsert.byIndex,
|
||||
flags = apiInsert.flagsAtEdge(where) | apiInsert.Select;
|
||||
insert = handleNewLine ? insertByIndexWithFullLines : insertByIndex,
|
||||
flags = insertFlagsAtEdge(where) | apiInsert.Flags.Select;
|
||||
|
||||
const insertedRanges = await insert(flags, () => textToInsert, selections),
|
||||
allSelections = [] as vscode.Selection[],
|
||||
@ -98,7 +98,7 @@ export async function insert(
|
||||
}
|
||||
|
||||
if (where === undefined) {
|
||||
Selections.set(await replace.byIndex((i) => contents![i], selections));
|
||||
Selections.set(await replaceByIndex((i) => contents![i], selections));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -106,13 +106,13 @@ export async function insert(
|
||||
throw new Error(`"where" must be one of "active", "anchor", "start", "end", or undefined`);
|
||||
}
|
||||
|
||||
const keepOrExtend = shift === Shift.Extend ? apiInsert.Extend : apiInsert.Keep,
|
||||
flags = apiInsert.flagsAtEdge(where) | keepOrExtend;
|
||||
const keepOrExtend = shift === Shift.Extend ? apiInsert.Flags.Extend : apiInsert.Flags.Keep,
|
||||
flags = insertFlagsAtEdge(where) | keepOrExtend;
|
||||
|
||||
Selections.set(
|
||||
handleNewLine
|
||||
? await apiInsert.byIndex.withFullLines(flags, (i) => contents![i], selections)
|
||||
: await apiInsert.byIndex(flags, (i) => contents![i], selections),
|
||||
? await insertByIndexWithFullLines(flags, (i) => contents![i], selections)
|
||||
: await insertByIndex(flags, (i) => contents![i], selections),
|
||||
);
|
||||
}
|
||||
|
||||
@ -452,7 +452,7 @@ function insertLinesNativelyAndCopySelections(
|
||||
repetitions: number,
|
||||
command: "editor.action.insertLineAfter" | "editor.action.insertLineBefore",
|
||||
) {
|
||||
Selections.update.byIndex(prepareSelectionForLineInsertion);
|
||||
Selections.updateByIndex(prepareSelectionForLineInsertion);
|
||||
|
||||
if (repetitions === 1) {
|
||||
return vscode.commands.executeCommand(command);
|
||||
|
@ -2,7 +2,7 @@ import * as vscode from "vscode";
|
||||
|
||||
import type { Argument, CommandDescriptor, RegisterOr } from ".";
|
||||
import type { Context } from "../api";
|
||||
import { ActiveRecording, Entry, Recorder } from "../state/recorder";
|
||||
import { ActiveRecording, Cursor, Entry, Recorder } from "../state/recorder";
|
||||
import type { Register } from "../state/registers";
|
||||
import { ArgumentError } from "../utils/errors";
|
||||
|
||||
@ -105,8 +105,8 @@ export async function repeat_edit(_: Context, repetitions: number) {
|
||||
|
||||
const recorder = _.extension.recorder,
|
||||
cursor = recorder.cursorFromEnd();
|
||||
let startCursor: Recorder.Cursor | undefined,
|
||||
endCursor: Recorder.Cursor | undefined;
|
||||
let startCursor: Cursor | undefined,
|
||||
endCursor: Cursor | undefined;
|
||||
|
||||
for (;;) {
|
||||
if (cursor.is(Entry.ChangeTextEditorMode)) {
|
||||
|
@ -164,7 +164,7 @@ export class CommandDescriptor<Flags extends CommandDescriptor.Flags = CommandDe
|
||||
}
|
||||
}
|
||||
|
||||
export namespace CommandDescriptor {
|
||||
export declare namespace CommandDescriptor {
|
||||
/**
|
||||
* Flags describing the behavior of some commands.
|
||||
*/
|
||||
|
@ -1,7 +1,7 @@
|
||||
import * as vscode from "vscode";
|
||||
|
||||
import type { RegisterOr } from ".";
|
||||
import { Context, prompt, todo } from "../api";
|
||||
import { Context, prompt, promptMany, promptOne, todo } from "../api";
|
||||
import type { Register } from "../state/registers";
|
||||
|
||||
/**
|
||||
@ -16,7 +16,7 @@ export async function setup(_: Context, register: RegisterOr<"dquote", Register.
|
||||
await vscode.commands.executeCommand("workbench.action.openGlobalKeybindingsFile");
|
||||
await _.switchToDocument(_.extension.editors.active!.editor.document);
|
||||
|
||||
const action = await prompt.one([
|
||||
const action = await promptOne([
|
||||
["y", "yank keybindings to register"],
|
||||
["a", "append keybindings"],
|
||||
["p", "prepend keybindings"],
|
||||
@ -26,7 +26,7 @@ export async function setup(_: Context, register: RegisterOr<"dquote", Register.
|
||||
return;
|
||||
}
|
||||
|
||||
const keybindings = await prompt.many([
|
||||
const keybindings = await promptMany([
|
||||
["d", "default keybindings"],
|
||||
]);
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import type { Argument, InputOr, RegisterOr } from ".";
|
||||
import { commands as apiCommands, run as apiRun, command, Context, findMenu, keypress, Menu, prompt, showLockedMenu, showMenu, validateMenu } from "../api";
|
||||
import { commands as apiCommands, run as apiRun, command, compileFunction, Context, findMenu, keypressForRegister, Menu, notifyPromptActionRequested, prompt, promptNumber, runIsEnabled, showLockedMenu, showMenu, showMenuAfterDelay, validateMenu } from "../api";
|
||||
import type { Extension } from "../state/extension";
|
||||
import type { Register } from "../state/registers";
|
||||
import { ArgumentError, CancellationError, InputError } from "../utils/errors";
|
||||
@ -147,7 +147,7 @@ export async function run(
|
||||
commands?: Argument<command.Any[]>,
|
||||
) {
|
||||
if (Array.isArray(commands)) {
|
||||
if (typeof input === "string" && apiRun.isEnabled()) {
|
||||
if (typeof input === "string" && runIsEnabled()) {
|
||||
// Prefer "input" to the "commands" array.
|
||||
} else {
|
||||
return apiCommands(...commands);
|
||||
@ -158,7 +158,7 @@ export async function run(
|
||||
prompt: "Code to run",
|
||||
validateInput(value) {
|
||||
try {
|
||||
apiRun.compileFunction(value);
|
||||
compileFunction(value);
|
||||
|
||||
return;
|
||||
} catch (e) {
|
||||
@ -166,7 +166,7 @@ export async function run(
|
||||
return `invalid syntax: ${e.message}`;
|
||||
}
|
||||
|
||||
return e?.message ?? `${e}`;
|
||||
return (e as Error)?.message ?? `${e}`;
|
||||
}
|
||||
},
|
||||
history: runHistory,
|
||||
@ -193,7 +193,7 @@ export async function run(
|
||||
* @noreplay
|
||||
*/
|
||||
export async function selectRegister(_: Context, inputOr: InputOr<string | Register>) {
|
||||
const input = await inputOr(() => keypress.forRegister(_));
|
||||
const input = await inputOr(() => keypressForRegister(_));
|
||||
|
||||
if (typeof input === "string") {
|
||||
if (input.length === 0) {
|
||||
@ -292,7 +292,7 @@ export async function updateCount(
|
||||
return;
|
||||
}
|
||||
|
||||
const input = +await inputOr(() => prompt.number({ integer: true, range: [0, 1_000_000] }, _));
|
||||
const input = +await inputOr(() => promptNumber({ integer: true, range: [0, 1_000_000] }, _));
|
||||
|
||||
InputError.validateInput(!isNaN(input), "value is not a number");
|
||||
InputError.validateInput(input >= 0, "value is negative");
|
||||
@ -354,7 +354,7 @@ export async function openMenu(
|
||||
}
|
||||
|
||||
if (delay > 0) {
|
||||
return showMenu.withDelay(delay, menu, pass, prefix);
|
||||
return showMenuAfterDelay(delay, menu, pass, prefix);
|
||||
}
|
||||
|
||||
return showMenu(menu, pass, prefix);
|
||||
@ -373,7 +373,7 @@ export async function openMenu(
|
||||
* @noreplay
|
||||
*/
|
||||
export function changeInput(
|
||||
action: Argument<Parameters<typeof prompt.notifyActionRequested>[0]>,
|
||||
action: Argument<Parameters<typeof notifyPromptActionRequested>[0]>,
|
||||
) {
|
||||
ArgumentError.validate(
|
||||
"action",
|
||||
@ -381,5 +381,5 @@ export function changeInput(
|
||||
`must be "previous" or "next"`,
|
||||
);
|
||||
|
||||
prompt.notifyActionRequested(action);
|
||||
notifyPromptActionRequested(action);
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import * as vscode from "vscode";
|
||||
|
||||
import type { Argument, Input, RegisterOr, SetInput } from ".";
|
||||
import { search as apiSearch, Context, Direction, EmptySelectionsError, Positions, prompt, Selections, Shift } from "../api";
|
||||
import { search as apiSearch, Context, Direction, EmptySelectionsError, manipulateSelectionsInteractively, Positions, promptRegexpOpts, Selections, Shift } from "../api";
|
||||
import type { Register } from "../state/registers";
|
||||
import { CharSet, getCharSetFunction } from "../utils/charset";
|
||||
import { escapeForRegExp } from "../utils/regexp";
|
||||
@ -35,8 +35,8 @@ export async function search(
|
||||
input: Input<string | RegExp>,
|
||||
setInput: SetInput<RegExp>,
|
||||
) {
|
||||
return prompt.manipulateSelectionsInteractively(_, input, setInput, interactive, {
|
||||
...prompt.regexpOpts("mu"),
|
||||
return manipulateSelectionsInteractively(_, input, setInput, interactive, {
|
||||
...promptRegexpOpts("mu"),
|
||||
value: (await register.get())?.[0],
|
||||
}, (input, selections) => {
|
||||
if (typeof input === "string") {
|
||||
@ -48,7 +48,7 @@ export async function search(
|
||||
const newSelections = add ? selections.slice() : [],
|
||||
regexpMatches = [] as RegExpMatchArray[];
|
||||
|
||||
newSelections.push(...Selections.map.byIndex((_i, selection, document) => {
|
||||
newSelections.push(...Selections.mapByIndex((_i, selection, document) => {
|
||||
let newSelection = selection;
|
||||
|
||||
for (let j = 0; j < repetitions; j++) {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import * as vscode from "vscode";
|
||||
|
||||
import type { Argument, InputOr } from ".";
|
||||
import { closestSurroundedBy, Context, Direction, keypress, Lines, moveTo, moveWhile, Pair, pair, Positions, prompt, Range, search, SelectionBehavior, Selections, Shift, surroundedBy, wordBoundary } from "../api";
|
||||
import { closestSurroundedBy, Context, Direction, keypress, Lines, moveTo, moveToExcluded, moveWhile, moveWhileBackward, moveWhileForward, Pair, pair, Positions, prompt, Range, search, SelectionBehavior, Selections, Shift, surroundedBy, wordBoundary } from "../api";
|
||||
import { CharSet } from "../utils/charset";
|
||||
import { ArgumentError, assert } from "../utils/errors";
|
||||
import { escapeForRegExp, execRange } from "../utils/regexp";
|
||||
@ -39,7 +39,7 @@ export async function seek(
|
||||
) {
|
||||
const input = await inputOr(() => keypress(_));
|
||||
|
||||
Selections.update.byIndex((_, selection, document) => {
|
||||
Selections.updateByIndex((_, selection, document) => {
|
||||
let position: vscode.Position | undefined = Selections.seekFrom(selection, -direction);
|
||||
|
||||
for (let i = 0; i < repetitions; i++) {
|
||||
@ -49,7 +49,7 @@ export async function seek(
|
||||
return undefined;
|
||||
}
|
||||
|
||||
position = moveTo.excluded(direction, input, position, document);
|
||||
position = moveToExcluded(direction, input, position, document);
|
||||
|
||||
if (position === undefined) {
|
||||
return undefined;
|
||||
@ -139,7 +139,7 @@ export function enclosing(
|
||||
// It only finds one next enclosing character and drags only once to its
|
||||
// matching counterpart. Repetitions > 1 does exactly the same with rep=1,
|
||||
// even though executing the command again will jump back and forth.
|
||||
Selections.update.byIndex((_, selection, document) => {
|
||||
Selections.updateByIndex((_, selection, document) => {
|
||||
// First, find an enclosing char (which may be the current character).
|
||||
let currentCharacter = selection.active;
|
||||
|
||||
@ -203,7 +203,7 @@ export function word(
|
||||
) {
|
||||
const charset = ws ? CharSet.NonBlank : CharSet.Word;
|
||||
|
||||
Selections.update.withFallback.byIndex((_i, selection) => {
|
||||
Selections.updateWithFallbackByIndex((_i, selection) => {
|
||||
const anchor = Selections.seekFrom(selection, direction, selection.anchor, _);
|
||||
let active = Selections.seekFrom(selection, direction, selection.active, _);
|
||||
|
||||
@ -298,7 +298,7 @@ export async function object(
|
||||
p = pair(openRe, closeRe);
|
||||
|
||||
if (where === "start") {
|
||||
return Selections.update.byIndex((_i, selection) => {
|
||||
return Selections.updateByIndex((_i, selection) => {
|
||||
const startResult = p.searchOpening(Selections.activeStart(selection, _));
|
||||
|
||||
if (startResult === undefined) {
|
||||
@ -314,7 +314,7 @@ export async function object(
|
||||
}
|
||||
|
||||
if (where === "end") {
|
||||
return Selections.update.byIndex((_i, selection) => {
|
||||
return Selections.updateByIndex((_i, selection) => {
|
||||
const endResult = p.searchClosing(Selections.activeEnd(selection, _));
|
||||
|
||||
if (endResult === undefined) {
|
||||
@ -332,7 +332,7 @@ export async function object(
|
||||
if (_.selectionBehavior === SelectionBehavior.Character) {
|
||||
const startRe = new RegExp("^" + openRe.source, openRe.flags);
|
||||
|
||||
return Selections.update.byIndex((_i, selection) => {
|
||||
return Selections.updateByIndex((_i, selection) => {
|
||||
// If the selection behavior is character and the current character
|
||||
// corresponds to the start of a pair, we select from here.
|
||||
const searchStart = Selections.activeStart(selection, _),
|
||||
@ -362,7 +362,7 @@ export async function object(
|
||||
});
|
||||
}
|
||||
|
||||
return Selections.update.byIndex(
|
||||
return Selections.updateByIndex(
|
||||
(_i, selection) => surroundedBy([p], Selections.activeStart(selection, _), !inner, _.document),
|
||||
);
|
||||
}
|
||||
@ -376,15 +376,15 @@ export async function object(
|
||||
return shiftWhere(
|
||||
_,
|
||||
(selection, _) => {
|
||||
let start = moveWhile.backward((c) => re.test(c), selection.active, _.document),
|
||||
end = moveWhile.forward((c) => re.test(c), selection.active, _.document);
|
||||
let start = moveWhileBackward((c) => re.test(c), selection.active, _.document),
|
||||
end = moveWhileForward((c) => re.test(c), selection.active, _.document);
|
||||
|
||||
if (beforeRe !== undefined) {
|
||||
start = moveWhile.backward((c) => beforeRe.test(c), start, _.document);
|
||||
start = moveWhileBackward((c) => beforeRe.test(c), start, _.document);
|
||||
}
|
||||
|
||||
if (afterRe !== undefined) {
|
||||
end = moveWhile.forward((c) => afterRe.test(c), end, _.document);
|
||||
end = moveWhileForward((c) => afterRe.test(c), end, _.document);
|
||||
}
|
||||
|
||||
return new vscode.Selection(start, end);
|
||||
@ -454,7 +454,7 @@ export async function object(
|
||||
let newSelections: vscode.Selection[];
|
||||
|
||||
if (where === "start") {
|
||||
newSelections = Selections.map.byIndex((_i, selection, document) => {
|
||||
newSelections = Selections.mapByIndex((_i, selection, document) => {
|
||||
const activePosition = Selections.activePosition(selection, _.document);
|
||||
let shiftTo = f.start(activePosition, inner, document);
|
||||
|
||||
@ -469,7 +469,7 @@ export async function object(
|
||||
return Selections.shift(selection, shiftTo, shift, _);
|
||||
});
|
||||
} else if (where === "end") {
|
||||
newSelections = Selections.map.byIndex((_i, selection, document) =>
|
||||
newSelections = Selections.mapByIndex((_i, selection, document) =>
|
||||
Selections.shift(
|
||||
selection,
|
||||
f.end(selection.active, inner, document),
|
||||
@ -478,7 +478,7 @@ export async function object(
|
||||
),
|
||||
);
|
||||
} else {
|
||||
newSelections = Selections.map.byIndex((_, selection, document) =>
|
||||
newSelections = Selections.mapByIndex((_, selection, document) =>
|
||||
f(selection.active, inner, document),
|
||||
);
|
||||
}
|
||||
@ -503,7 +503,7 @@ function shiftWhere(
|
||||
shift: Shift,
|
||||
where: "start" | "end" | undefined,
|
||||
) {
|
||||
Selections.update.byIndex((_, selection) => {
|
||||
Selections.updateByIndex((_, selection) => {
|
||||
const result = f(selection, context);
|
||||
|
||||
if (result === undefined) {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import * as vscode from "vscode";
|
||||
|
||||
import type { Argument } from ".";
|
||||
import { firstVisibleLine as apiFirstVisibleLine, lastVisibleLine as apiLastVisibleLine, middleVisibleLine as apiMiddleVisibleLine, Context, Direction, Lines, Positions, SelectionBehavior, Selections, Shift, showMenu } from "../api";
|
||||
import { firstVisibleLine as apiFirstVisibleLine, lastVisibleLine as apiLastVisibleLine, middleVisibleLine as apiMiddleVisibleLine, Context, Direction, Lines, Positions, SelectionBehavior, Selections, Shift, showMenuByName } from "../api";
|
||||
import { PerEditorState } from "../state/editors";
|
||||
import { unsafeSelections } from "../utils/misc";
|
||||
|
||||
@ -126,7 +126,7 @@ export function vertically(
|
||||
);
|
||||
}
|
||||
|
||||
const newSelections = Selections.map.byIndex((i, selection) => {
|
||||
const newSelections = Selections.mapByIndex((i, selection) => {
|
||||
// TODO: handle tab characters
|
||||
const activeLine = isCharacterMode ? Selections.activeLine(selection) : selection.active.line,
|
||||
targetLine = Lines.clamp(activeLine + repetitions * direction, document),
|
||||
@ -188,7 +188,7 @@ export function vertically(
|
||||
|
||||
let newPosition = new vscode.Position(
|
||||
targetLine,
|
||||
Lines.column.character(targetLine, targetColumn, _.editor, /* roundUp= */ isCharacterMode),
|
||||
Lines.character(targetLine, targetColumn, _.editor, /* roundUp= */ isCharacterMode),
|
||||
);
|
||||
|
||||
if (isCharacterMode && shift !== Shift.Jump) {
|
||||
@ -240,7 +240,7 @@ export function horizontally(
|
||||
const mayNeedAdjustment = direction === Direction.Backward
|
||||
&& _.selectionBehavior === SelectionBehavior.Character;
|
||||
|
||||
const newSelections = Selections.map.byIndex((_i, selection, document) => {
|
||||
const newSelections = Selections.mapByIndex((_i, selection, document) => {
|
||||
let active = selection.active === selection.start
|
||||
? Selections.activeStart(selection, _)
|
||||
: Selections.activeEnd(selection, _);
|
||||
@ -305,7 +305,7 @@ export function to(
|
||||
if (count === 0) {
|
||||
// TODO: Make just merely opening the menu not count as a command execution
|
||||
// and do not record it.
|
||||
return showMenu.byName("goto", [argument]);
|
||||
return showMenuByName("goto", [argument]);
|
||||
}
|
||||
|
||||
return lineStart(_, count, shift);
|
||||
@ -318,7 +318,7 @@ export function to(
|
||||
*/
|
||||
export function line_below(_: Context, count: number) {
|
||||
if (count === 0 || count === 1) {
|
||||
Selections.update.byIndex((_, selection) => {
|
||||
Selections.updateByIndex((_, selection) => {
|
||||
let line = Selections.activeLine(selection);
|
||||
|
||||
if (Selections.isEntireLines(selection) && !selection.isReversed) {
|
||||
@ -328,7 +328,7 @@ export function line_below(_: Context, count: number) {
|
||||
return new vscode.Selection(line, 0, line + 1, 0);
|
||||
});
|
||||
} else {
|
||||
Selections.update.byIndex((_, selection, document) => {
|
||||
Selections.updateByIndex((_, selection, document) => {
|
||||
const lastLine = document.lineCount - 1;
|
||||
let line = Math.min(Selections.activeLine(selection) + count - 1, lastLine);
|
||||
|
||||
@ -348,7 +348,7 @@ export function line_below(_: Context, count: number) {
|
||||
*/
|
||||
export function line_below_extend(_: Context, count: number) {
|
||||
if (count === 0 || count === 1) {
|
||||
Selections.update.byIndex((_, selection, document) => {
|
||||
Selections.updateByIndex((_, selection, document) => {
|
||||
const isFullLine = Selections.endsWithEntireLine(selection),
|
||||
isSameLine = Selections.isSingleLine(selection),
|
||||
isFullLineDiff = isFullLine && !(isSameLine && selection.isReversed) ? 1 : 0,
|
||||
@ -360,7 +360,7 @@ export function line_below_extend(_: Context, count: number) {
|
||||
return new vscode.Selection(anchor, active);
|
||||
});
|
||||
} else {
|
||||
Selections.update.byIndex((_, selection, document) => {
|
||||
Selections.updateByIndex((_, selection, document) => {
|
||||
const activeLine = Selections.activeLine(selection),
|
||||
line = Math.min(activeLine + count - 1, document.lineCount - 1),
|
||||
isSameLine = Selections.isSingleLine(selection);
|
||||
@ -378,7 +378,7 @@ export function line_below_extend(_: Context, count: number) {
|
||||
*/
|
||||
export function line_above(_: Context, count: number) {
|
||||
if (count === 0 || count === 1) {
|
||||
Selections.update.byIndex((_, selection) => {
|
||||
Selections.updateByIndex((_, selection) => {
|
||||
let line = Selections.activeLine(selection);
|
||||
|
||||
if (!Selections.isEntireLines(selection)) {
|
||||
@ -388,7 +388,7 @@ export function line_above(_: Context, count: number) {
|
||||
return new vscode.Selection(line, 0, line - 1, 0);
|
||||
});
|
||||
} else {
|
||||
Selections.update.byIndex((_, selection) => {
|
||||
Selections.updateByIndex((_, selection) => {
|
||||
let line = Math.max(Selections.activeLine(selection) - count + 1, 0);
|
||||
|
||||
if (!Selections.isEntireLines(selection)) {
|
||||
@ -405,7 +405,7 @@ export function line_above(_: Context, count: number) {
|
||||
*/
|
||||
export function line_above_extend(_: Context, count: number) {
|
||||
if (count === 0 || count === 1) {
|
||||
Selections.update.byIndex((_, selection) => {
|
||||
Selections.updateByIndex((_, selection) => {
|
||||
if (selection.isSingleLine) {
|
||||
let line = Selections.activeLine(selection);
|
||||
|
||||
@ -429,7 +429,7 @@ export function line_above_extend(_: Context, count: number) {
|
||||
return new vscode.Selection(selection.anchor, active);
|
||||
});
|
||||
} else {
|
||||
Selections.update.byIndex((_, selection, document) => {
|
||||
Selections.updateByIndex((_, selection, document) => {
|
||||
let line = Math.max(Selections.activeLine(selection) - count, 0),
|
||||
anchor = selection.anchor;
|
||||
|
||||
@ -485,7 +485,7 @@ export function lineStart(
|
||||
return;
|
||||
}
|
||||
|
||||
Selections.update.byIndex((_, selection) =>
|
||||
Selections.updateByIndex((_, selection) =>
|
||||
Selections.shift(
|
||||
selection,
|
||||
skipBlank
|
||||
@ -541,7 +541,7 @@ export function lineEnd(
|
||||
return;
|
||||
}
|
||||
|
||||
Selections.update.byIndex((_, selection) =>
|
||||
Selections.updateByIndex((_, selection) =>
|
||||
mapSelection(selection, Selections.activeLine(selection)),
|
||||
);
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import type { Argument } from ".";
|
||||
import { Context, rotate } from "../api";
|
||||
import { Context, rotate, rotateContents, rotateSelections } from "../api";
|
||||
|
||||
/**
|
||||
* Rotate selection indices and contents.
|
||||
@ -39,7 +39,7 @@ export function contents(_: Context, repetitions: number, reverse: Argument<bool
|
||||
repetitions = -repetitions;
|
||||
}
|
||||
|
||||
return rotate.contentsOnly(repetitions);
|
||||
return rotateContents(repetitions);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -58,5 +58,5 @@ export function selections(_: Context, repetitions: number, reverse: Argument<bo
|
||||
repetitions = -repetitions;
|
||||
}
|
||||
|
||||
return rotate.selectionsOnly(repetitions);
|
||||
return rotateSelections(repetitions);
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import * as vscode from "vscode";
|
||||
|
||||
import type { Argument, Input, InputOr, RegisterOr, SetInput } from ".";
|
||||
import { Context, Direction, moveWhile, Positions, prompt, SelectionBehavior, Selections, switchRun } from "../api";
|
||||
import { Context, Direction, manipulateSelectionsInteractively, moveWhile, moveWhileBackward, moveWhileForward, Positions, prompt, promptOne, promptRegexpOpts, SelectionBehavior, Selections, switchRun, validateForSwitchRun } from "../api";
|
||||
import { PerEditorState } from "../state/editors";
|
||||
import { Mode } from "../state/modes";
|
||||
import type { Register } from "../state/registers";
|
||||
@ -10,7 +10,7 @@ import { AutoDisposable } from "../utils/disposables";
|
||||
import { ArgumentError, EmptySelectionsError } from "../utils/errors";
|
||||
import { unsafeSelections } from "../utils/misc";
|
||||
import { SettingsValidator } from "../utils/settings-validator";
|
||||
import { TrackedSelection } from "../utils/tracked-selection";
|
||||
import * as TrackedSelection from "../utils/tracked-selection";
|
||||
|
||||
/**
|
||||
* Interacting with selections.
|
||||
@ -143,7 +143,7 @@ export async function restore_withCurrent(
|
||||
add = savedSelections;
|
||||
}
|
||||
|
||||
const type = await prompt.one([
|
||||
const type = await promptOne([
|
||||
["a", "Append lists"],
|
||||
["u", "Union"],
|
||||
["i", "Intersection"],
|
||||
@ -263,9 +263,9 @@ export async function pipe(
|
||||
prompt: "Expression",
|
||||
validateInput(value) {
|
||||
try {
|
||||
switchRun.validate(value);
|
||||
return void validateForSwitchRun(value);
|
||||
} catch (e) {
|
||||
return e?.message ?? `${e}`;
|
||||
return (e as Error)?.message ?? `${e}`;
|
||||
}
|
||||
},
|
||||
history: pipeHistory,
|
||||
@ -331,20 +331,20 @@ export function filter(
|
||||
const document = _.document,
|
||||
strings = _.selections.map((selection) => document.getText(selection));
|
||||
|
||||
return prompt.manipulateSelectionsInteractively(_, input, setInput, interactive, {
|
||||
return manipulateSelectionsInteractively(_, input, setInput, interactive, {
|
||||
prompt: "Expression",
|
||||
validateInput(value) {
|
||||
try {
|
||||
switchRun.validate(value);
|
||||
return void validateForSwitchRun(value);
|
||||
} catch (e) {
|
||||
return e?.message ?? `${e}`;
|
||||
return (e as Error)?.message ?? `${e}`;
|
||||
}
|
||||
},
|
||||
value: defaultInput,
|
||||
valueSelection: defaultInput ? [defaultInput.length, defaultInput.length] : undefined,
|
||||
history: filterHistory,
|
||||
}, (input, selections) => {
|
||||
return Selections.filter.byIndex(async (i) => {
|
||||
return Selections.filterByIndex(async (i) => {
|
||||
const context = { $: strings[i], $$: strings, i, n: strings.length, count };
|
||||
|
||||
try {
|
||||
@ -368,12 +368,12 @@ export function select(
|
||||
input: Input<string | RegExp>,
|
||||
setInput: SetInput<RegExp>,
|
||||
) {
|
||||
return prompt.manipulateSelectionsInteractively(
|
||||
return manipulateSelectionsInteractively(
|
||||
_,
|
||||
input,
|
||||
setInput,
|
||||
interactive,
|
||||
prompt.regexpOpts("mu"),
|
||||
promptRegexpOpts("mu"),
|
||||
(input, selections) => {
|
||||
if (typeof input === "string") {
|
||||
input = new RegExp(input, "mu");
|
||||
@ -399,12 +399,12 @@ export function split(
|
||||
input: Input<string | RegExp>,
|
||||
setInput: SetInput<RegExp>,
|
||||
) {
|
||||
return prompt.manipulateSelectionsInteractively(
|
||||
return manipulateSelectionsInteractively(
|
||||
_,
|
||||
input,
|
||||
setInput,
|
||||
interactive,
|
||||
prompt.regexpOpts("mu"),
|
||||
promptRegexpOpts("mu"),
|
||||
(input, selections) => {
|
||||
if (typeof input === "string") {
|
||||
input = new RegExp(input, "mu");
|
||||
@ -485,7 +485,7 @@ export function splitLines(
|
||||
* @keys `a-x` (normal)
|
||||
*/
|
||||
export function expandToLines(_: Context) {
|
||||
return Selections.update.byIndex((_, selection, document) => {
|
||||
return Selections.updateByIndex((_, selection, document) => {
|
||||
const start = selection.start,
|
||||
end = selection.end;
|
||||
|
||||
@ -519,7 +519,7 @@ export function expandToLines(_: Context) {
|
||||
* @keys `s-a-x` (normal)
|
||||
*/
|
||||
export function trimLines(_: Context) {
|
||||
return Selections.update.byIndex((_, selection) => {
|
||||
return Selections.updateByIndex((_, selection) => {
|
||||
const start = selection.start,
|
||||
end = selection.end;
|
||||
|
||||
@ -554,12 +554,12 @@ export function trimWhitespace(_: Context) {
|
||||
const blank = getCharacters(CharSet.Blank, _.document),
|
||||
isBlank = (character: string) => blank.includes(character);
|
||||
|
||||
return Selections.update.byIndex((_, selection, document) => {
|
||||
return Selections.updateByIndex((_, selection, document) => {
|
||||
const firstCharacter = selection.start,
|
||||
lastCharacter = selection.end;
|
||||
|
||||
const start = moveWhile.forward(isBlank, firstCharacter, document),
|
||||
end = moveWhile.backward(isBlank, lastCharacter, document);
|
||||
const start = moveWhileForward(isBlank, firstCharacter, document),
|
||||
end = moveWhileBackward(isBlank, lastCharacter, document);
|
||||
|
||||
if (start.isAfter(end)) {
|
||||
return undefined;
|
||||
@ -597,7 +597,7 @@ export function reduce(
|
||||
|
||||
if (empty && _.selectionBehavior !== SelectionBehavior.Character) {
|
||||
if (where !== "both") {
|
||||
Selections.update.byIndex((_, selection) => Selections.empty(selection[where]));
|
||||
Selections.updateByIndex((_, selection) => Selections.empty(selection[where]));
|
||||
} else {
|
||||
Selections.set(_.selections.flatMap((selection) => {
|
||||
if (selection.isEmpty) {
|
||||
@ -633,7 +633,7 @@ export function reduce(
|
||||
};
|
||||
|
||||
if (where !== "both") {
|
||||
Selections.update.byIndex((_, selection) => takeWhere(selection, where));
|
||||
Selections.updateByIndex((_, selection) => takeWhere(selection, where));
|
||||
|
||||
return;
|
||||
}
|
||||
@ -668,21 +668,21 @@ export function reduce(
|
||||
export function changeDirection(_: Context, direction?: Direction) {
|
||||
switch (direction) {
|
||||
case Direction.Backward:
|
||||
Selections.update.byIndex((_, selection) =>
|
||||
Selections.updateByIndex((_, selection) =>
|
||||
selection.isReversed || selection.isEmpty || Selections.isNonDirectional(selection)
|
||||
? selection
|
||||
: new vscode.Selection(selection.end, selection.start));
|
||||
break;
|
||||
|
||||
case Direction.Forward:
|
||||
Selections.update.byIndex((_, selection) =>
|
||||
Selections.updateByIndex((_, selection) =>
|
||||
selection.isReversed
|
||||
? new vscode.Selection(selection.start, selection.end)
|
||||
: selection);
|
||||
break;
|
||||
|
||||
default:
|
||||
Selections.update.byIndex((_, selection) =>
|
||||
Selections.updateByIndex((_, selection) =>
|
||||
selection.isEmpty || Selections.isNonDirectional(selection)
|
||||
? selection
|
||||
: new vscode.Selection(selection.active, selection.anchor));
|
||||
|
@ -22,13 +22,13 @@ export function activate() {
|
||||
extensionPackageJSON = extensionData?.packageJSON;
|
||||
|
||||
if (extensionPackageJSON?.[`${extensionName}.disableArbitraryCodeExecution`]) {
|
||||
api.run.disable();
|
||||
api.disableRunFunction();
|
||||
} else {
|
||||
api.run.setGlobals({ vscode, ...api });
|
||||
api.setRunGlobals({ vscode, ...api });
|
||||
}
|
||||
|
||||
if (extensionPackageJSON?.[`${extensionName}.disableArbitraryCommandExecution`]) {
|
||||
api.execute.disable();
|
||||
api.disableExecuteFunction();
|
||||
}
|
||||
|
||||
return loadCommands().then((commands) => {
|
||||
|
@ -487,8 +487,8 @@ export class PerEditorState implements vscode.Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
export namespace PerEditorState {
|
||||
export declare class Token<T> {
|
||||
export declare namespace PerEditorState {
|
||||
export class Token<T> {
|
||||
private can_never_implement_this: never;
|
||||
}
|
||||
}
|
||||
|
@ -484,7 +484,7 @@ export class Mode {
|
||||
}
|
||||
}
|
||||
|
||||
export namespace Mode {
|
||||
export declare namespace Mode {
|
||||
/**
|
||||
* The configuration of a `Mode` as specified in the user preferences.
|
||||
*/
|
||||
@ -752,7 +752,7 @@ export class Modes implements Iterable<Mode> {
|
||||
}
|
||||
}
|
||||
|
||||
export namespace Modes {
|
||||
export declare namespace Modes {
|
||||
export interface Configuration {
|
||||
readonly [modeName: string]: Mode.Configuration;
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -4,7 +4,7 @@ import type { Recording } from "./recorder";
|
||||
import { Context, prompt, SelectionBehavior, Selections } from "../api";
|
||||
import { ArgumentError, assert, EditNotAppliedError, EditorRequiredError } from "../utils/errors";
|
||||
import { noUndoStops } from "../utils/misc";
|
||||
import type { TrackedSelection } from "../utils/tracked-selection";
|
||||
import type * as TrackedSelection from "../utils/tracked-selection";
|
||||
|
||||
/**
|
||||
* The base class for all registers.
|
||||
@ -148,7 +148,7 @@ export abstract class Register {
|
||||
}
|
||||
}
|
||||
|
||||
export namespace Register {
|
||||
export declare namespace Register {
|
||||
/**
|
||||
* Flags describing the capabilities of a `Register`.
|
||||
*/
|
||||
|
@ -1,9 +1,52 @@
|
||||
import * as vscode from "vscode";
|
||||
|
||||
export class StatusBarSegment implements vscode.Disposable {
|
||||
private readonly _statusBarItem: vscode.StatusBarItem;
|
||||
|
||||
private _content?: string;
|
||||
|
||||
public get content() {
|
||||
return this._content;
|
||||
}
|
||||
|
||||
public get statusBarItem() {
|
||||
return this._statusBarItem;
|
||||
}
|
||||
|
||||
public constructor(
|
||||
public readonly name: string,
|
||||
public readonly icon: string,
|
||||
public readonly priority: number,
|
||||
command: string | vscode.Command,
|
||||
) {
|
||||
this._statusBarItem =
|
||||
vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, priority);
|
||||
this._statusBarItem.tooltip = name;
|
||||
this._statusBarItem.command = command;
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
this._statusBarItem.dispose();
|
||||
}
|
||||
|
||||
public setContent(content?: string) {
|
||||
this._content = content;
|
||||
|
||||
if (content) {
|
||||
this._statusBarItem.text = `$(${this.icon}) ${content}`;
|
||||
this._statusBarItem.show();
|
||||
} else {
|
||||
this._statusBarItem.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Controls the Dance status bar item.
|
||||
*/
|
||||
export class StatusBar implements vscode.Disposable {
|
||||
public static readonly Segment = StatusBarSegment;
|
||||
|
||||
private readonly _segments: StatusBar.Segment[] = [];
|
||||
|
||||
public readonly activeModeSegment: StatusBar.Segment;
|
||||
@ -48,45 +91,6 @@ export class StatusBar implements vscode.Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
export namespace StatusBar {
|
||||
export class Segment implements vscode.Disposable {
|
||||
private readonly _statusBarItem: vscode.StatusBarItem;
|
||||
|
||||
private _content?: string;
|
||||
|
||||
public get content() {
|
||||
return this._content;
|
||||
}
|
||||
|
||||
public get statusBarItem() {
|
||||
return this._statusBarItem;
|
||||
}
|
||||
|
||||
public constructor(
|
||||
public readonly name: string,
|
||||
public readonly icon: string,
|
||||
public readonly priority: number,
|
||||
command: string | vscode.Command,
|
||||
) {
|
||||
this._statusBarItem =
|
||||
vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, priority);
|
||||
this._statusBarItem.tooltip = name;
|
||||
this._statusBarItem.command = command;
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
this._statusBarItem.dispose();
|
||||
}
|
||||
|
||||
public setContent(content?: string) {
|
||||
this._content = content;
|
||||
|
||||
if (content) {
|
||||
this._statusBarItem.text = `$(${this.icon}) ${content}`;
|
||||
this._statusBarItem.show();
|
||||
} else {
|
||||
this._statusBarItem.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
export declare namespace StatusBar {
|
||||
export type Segment = StatusBarSegment;
|
||||
}
|
||||
|
@ -229,7 +229,7 @@ export class AutoDisposable implements vscode.Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
export namespace AutoDisposable {
|
||||
export declare namespace AutoDisposable {
|
||||
export const enum EventType {
|
||||
OnEditorWasClosed = "editor-was-closed",
|
||||
OnModeDidChange = "mode-did-change",
|
||||
|
@ -159,7 +159,7 @@ export class CancellationError extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
export namespace CancellationError {
|
||||
export declare namespace CancellationError {
|
||||
export const enum Reason {
|
||||
CancellationToken = "cancellation token was used",
|
||||
PressedEscape = "user pressed <escape>",
|
||||
|
@ -456,7 +456,7 @@ export interface Node<To extends Node<To>> {
|
||||
reverse(state: Node.ReverseState): To;
|
||||
}
|
||||
|
||||
export namespace Node {
|
||||
export declare namespace Node {
|
||||
export type Inner<T extends Node<any>> = T extends Node<infer R> ? R : never;
|
||||
|
||||
export interface ReverseState {
|
||||
@ -491,7 +491,7 @@ export class Sequence implements Node<Sequence> {
|
||||
}
|
||||
}
|
||||
|
||||
export namespace Sequence {
|
||||
export declare namespace Sequence {
|
||||
export type Node = Repeat | Anchor;
|
||||
}
|
||||
|
||||
@ -733,7 +733,7 @@ export class CharacterSet implements Node<CharacterSet> {
|
||||
CharacterSet.whitespace.alternatives, true);
|
||||
}
|
||||
|
||||
export namespace CharacterSet {
|
||||
export declare namespace CharacterSet {
|
||||
export type AlternativeAtom = Raw | Escaped;
|
||||
export type Alternative = AlternativeAtom | CharacterClass | [AlternativeAtom, AlternativeAtom];
|
||||
}
|
||||
@ -876,7 +876,7 @@ export class Lookaround extends Disjunction<Lookaround> {
|
||||
}
|
||||
}
|
||||
|
||||
export namespace Anchor {
|
||||
export declare namespace Anchor {
|
||||
export type Kind = AnchorKind;
|
||||
}
|
||||
|
||||
@ -938,7 +938,7 @@ export class Repeat<T extends Repeat.Node = Repeat.Node> implements Node<Repeat>
|
||||
}
|
||||
}
|
||||
|
||||
export namespace Repeat {
|
||||
export declare namespace Repeat {
|
||||
export type Node = Group | Lookaround | CharacterSet | Raw | Escaped | CharacterClass | Dot
|
||||
| NumericEscape | Backreference;
|
||||
}
|
||||
|
@ -3,346 +3,343 @@ import * as vscode from "vscode";
|
||||
import { ArgumentError } from "./errors";
|
||||
import type { PerEditorState } from "../state/editors";
|
||||
|
||||
export namespace TrackedSelection {
|
||||
/**
|
||||
* Flags passed to `TrackedSelection.updateAfterDocumentChanged`.
|
||||
*/
|
||||
export const enum Flags {
|
||||
Inclusive = 0,
|
||||
/**
|
||||
* Flags passed to {@link updateAfterDocumentChanged}.
|
||||
*/
|
||||
export const enum Flags {
|
||||
Inclusive = 0,
|
||||
|
||||
StrictStart = 0b01,
|
||||
StrictEnd = 0b10,
|
||||
StrictStart = 0b01,
|
||||
StrictEnd = 0b10,
|
||||
|
||||
Strict = 0b11,
|
||||
Strict = 0b11,
|
||||
|
||||
EmptyExtendsForward = 0b01_00,
|
||||
EmptyExtendsBackward = 0b10_00,
|
||||
EmptyMoves = 0b11_00,
|
||||
}
|
||||
EmptyExtendsForward = 0b01_00,
|
||||
EmptyExtendsBackward = 0b10_00,
|
||||
EmptyMoves = 0b11_00,
|
||||
}
|
||||
|
||||
/**
|
||||
* An array of tracked selections selections.
|
||||
*/
|
||||
export interface Array extends Iterable<number> {
|
||||
[index: number]: number;
|
||||
readonly length: number;
|
||||
}
|
||||
/**
|
||||
* An array of tracked selections selections.
|
||||
*/
|
||||
export interface Array extends Iterable<number> {
|
||||
[index: number]: number;
|
||||
readonly length: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new `TrackedSelection.Array`, reading offsets from the given
|
||||
* selections in the given document.
|
||||
*/
|
||||
export function fromArray(
|
||||
selections: readonly vscode.Selection[],
|
||||
document: vscode.TextDocument,
|
||||
): Array {
|
||||
const trackedSelections = [] as number[];
|
||||
/**
|
||||
* Creates a new `TrackedSelection.Array`, reading offsets from the given
|
||||
* selections in the given document.
|
||||
*/
|
||||
export function fromArray(
|
||||
selections: readonly vscode.Selection[],
|
||||
document: vscode.TextDocument,
|
||||
): Array {
|
||||
const trackedSelections = [] as number[];
|
||||
|
||||
for (let i = 0, len = selections.length; i < len; i++) {
|
||||
const selection = selections[i],
|
||||
anchor = selection.anchor,
|
||||
active = selection.active;
|
||||
for (let i = 0, len = selections.length; i < len; i++) {
|
||||
const selection = selections[i],
|
||||
anchor = selection.anchor,
|
||||
active = selection.active;
|
||||
|
||||
if (anchor.line === active.line) {
|
||||
const anchorOffset = document.offsetAt(anchor),
|
||||
activeOffset = anchorOffset + active.character - anchor.character;
|
||||
if (anchor.line === active.line) {
|
||||
const anchorOffset = document.offsetAt(anchor),
|
||||
activeOffset = anchorOffset + active.character - anchor.character;
|
||||
|
||||
trackedSelections.push(anchorOffset, activeOffset);
|
||||
} else {
|
||||
trackedSelections.push(document.offsetAt(anchor), document.offsetAt(active));
|
||||
}
|
||||
}
|
||||
|
||||
return trackedSelections;
|
||||
}
|
||||
|
||||
export function restore(array: Array, index: number, document: vscode.TextDocument) {
|
||||
const anchor = document.positionAt(array[index << 1]),
|
||||
active = document.positionAt(array[(index << 1) | 1]);
|
||||
|
||||
return new vscode.Selection(anchor, active);
|
||||
}
|
||||
|
||||
export function anchorOffset(array: Array, index: number) {
|
||||
return array[index << 1];
|
||||
}
|
||||
|
||||
export function activeOffset(array: Array, index: number) {
|
||||
return array[(index << 1) | 1];
|
||||
}
|
||||
|
||||
export function startOffset(array: Array, index: number) {
|
||||
return Math.min(anchorOffset(array, index), activeOffset(array, index));
|
||||
}
|
||||
|
||||
export function endOffset(array: Array, index: number) {
|
||||
return Math.max(anchorOffset(array, index), activeOffset(array, index));
|
||||
}
|
||||
|
||||
export function length(array: Array, index: number) {
|
||||
return Math.abs(anchorOffset(array, index) - activeOffset(array, index));
|
||||
}
|
||||
|
||||
export function setAnchorOffset(array: Array, index: number, offset: number) {
|
||||
array[index << 1] = offset;
|
||||
}
|
||||
|
||||
export function setActiveOffset(array: Array, index: number, offset: number) {
|
||||
array[(index << 1) | 1] = offset;
|
||||
}
|
||||
|
||||
export function activeIsStart(array: Array, index: number) {
|
||||
return activeOffset(array, index) <= anchorOffset(array, index);
|
||||
}
|
||||
|
||||
export function setLength(array: Array, index: number, length: number) {
|
||||
const active = activeOffset(array, index),
|
||||
anchor = anchorOffset(array, index);
|
||||
|
||||
if (active < anchor) {
|
||||
setAnchorOffset(array, index, active + length);
|
||||
trackedSelections.push(anchorOffset, activeOffset);
|
||||
} else {
|
||||
setActiveOffset(array, index, anchor + length);
|
||||
trackedSelections.push(document.offsetAt(anchor), document.offsetAt(active));
|
||||
}
|
||||
}
|
||||
|
||||
export function setStartEnd(
|
||||
array: Array,
|
||||
index: number,
|
||||
startOffset: number,
|
||||
endOffset: number,
|
||||
startIsActive: boolean,
|
||||
) {
|
||||
if (startIsActive) {
|
||||
setActiveOffset(array, index, startOffset);
|
||||
setAnchorOffset(array, index, endOffset);
|
||||
return trackedSelections;
|
||||
}
|
||||
|
||||
export function restore(array: Array, index: number, document: vscode.TextDocument) {
|
||||
const anchor = document.positionAt(array[index << 1]),
|
||||
active = document.positionAt(array[(index << 1) | 1]);
|
||||
|
||||
return new vscode.Selection(anchor, active);
|
||||
}
|
||||
|
||||
export function anchorOffset(array: Array, index: number) {
|
||||
return array[index << 1];
|
||||
}
|
||||
|
||||
export function activeOffset(array: Array, index: number) {
|
||||
return array[(index << 1) | 1];
|
||||
}
|
||||
|
||||
export function startOffset(array: Array, index: number) {
|
||||
return Math.min(anchorOffset(array, index), activeOffset(array, index));
|
||||
}
|
||||
|
||||
export function endOffset(array: Array, index: number) {
|
||||
return Math.max(anchorOffset(array, index), activeOffset(array, index));
|
||||
}
|
||||
|
||||
export function length(array: Array, index: number) {
|
||||
return Math.abs(anchorOffset(array, index) - activeOffset(array, index));
|
||||
}
|
||||
|
||||
export function setAnchorOffset(array: Array, index: number, offset: number) {
|
||||
array[index << 1] = offset;
|
||||
}
|
||||
|
||||
export function setActiveOffset(array: Array, index: number, offset: number) {
|
||||
array[(index << 1) | 1] = offset;
|
||||
}
|
||||
|
||||
export function activeIsStart(array: Array, index: number) {
|
||||
return activeOffset(array, index) <= anchorOffset(array, index);
|
||||
}
|
||||
|
||||
export function setLength(array: Array, index: number, length: number) {
|
||||
const active = activeOffset(array, index),
|
||||
anchor = anchorOffset(array, index);
|
||||
|
||||
if (active < anchor) {
|
||||
setAnchorOffset(array, index, active + length);
|
||||
} else {
|
||||
setActiveOffset(array, index, anchor + length);
|
||||
}
|
||||
}
|
||||
|
||||
export function setStartEnd(
|
||||
array: Array,
|
||||
index: number,
|
||||
startOffset: number,
|
||||
endOffset: number,
|
||||
startIsActive: boolean,
|
||||
) {
|
||||
if (startIsActive) {
|
||||
setActiveOffset(array, index, startOffset);
|
||||
setAnchorOffset(array, index, endOffset);
|
||||
} else {
|
||||
setActiveOffset(array, index, endOffset);
|
||||
setAnchorOffset(array, index, startOffset);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the saved selections, restored in the given document.
|
||||
*/
|
||||
export function restoreArray(array: Array, document: vscode.TextDocument) {
|
||||
const selections = [] as vscode.Selection[];
|
||||
|
||||
for (let i = 0, len = array.length >> 1; i < len; i++) {
|
||||
selections.push(restore(array, i, document));
|
||||
}
|
||||
|
||||
return selections;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the saved selections, restored in the given document, skipping
|
||||
* empty selections.
|
||||
*/
|
||||
export function restoreNonEmpty(array: Array, document: vscode.TextDocument) {
|
||||
const selections = [] as vscode.Selection[];
|
||||
|
||||
for (let i = 0, len = array.length >> 1; i < len; i++) {
|
||||
const anchorOffset = array[i << 1],
|
||||
activeOffset = array[(i << 1) | 1];
|
||||
|
||||
if (anchorOffset === activeOffset) {
|
||||
continue;
|
||||
}
|
||||
|
||||
selections.push(
|
||||
new vscode.Selection(document.positionAt(anchorOffset), document.positionAt(activeOffset)),
|
||||
);
|
||||
}
|
||||
|
||||
return selections;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the underlying selection to reflect a change in its document.
|
||||
*/
|
||||
export function updateAfterDocumentChanged(
|
||||
array: Array,
|
||||
changes: readonly vscode.TextDocumentContentChangeEvent[],
|
||||
flags: Flags,
|
||||
) {
|
||||
for (let i = 0, len = array.length; i < len; i += 2) {
|
||||
let anchorOffset = array[i],
|
||||
activeOffset = array[i + 1],
|
||||
inclusiveActive: boolean,
|
||||
inclusiveAnchor: boolean;
|
||||
|
||||
if (anchorOffset === activeOffset) {
|
||||
// Empty selection.
|
||||
inclusiveActive = (flags & Flags.EmptyExtendsForward) === Flags.EmptyExtendsForward;
|
||||
inclusiveAnchor = (flags & Flags.EmptyExtendsBackward) === Flags.EmptyExtendsBackward;
|
||||
} else {
|
||||
setActiveOffset(array, index, endOffset);
|
||||
setAnchorOffset(array, index, startOffset);
|
||||
}
|
||||
}
|
||||
const activeIsStart = activeOffset <= anchorOffset,
|
||||
anchorIsStart = activeOffset >= anchorOffset,
|
||||
inclusiveStart = (flags & Flags.StrictStart) === 0,
|
||||
inclusiveEnd = (flags & Flags.StrictEnd) === 0;
|
||||
|
||||
/**
|
||||
* Returns the saved selections, restored in the given document.
|
||||
*/
|
||||
export function restoreArray(array: Array, document: vscode.TextDocument) {
|
||||
const selections = [] as vscode.Selection[];
|
||||
|
||||
for (let i = 0, len = array.length >> 1; i < len; i++) {
|
||||
selections.push(restore(array, i, document));
|
||||
inclusiveActive = activeIsStart ? !inclusiveStart : inclusiveEnd;
|
||||
inclusiveAnchor = anchorIsStart ? !inclusiveStart : inclusiveEnd;
|
||||
}
|
||||
|
||||
return selections;
|
||||
}
|
||||
for (let i = 0, len = changes.length; i < len; i++) {
|
||||
const change = changes[i],
|
||||
diff = change.text.length - change.rangeLength,
|
||||
offset = change.rangeOffset + change.rangeLength;
|
||||
|
||||
/**
|
||||
* Returns the saved selections, restored in the given document, skipping
|
||||
* empty selections.
|
||||
*/
|
||||
export function restoreNonEmpty(array: Array, document: vscode.TextDocument) {
|
||||
const selections = [] as vscode.Selection[];
|
||||
|
||||
for (let i = 0, len = array.length >> 1; i < len; i++) {
|
||||
const anchorOffset = array[i << 1],
|
||||
activeOffset = array[(i << 1) | 1];
|
||||
|
||||
if (anchorOffset === activeOffset) {
|
||||
continue;
|
||||
if (offset < activeOffset || (inclusiveActive && offset === activeOffset)) {
|
||||
activeOffset += diff;
|
||||
}
|
||||
|
||||
selections.push(
|
||||
new vscode.Selection(document.positionAt(anchorOffset), document.positionAt(activeOffset)),
|
||||
);
|
||||
if (offset < anchorOffset || (inclusiveAnchor && offset === anchorOffset)) {
|
||||
anchorOffset += diff;
|
||||
}
|
||||
}
|
||||
|
||||
return selections;
|
||||
array[i] = anchorOffset;
|
||||
array[i + 1] = activeOffset;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A set of tracked selections.
|
||||
*/
|
||||
export class Set implements vscode.Disposable {
|
||||
private readonly _onDisposed = new vscode.EventEmitter<this>();
|
||||
private readonly _selections: Array;
|
||||
private readonly _onDidChangeTextDocumentSubscription: vscode.Disposable;
|
||||
|
||||
public get onDisposed() {
|
||||
return this._onDisposed.event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the underlying selection to reflect a change in its document.
|
||||
*/
|
||||
export function updateAfterDocumentChanged(
|
||||
array: Array,
|
||||
changes: readonly vscode.TextDocumentContentChangeEvent[],
|
||||
flags: TrackedSelection.Flags,
|
||||
public constructor(
|
||||
selections: Array,
|
||||
public readonly document: vscode.TextDocument,
|
||||
public flags = Flags.Inclusive,
|
||||
) {
|
||||
for (let i = 0, len = array.length; i < len; i += 2) {
|
||||
let anchorOffset = array[i],
|
||||
activeOffset = array[i + 1],
|
||||
inclusiveActive: boolean,
|
||||
inclusiveAnchor: boolean;
|
||||
ArgumentError.validate("selections", selections.length > 0, "selections cannot be empty");
|
||||
|
||||
if (anchorOffset === activeOffset) {
|
||||
// Empty selection.
|
||||
inclusiveActive = (flags & Flags.EmptyExtendsForward) === Flags.EmptyExtendsForward;
|
||||
inclusiveAnchor = (flags & Flags.EmptyExtendsBackward) === Flags.EmptyExtendsBackward;
|
||||
} else {
|
||||
const activeIsStart = activeOffset <= anchorOffset,
|
||||
anchorIsStart = activeOffset >= anchorOffset,
|
||||
inclusiveStart = (flags & Flags.StrictStart) === 0,
|
||||
inclusiveEnd = (flags & Flags.StrictEnd) === 0;
|
||||
this._selections = selections;
|
||||
this._onDidChangeTextDocumentSubscription =
|
||||
vscode.workspace.onDidChangeTextDocument(this.updateAfterDocumentChanged, this);
|
||||
}
|
||||
|
||||
inclusiveActive = activeIsStart ? !inclusiveStart : inclusiveEnd;
|
||||
inclusiveAnchor = anchorIsStart ? !inclusiveStart : inclusiveEnd;
|
||||
}
|
||||
public addArray(array: Array) {
|
||||
(this._selections as number[]).push(...array);
|
||||
|
||||
for (let i = 0, len = changes.length; i < len; i++) {
|
||||
const change = changes[i],
|
||||
diff = change.text.length - change.rangeLength,
|
||||
offset = change.rangeOffset + change.rangeLength;
|
||||
return this;
|
||||
}
|
||||
|
||||
if (offset < activeOffset || (inclusiveActive && offset === activeOffset)) {
|
||||
activeOffset += diff;
|
||||
}
|
||||
public addSelections(selections: readonly vscode.Selection[]) {
|
||||
return this.addArray(fromArray(selections, this.document));
|
||||
}
|
||||
|
||||
if (offset < anchorOffset || (inclusiveAnchor && offset === anchorOffset)) {
|
||||
anchorOffset += diff;
|
||||
}
|
||||
}
|
||||
|
||||
array[i] = anchorOffset;
|
||||
array[i + 1] = activeOffset;
|
||||
}
|
||||
public addSelection(selection: vscode.Selection) {
|
||||
return this.addArray(fromArray([selection], this.document));
|
||||
}
|
||||
|
||||
/**
|
||||
* A set of `TrackedSelection`s.
|
||||
* Updates the tracked selections to reflect a change in their document.
|
||||
*
|
||||
* @return whether the change was applied.
|
||||
*/
|
||||
export class Set implements vscode.Disposable {
|
||||
private readonly _onDisposed = new vscode.EventEmitter<this>();
|
||||
private readonly _selections: Array;
|
||||
private readonly _onDidChangeTextDocumentSubscription: vscode.Disposable;
|
||||
|
||||
public get onDisposed() {
|
||||
return this._onDisposed.event;
|
||||
protected updateAfterDocumentChanged(e: vscode.TextDocumentChangeEvent) {
|
||||
if (e.document !== this.document || e.contentChanges.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public constructor(
|
||||
selections: Array,
|
||||
public readonly document: vscode.TextDocument,
|
||||
public flags = Flags.Inclusive,
|
||||
) {
|
||||
ArgumentError.validate("selections", selections.length > 0, "selections cannot be empty");
|
||||
updateAfterDocumentChanged(this._selections, e.contentChanges, this.flags);
|
||||
|
||||
this._selections = selections;
|
||||
this._onDidChangeTextDocumentSubscription =
|
||||
vscode.workspace.onDidChangeTextDocument(this.updateAfterDocumentChanged, this);
|
||||
}
|
||||
|
||||
public addArray(array: Array) {
|
||||
(this._selections as number[]).push(...array);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public addSelections(selections: readonly vscode.Selection[]) {
|
||||
return this.addArray(TrackedSelection.fromArray(selections, this.document));
|
||||
}
|
||||
|
||||
public addSelection(selection: vscode.Selection) {
|
||||
return this.addArray(TrackedSelection.fromArray([selection], this.document));
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the tracked selections to reflect a change in their document.
|
||||
*
|
||||
* @return whether the change was applied.
|
||||
*/
|
||||
protected updateAfterDocumentChanged(e: vscode.TextDocumentChangeEvent) {
|
||||
if (e.document !== this.document || e.contentChanges.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
updateAfterDocumentChanged(this._selections, e.contentChanges, this.flags);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public restore() {
|
||||
return restoreArray(this._selections, this.document);
|
||||
}
|
||||
|
||||
public restoreNonEmpty() {
|
||||
return restoreNonEmpty(this._selections, this.document);
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
this._onDisposed.fire(this);
|
||||
this._onDisposed.dispose();
|
||||
this._onDidChangeTextDocumentSubscription.dispose();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* A `TrackedSelection.Set` that displays active selections using some given
|
||||
* style.
|
||||
*/
|
||||
export class StyledSet extends Set {
|
||||
private readonly _decorationType: vscode.TextEditorDecorationType;
|
||||
private readonly _onDidEditorVisibilityChangeSubscription: vscode.Disposable;
|
||||
public restore() {
|
||||
return restoreArray(this._selections, this.document);
|
||||
}
|
||||
|
||||
public constructor(
|
||||
selections: Array,
|
||||
public readonly editorState: PerEditorState,
|
||||
renderOptions: vscode.DecorationRenderOptions,
|
||||
) {
|
||||
super(
|
||||
selections, editorState.editor.document, rangeBehaviorToFlags(renderOptions.rangeBehavior));
|
||||
public restoreNonEmpty() {
|
||||
return restoreNonEmpty(this._selections, this.document);
|
||||
}
|
||||
|
||||
this._decorationType = vscode.window.createTextEditorDecorationType(renderOptions);
|
||||
this._onDidEditorVisibilityChangeSubscription = editorState.onVisibilityDidChange((e) =>
|
||||
e.isVisible && this._updateDecorations());
|
||||
this._updateDecorations();
|
||||
}
|
||||
public dispose() {
|
||||
this._onDisposed.fire(this);
|
||||
this._onDisposed.dispose();
|
||||
this._onDidChangeTextDocumentSubscription.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public addArray(selections: Array) {
|
||||
super.addArray(selections);
|
||||
/**
|
||||
* A {@link Set} that displays active selections using some given style.
|
||||
*/
|
||||
export class StyledSet extends Set {
|
||||
private readonly _decorationType: vscode.TextEditorDecorationType;
|
||||
private readonly _onDidEditorVisibilityChangeSubscription: vscode.Disposable;
|
||||
|
||||
for (let i = 0, len = selections.length; i < len; i += 2) {
|
||||
if (selections[i] !== selections[i + 1]) {
|
||||
this._updateDecorations();
|
||||
break;
|
||||
}
|
||||
public constructor(
|
||||
selections: Array,
|
||||
public readonly editorState: PerEditorState,
|
||||
renderOptions: vscode.DecorationRenderOptions,
|
||||
) {
|
||||
super(
|
||||
selections, editorState.editor.document, rangeBehaviorToFlags(renderOptions.rangeBehavior));
|
||||
|
||||
this._decorationType = vscode.window.createTextEditorDecorationType(renderOptions);
|
||||
this._onDidEditorVisibilityChangeSubscription = editorState.onVisibilityDidChange((e) =>
|
||||
e.isVisible && this._updateDecorations());
|
||||
this._updateDecorations();
|
||||
}
|
||||
|
||||
public addArray(selections: Array) {
|
||||
super.addArray(selections);
|
||||
|
||||
for (let i = 0, len = selections.length; i < len; i += 2) {
|
||||
if (selections[i] !== selections[i + 1]) {
|
||||
this._updateDecorations();
|
||||
break;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
protected updateAfterDocumentChanged(e: vscode.TextDocumentChangeEvent) {
|
||||
if (!super.updateAfterDocumentChanged(e)) {
|
||||
return false;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
this._updateDecorations();
|
||||
|
||||
return true;
|
||||
protected updateAfterDocumentChanged(e: vscode.TextDocumentChangeEvent) {
|
||||
if (!super.updateAfterDocumentChanged(e)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
super.dispose();
|
||||
this._updateDecorations();
|
||||
|
||||
this._decorationType.dispose();
|
||||
this._onDidEditorVisibilityChangeSubscription.dispose();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private _updateDecorations() {
|
||||
this.editorState.editor.setDecorations(this._decorationType, this.restoreNonEmpty());
|
||||
}
|
||||
public dispose() {
|
||||
super.dispose();
|
||||
|
||||
this._decorationType.dispose();
|
||||
this._onDidEditorVisibilityChangeSubscription.dispose();
|
||||
}
|
||||
|
||||
private _updateDecorations() {
|
||||
this.editorState.editor.setDecorations(this._decorationType, this.restoreNonEmpty());
|
||||
}
|
||||
}
|
||||
|
||||
function rangeBehaviorToFlags(rangeBehavior: vscode.DecorationRangeBehavior | undefined) {
|
||||
switch (rangeBehavior) {
|
||||
case vscode.DecorationRangeBehavior.ClosedOpen:
|
||||
return TrackedSelection.Flags.StrictStart;
|
||||
return Flags.StrictStart;
|
||||
|
||||
case vscode.DecorationRangeBehavior.OpenClosed:
|
||||
return TrackedSelection.Flags.StrictEnd;
|
||||
return Flags.StrictEnd;
|
||||
|
||||
case vscode.DecorationRangeBehavior.ClosedClosed:
|
||||
return TrackedSelection.Flags.Strict;
|
||||
return Flags.Strict;
|
||||
|
||||
default:
|
||||
return TrackedSelection.Flags.Inclusive;
|
||||
return Flags.Inclusive;
|
||||
}
|
||||
}
|
||||
|
340
test/suite/api.test.ts
generated
340
test/suite/api.test.ts
generated
@ -3,7 +3,7 @@ import * as assert from "assert";
|
||||
import * as vscode from "vscode";
|
||||
|
||||
import { expect, ExpectedDocument } from "./utils";
|
||||
import { Context, deindentLines, EmptySelectionsError, indentLines, insert, isPosition, isRange, isSelection, joinLines, moveWhile, NotASelectionError, Positions, replace, rotate, search, Select, SelectionBehavior, Selections, text } from "../../src/api";
|
||||
import { Context, deindentLines, edit, EmptySelectionsError, indentLines, insert, insertByIndex, isPosition, isRange, isSelection, joinLines, mapActive, mapBoth, mapEnd, mapStart, moveWhileBackward, moveWhileForward, moveWithBackward, moveWithForward, NotASelectionError, pipe, Positions, replace, replaceByIndex, rotate, rotateContents, rotateSelections, searchBackward, searchForward, Select, SelectionBehavior, Selections, text } from "../../src/api";
|
||||
import { Extension } from "../../src/state/extension";
|
||||
|
||||
function setSelectionBehavior(selectionBehavior: SelectionBehavior) {
|
||||
@ -89,6 +89,32 @@ suite("API tests", function () {
|
||||
// No expected end document.
|
||||
});
|
||||
|
||||
test("function edit", async function () {
|
||||
const editorState = extension.editors.getState(editor)!,
|
||||
context = new Context(editorState, cancellationToken),
|
||||
before = ExpectedDocument.parseIndented(14, String.raw`
|
||||
hello world
|
||||
^^^^^ 0
|
||||
`),
|
||||
after = ExpectedDocument.parseIndented(14, String.raw`
|
||||
heo world
|
||||
^^^ 0
|
||||
`);
|
||||
|
||||
await before.apply(editor);
|
||||
|
||||
await context.runAsync(async () => {
|
||||
await edit((editBuilder) => {
|
||||
const start = new vscode.Position(0, 2),
|
||||
end = new vscode.Position(0, 4);
|
||||
|
||||
editBuilder.delete(new vscode.Range(start, end));
|
||||
});
|
||||
});
|
||||
|
||||
after.assertEquals(editor);
|
||||
});
|
||||
|
||||
test("function selectionsToCharacterMode", async function () {
|
||||
const editorState = extension.editors.getState(editor)!,
|
||||
context = new Context(editorState, cancellationToken),
|
||||
@ -247,6 +273,141 @@ suite("API tests", function () {
|
||||
// No expected end document.
|
||||
});
|
||||
|
||||
test("function mapStart", async function () {
|
||||
const editorState = extension.editors.getState(editor)!,
|
||||
context = new Context(editorState, cancellationToken);
|
||||
|
||||
// No setup needed.
|
||||
|
||||
await context.runAsync(async () => {
|
||||
const p1 = new vscode.Position(0, 0),
|
||||
p2 = new vscode.Position(0, 1);
|
||||
|
||||
assert.deepStrictEqual(
|
||||
mapStart(p1, (x) => x.translate(1)),
|
||||
new vscode.Position(1, 0),
|
||||
);
|
||||
assert.deepStrictEqual(
|
||||
mapStart(new vscode.Range(p1, p2), (x) => x.translate(1)),
|
||||
new vscode.Range(p2, new vscode.Position(1, 0)),
|
||||
);
|
||||
assert.deepStrictEqual(
|
||||
mapStart(new vscode.Selection(p1, p2), (x) => x.translate(1)),
|
||||
new vscode.Selection(new vscode.Position(1, 0), p2),
|
||||
);
|
||||
assert.deepStrictEqual(
|
||||
mapStart(new vscode.Selection(p2, p1), (x) => x.translate(1)),
|
||||
new vscode.Selection(p2, new vscode.Position(1, 0)),
|
||||
);
|
||||
});
|
||||
|
||||
// No expected end document.
|
||||
});
|
||||
|
||||
test("function mapEnd", async function () {
|
||||
const editorState = extension.editors.getState(editor)!,
|
||||
context = new Context(editorState, cancellationToken);
|
||||
|
||||
// No setup needed.
|
||||
|
||||
await context.runAsync(async () => {
|
||||
const p1 = new vscode.Position(0, 0),
|
||||
p2 = new vscode.Position(0, 1);
|
||||
|
||||
assert.deepStrictEqual(
|
||||
mapEnd(p1, (x) => x.translate(1)),
|
||||
new vscode.Position(1, 0),
|
||||
);
|
||||
assert.deepStrictEqual(
|
||||
mapEnd(new vscode.Range(p1, p2), (x) => x.translate(1)),
|
||||
new vscode.Range(p1, new vscode.Position(1, 1)),
|
||||
);
|
||||
assert.deepStrictEqual(
|
||||
mapEnd(new vscode.Selection(p1, p2), (x) => x.translate(1)),
|
||||
new vscode.Selection(p1, new vscode.Position(1, 1)),
|
||||
);
|
||||
assert.deepStrictEqual(
|
||||
mapEnd(new vscode.Selection(p2, p1), (x) => x.translate(1)),
|
||||
new vscode.Selection(new vscode.Position(1, 1), p1),
|
||||
);
|
||||
});
|
||||
|
||||
// No expected end document.
|
||||
});
|
||||
|
||||
test("function mapActive", async function () {
|
||||
const editorState = extension.editors.getState(editor)!,
|
||||
context = new Context(editorState, cancellationToken);
|
||||
|
||||
// No setup needed.
|
||||
|
||||
await context.runAsync(async () => {
|
||||
const p1 = new vscode.Position(0, 0),
|
||||
p2 = new vscode.Position(0, 1);
|
||||
|
||||
assert.deepStrictEqual(
|
||||
mapActive(p1, (x) => x.translate(1)),
|
||||
new vscode.Position(1, 0),
|
||||
);
|
||||
assert.deepStrictEqual(
|
||||
mapActive(new vscode.Selection(p1, p2), (x) => x.translate(1)),
|
||||
new vscode.Selection(p1, new vscode.Position(1, 1)),
|
||||
);
|
||||
});
|
||||
|
||||
// No expected end document.
|
||||
});
|
||||
|
||||
test("function mapBoth", async function () {
|
||||
const editorState = extension.editors.getState(editor)!,
|
||||
context = new Context(editorState, cancellationToken);
|
||||
|
||||
// No setup needed.
|
||||
|
||||
await context.runAsync(async () => {
|
||||
const p1 = new vscode.Position(0, 0),
|
||||
p2 = new vscode.Position(0, 1);
|
||||
|
||||
assert.deepStrictEqual(
|
||||
mapBoth(p1, (x) => x.translate(1)),
|
||||
new vscode.Position(1, 0),
|
||||
);
|
||||
assert.deepStrictEqual(
|
||||
mapBoth(new vscode.Range(p1, p2), (x) => x.translate(1)),
|
||||
new vscode.Range(new vscode.Position(1, 0), new vscode.Position(1, 1)),
|
||||
);
|
||||
assert.deepStrictEqual(
|
||||
mapBoth(new vscode.Selection(p1, p2), (x) => x.translate(1)),
|
||||
new vscode.Selection(new vscode.Position(1, 0), new vscode.Position(1, 1)),
|
||||
);
|
||||
assert.deepStrictEqual(
|
||||
mapBoth(new vscode.Selection(p2, p1), (x) => x.translate(1)),
|
||||
new vscode.Selection(new vscode.Position(1, 1), new vscode.Position(1, 0)),
|
||||
);
|
||||
});
|
||||
|
||||
// No expected end document.
|
||||
});
|
||||
|
||||
test("function pipe", async function () {
|
||||
const editorState = extension.editors.getState(editor)!,
|
||||
context = new Context(editorState, cancellationToken);
|
||||
|
||||
// No setup needed.
|
||||
|
||||
await context.runAsync(async () => {
|
||||
const doubleNumbers = pipe((n) => typeof n === "number" ? n : undefined,
|
||||
(n) => n * 2);
|
||||
|
||||
assert.deepStrictEqual(
|
||||
doubleNumbers([1, "a", 2, null, 3, {}]),
|
||||
[2, 4, 6],
|
||||
);
|
||||
});
|
||||
|
||||
// No expected end document.
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
suite("./src/api/edit/index.ts", function () {
|
||||
@ -276,7 +437,7 @@ suite("API tests", function () {
|
||||
after.assertEquals(editor);
|
||||
});
|
||||
|
||||
test("function byIndex", async function () {
|
||||
test("function insertByIndex", async function () {
|
||||
const editorState = extension.editors.getState(editor)!,
|
||||
context = new Context(editorState, cancellationToken),
|
||||
before = ExpectedDocument.parseIndented(14, String.raw`
|
||||
@ -295,13 +456,13 @@ suite("API tests", function () {
|
||||
await before.apply(editor);
|
||||
|
||||
await context.runAsync(async () => {
|
||||
Selections.set(await insert.byIndex(insert.Start, (i) => `${i + 1}`));
|
||||
Selections.set(await insertByIndex(insert.Start, (i) => `${i + 1}`));
|
||||
});
|
||||
|
||||
after.assertEquals(editor);
|
||||
});
|
||||
|
||||
test("function byIndex#1", async function () {
|
||||
test("function insertByIndex#1", async function () {
|
||||
const editorState = extension.editors.getState(editor)!,
|
||||
context = new Context(editorState, cancellationToken),
|
||||
before = ExpectedDocument.parseIndented(14, String.raw`
|
||||
@ -320,13 +481,13 @@ suite("API tests", function () {
|
||||
await before.apply(editor);
|
||||
|
||||
await context.runAsync(async () => {
|
||||
Selections.set(await insert.byIndex(insert.Start | insert.Select, (i) => `${i + 1}`));
|
||||
Selections.set(await insertByIndex(insert.Start | insert.Select, (i) => `${i + 1}`));
|
||||
});
|
||||
|
||||
after.assertEquals(editor);
|
||||
});
|
||||
|
||||
test("function byIndex#2", async function () {
|
||||
test("function insertByIndex#2", async function () {
|
||||
const editorState = extension.editors.getState(editor)!,
|
||||
context = new Context(editorState, cancellationToken),
|
||||
before = ExpectedDocument.parseIndented(14, String.raw`
|
||||
@ -345,13 +506,13 @@ suite("API tests", function () {
|
||||
await before.apply(editor);
|
||||
|
||||
await context.runAsync(async () => {
|
||||
Selections.set(await insert.byIndex(insert.Start | insert.Extend, (i) => `${i + 1}`));
|
||||
Selections.set(await insertByIndex(insert.Start | insert.Extend, (i) => `${i + 1}`));
|
||||
});
|
||||
|
||||
after.assertEquals(editor);
|
||||
});
|
||||
|
||||
test("function byIndex#3", async function () {
|
||||
test("function insertByIndex#3", async function () {
|
||||
const editorState = extension.editors.getState(editor)!,
|
||||
context = new Context(editorState, cancellationToken),
|
||||
before = ExpectedDocument.parseIndented(14, String.raw`
|
||||
@ -370,13 +531,13 @@ suite("API tests", function () {
|
||||
await before.apply(editor);
|
||||
|
||||
await context.runAsync(async () => {
|
||||
Selections.set(await insert.byIndex(insert.End, (i) => `${i + 1}`));
|
||||
Selections.set(await insertByIndex(insert.End, (i) => `${i + 1}`));
|
||||
});
|
||||
|
||||
after.assertEquals(editor);
|
||||
});
|
||||
|
||||
test("function byIndex#4", async function () {
|
||||
test("function insertByIndex#4", async function () {
|
||||
const editorState = extension.editors.getState(editor)!,
|
||||
context = new Context(editorState, cancellationToken),
|
||||
before = ExpectedDocument.parseIndented(14, String.raw`
|
||||
@ -395,13 +556,13 @@ suite("API tests", function () {
|
||||
await before.apply(editor);
|
||||
|
||||
await context.runAsync(async () => {
|
||||
Selections.set(await insert.byIndex(insert.End | insert.Select, (i) => `${i + 1}`));
|
||||
Selections.set(await insertByIndex(insert.End | insert.Select, (i) => `${i + 1}`));
|
||||
});
|
||||
|
||||
after.assertEquals(editor);
|
||||
});
|
||||
|
||||
test("function byIndex#5", async function () {
|
||||
test("function insertByIndex#5", async function () {
|
||||
const editorState = extension.editors.getState(editor)!,
|
||||
context = new Context(editorState, cancellationToken),
|
||||
before = ExpectedDocument.parseIndented(14, String.raw`
|
||||
@ -420,7 +581,7 @@ suite("API tests", function () {
|
||||
await before.apply(editor);
|
||||
|
||||
await context.runAsync(async () => {
|
||||
Selections.set(await insert.byIndex(insert.End | insert.Extend, (i) => `${i + 1}`));
|
||||
Selections.set(await insertByIndex(insert.End | insert.Extend, (i) => `${i + 1}`));
|
||||
});
|
||||
|
||||
after.assertEquals(editor);
|
||||
@ -451,7 +612,7 @@ suite("API tests", function () {
|
||||
after.assertEquals(editor);
|
||||
});
|
||||
|
||||
test("function byIndex#6", async function () {
|
||||
test("function replaceByIndex", async function () {
|
||||
const editorState = extension.editors.getState(editor)!,
|
||||
context = new Context(editorState, cancellationToken),
|
||||
before = ExpectedDocument.parseIndented(14, String.raw`
|
||||
@ -470,7 +631,7 @@ suite("API tests", function () {
|
||||
await before.apply(editor);
|
||||
|
||||
await context.runAsync(async () => {
|
||||
await replace.byIndex((i) => `${i + 1}`);
|
||||
await replaceByIndex((i) => `${i + 1}`);
|
||||
});
|
||||
|
||||
after.assertEquals(editor);
|
||||
@ -501,7 +662,32 @@ suite("API tests", function () {
|
||||
after.assertEquals(editor);
|
||||
});
|
||||
|
||||
test("function selectionsOnly", async function () {
|
||||
test("function rotateContents", async function () {
|
||||
const editorState = extension.editors.getState(editor)!,
|
||||
context = new Context(editorState, cancellationToken),
|
||||
before = ExpectedDocument.parseIndented(14, String.raw`
|
||||
a b c
|
||||
^ 0
|
||||
^ 1
|
||||
^ 2
|
||||
`),
|
||||
after = ExpectedDocument.parseIndented(14, String.raw`
|
||||
b c a
|
||||
^ 0
|
||||
^ 1
|
||||
^ 2
|
||||
`);
|
||||
|
||||
await before.apply(editor);
|
||||
|
||||
await context.runAsync(async () => {
|
||||
await rotateContents(1);
|
||||
});
|
||||
|
||||
after.assertEquals(editor);
|
||||
});
|
||||
|
||||
test("function rotateSelections", async function () {
|
||||
const editorState = extension.editors.getState(editor)!,
|
||||
context = new Context(editorState, cancellationToken),
|
||||
before = ExpectedDocument.parseIndented(14, String.raw`
|
||||
@ -520,7 +706,7 @@ suite("API tests", function () {
|
||||
await before.apply(editor);
|
||||
|
||||
await context.runAsync(async () => {
|
||||
rotate.selectionsOnly(1);
|
||||
rotateSelections(1);
|
||||
});
|
||||
|
||||
after.assertEquals(editor);
|
||||
@ -530,7 +716,7 @@ suite("API tests", function () {
|
||||
|
||||
suite("./src/api/search/index.ts", function () {
|
||||
|
||||
test("function backward", async function () {
|
||||
test("function searchBackward", async function () {
|
||||
const editorState = extension.editors.getState(editor)!,
|
||||
context = new Context(editorState, cancellationToken),
|
||||
before = ExpectedDocument.parseIndented(14, String.raw`
|
||||
@ -540,23 +726,23 @@ suite("API tests", function () {
|
||||
await before.apply(editor);
|
||||
|
||||
await context.runAsync(async () => {
|
||||
const [p1, [t1]] = search.backward(/\w/, new vscode.Position(0, 1))!;
|
||||
const [p1, [t1]] = searchBackward(/\w/, new vscode.Position(0, 1))!;
|
||||
|
||||
assert.deepStrictEqual(p1, new vscode.Position(0, 0));
|
||||
assert.strictEqual(t1, "a");
|
||||
|
||||
const [p2, [t2]] = search.backward(/\w/, new vscode.Position(0, 2))!;
|
||||
const [p2, [t2]] = searchBackward(/\w/, new vscode.Position(0, 2))!;
|
||||
|
||||
assert.deepStrictEqual(p2, new vscode.Position(0, 1));
|
||||
assert.strictEqual(t2, "b");
|
||||
|
||||
const [p3, [t3]] = search.backward(/\w+/, new vscode.Position(0, 2))!;
|
||||
const [p3, [t3]] = searchBackward(/\w+/, new vscode.Position(0, 2))!;
|
||||
|
||||
assert.deepStrictEqual(p3, new vscode.Position(0, 0));
|
||||
assert.strictEqual(t3, "ab");
|
||||
|
||||
assert.strictEqual(
|
||||
search.backward(/\w/, new vscode.Position(0, 0)),
|
||||
searchBackward(/\w/, new vscode.Position(0, 0)),
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
@ -564,7 +750,7 @@ suite("API tests", function () {
|
||||
// No expected end document.
|
||||
});
|
||||
|
||||
test("function forward", async function () {
|
||||
test("function searchForward", async function () {
|
||||
const editorState = extension.editors.getState(editor)!,
|
||||
context = new Context(editorState, cancellationToken),
|
||||
before = ExpectedDocument.parseIndented(14, String.raw`
|
||||
@ -574,23 +760,23 @@ suite("API tests", function () {
|
||||
await before.apply(editor);
|
||||
|
||||
await context.runAsync(async () => {
|
||||
const [p1, [t1]] = search.forward(/\w/, new vscode.Position(0, 0))!;
|
||||
const [p1, [t1]] = searchForward(/\w/, new vscode.Position(0, 0))!;
|
||||
|
||||
assert.deepStrictEqual(p1, new vscode.Position(0, 0));
|
||||
assert.strictEqual(t1, "a");
|
||||
|
||||
const [p2, [t2]] = search.forward(/\w/, new vscode.Position(0, 1))!;
|
||||
const [p2, [t2]] = searchForward(/\w/, new vscode.Position(0, 1))!;
|
||||
|
||||
assert.deepStrictEqual(p2, new vscode.Position(0, 1));
|
||||
assert.strictEqual(t2, "b");
|
||||
|
||||
const [p3, [t3]] = search.forward(/\w+/, new vscode.Position(0, 1))!;
|
||||
const [p3, [t3]] = searchForward(/\w+/, new vscode.Position(0, 1))!;
|
||||
|
||||
assert.deepStrictEqual(p3, new vscode.Position(0, 1));
|
||||
assert.strictEqual(t3, "bc");
|
||||
|
||||
assert.strictEqual(
|
||||
search.forward(/\w/, new vscode.Position(0, 3)),
|
||||
searchForward(/\w/, new vscode.Position(0, 3)),
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
@ -816,7 +1002,49 @@ suite("API tests", function () {
|
||||
|
||||
suite("./src/api/search/move.ts", function () {
|
||||
|
||||
test("function backward", async function () {
|
||||
test("function moveWithBackward", async function () {
|
||||
const editorState = extension.editors.getState(editor)!,
|
||||
context = new Context(editorState, cancellationToken),
|
||||
before = ExpectedDocument.parseIndented(14, String.raw`
|
||||
1234578
|
||||
`);
|
||||
|
||||
await before.apply(editor);
|
||||
|
||||
await context.runAsync(async () => {
|
||||
// Go backward as long as the previous character is equal to the current
|
||||
// character minus one.
|
||||
assert.deepStrictEqual(
|
||||
moveWithBackward((c, i) => +c === i - 1 ? +c : undefined,
|
||||
9, new vscode.Position(0, 7)),
|
||||
new vscode.Position(0, 5),
|
||||
);
|
||||
});
|
||||
|
||||
// No expected end document.
|
||||
});
|
||||
|
||||
test("function moveWithForward", async function () {
|
||||
const editorState = extension.editors.getState(editor)!,
|
||||
context = new Context(editorState, cancellationToken),
|
||||
before = ExpectedDocument.parseIndented(14, String.raw`
|
||||
1234578
|
||||
`);
|
||||
|
||||
await before.apply(editor);
|
||||
|
||||
await context.runAsync(async () => {
|
||||
assert.deepStrictEqual(
|
||||
moveWithForward((c, i) => +c === i + 1 ? +c : undefined,
|
||||
0, new vscode.Position(0, 0)),
|
||||
new vscode.Position(0, 5),
|
||||
);
|
||||
});
|
||||
|
||||
// No expected end document.
|
||||
});
|
||||
|
||||
test("function moveWhileBackward", async function () {
|
||||
const editorState = extension.editors.getState(editor)!,
|
||||
context = new Context(editorState, cancellationToken),
|
||||
before = ExpectedDocument.parseIndented(14, String.raw`
|
||||
@ -827,17 +1055,17 @@ suite("API tests", function () {
|
||||
|
||||
await context.runAsync(async () => {
|
||||
assert.deepStrictEqual(
|
||||
moveWhile.backward((c) => /\w/.test(c), new vscode.Position(0, 3)),
|
||||
moveWhileBackward((c) => /\w/.test(c), new vscode.Position(0, 3)),
|
||||
new vscode.Position(0, 0),
|
||||
);
|
||||
|
||||
assert.deepStrictEqual(
|
||||
moveWhile.backward((c) => c === "c", new vscode.Position(0, 3)),
|
||||
moveWhileBackward((c) => c === "c", new vscode.Position(0, 3)),
|
||||
new vscode.Position(0, 2),
|
||||
);
|
||||
|
||||
assert.deepStrictEqual(
|
||||
moveWhile.backward((c) => c === "b", new vscode.Position(0, 3)),
|
||||
moveWhileBackward((c) => c === "b", new vscode.Position(0, 3)),
|
||||
new vscode.Position(0, 3),
|
||||
);
|
||||
});
|
||||
@ -845,7 +1073,7 @@ suite("API tests", function () {
|
||||
// No expected end document.
|
||||
});
|
||||
|
||||
test("function forward", async function () {
|
||||
test("function moveWhileForward", async function () {
|
||||
const editorState = extension.editors.getState(editor)!,
|
||||
context = new Context(editorState, cancellationToken),
|
||||
before = ExpectedDocument.parseIndented(14, String.raw`
|
||||
@ -856,17 +1084,17 @@ suite("API tests", function () {
|
||||
|
||||
await context.runAsync(async () => {
|
||||
assert.deepStrictEqual(
|
||||
moveWhile.forward((c) => /\w/.test(c), new vscode.Position(0, 0)),
|
||||
moveWhileForward((c) => /\w/.test(c), new vscode.Position(0, 0)),
|
||||
new vscode.Position(0, 3),
|
||||
);
|
||||
|
||||
assert.deepStrictEqual(
|
||||
moveWhile.forward((c) => c === "a", new vscode.Position(0, 0)),
|
||||
moveWhileForward((c) => c === "a", new vscode.Position(0, 0)),
|
||||
new vscode.Position(0, 1),
|
||||
);
|
||||
|
||||
assert.deepStrictEqual(
|
||||
moveWhile.forward((c) => c === "b", new vscode.Position(0, 0)),
|
||||
moveWhileForward((c) => c === "b", new vscode.Position(0, 0)),
|
||||
new vscode.Position(0, 0),
|
||||
);
|
||||
});
|
||||
@ -962,6 +1190,48 @@ suite("API tests", function () {
|
||||
// No expected end document.
|
||||
});
|
||||
|
||||
test("function map", async function () {
|
||||
const editorState = extension.editors.getState(editor)!,
|
||||
context = new Context(editorState, cancellationToken),
|
||||
before = ExpectedDocument.parseIndented(14, String.raw`
|
||||
foo 123
|
||||
^^^ 0
|
||||
^^^ 1
|
||||
`);
|
||||
|
||||
await before.apply(editor);
|
||||
|
||||
await context.runAsync(async () => {
|
||||
assert.deepStrictEqual(
|
||||
Selections.map((text) => isNaN(+text) ? undefined : +text),
|
||||
[123],
|
||||
);
|
||||
});
|
||||
|
||||
// No expected end document.
|
||||
});
|
||||
|
||||
test("function map#1", async function () {
|
||||
const editorState = extension.editors.getState(editor)!,
|
||||
context = new Context(editorState, cancellationToken),
|
||||
before = ExpectedDocument.parseIndented(14, String.raw`
|
||||
foo 123
|
||||
^^^ 0
|
||||
^^^ 1
|
||||
`);
|
||||
|
||||
await before.apply(editor);
|
||||
|
||||
await context.runAsync(async () => {
|
||||
assert.deepStrictEqual(
|
||||
await Selections.map(async (text) => isNaN(+text) ? undefined : +text),
|
||||
[123],
|
||||
);
|
||||
});
|
||||
|
||||
// No expected end document.
|
||||
});
|
||||
|
||||
test("function update", async function () {
|
||||
const editorState = extension.editors.getState(editor)!,
|
||||
context = new Context(editorState, cancellationToken),
|
||||
|
Loading…
Reference in New Issue
Block a user