tabby/clients/tabby-agent/src/utils.ts

112 lines
3.1 KiB
TypeScript
Raw Normal View History

export function splitLines(input: string) {
const lines = input.match(/.*(?:$|\r?\n)/g).filter(Boolean); // Split lines and keep newline character
if (lines.length > 0 && lines[lines.length - 1].endsWith("\n")) {
// Keep last empty line
lines.push("");
}
return lines;
}
2023-05-29 02:09:44 +00:00
export function splitWords(input: string) {
return input.match(/\w+|\W+/g).filter(Boolean); // Split consecutive words and non-words
}
export function isBlank(input: string) {
return input.trim().length === 0;
}
export const autoClosingPairs = [
["(", ")"],
["[", "]"],
["{", "}"],
["'", "'"],
['"', '"'],
["`", "`"],
];
export const autoClosingPairOpenings = autoClosingPairs.map((pair) => pair[0]);
export const autoClosingPairClosings = autoClosingPairs.map((pair) => pair[1]);
// FIXME: This function is not good enough, it can not handle escaped characters.
export function findUnpairedAutoClosingChars(input: string): string {
const stack: string[] = [];
for (const char of input) {
[
["(", ")"],
["[", "]"],
["{", "}"],
].forEach((pair) => {
if (char === pair[1]) {
if (stack.length > 0 && stack[stack.length - 1] === pair[0]) {
stack.pop();
} else {
stack.push(char);
}
}
});
if ("([{".includes(char)) {
stack.push(char);
}
["'", '"', "`"].forEach((quote) => {
if (char === quote) {
if (stack.length > 0 && stack.includes(quote)) {
stack.splice(stack.lastIndexOf(quote), stack.length - stack.lastIndexOf(quote));
} else {
stack.push(char);
}
}
});
}
return stack.join("");
}
// Using string levenshtein distance is not good, because variable name may create a large distance.
// Such as distance is 9 between `const fooFooFoo = 1;` and `const barBarBar = 1;`, but maybe 1 is enough.
// May be better to count distance based on words instead of characters.
import * as levenshtein from "fast-levenshtein";
export function calcDistance(a: string, b: string) {
return levenshtein.get(a, b);
}
// Polyfill for AbortSignal.any(signals) which added in Node.js v20.
export function abortSignalFromAnyOf(signals: AbortSignal[]) {
const controller = new AbortController();
for (const signal of signals) {
if (signal?.aborted) {
controller.abort(signal.reason);
return signal;
}
signal?.addEventListener("abort", () => controller.abort(signal.reason), {
signal: controller.signal,
2023-05-29 02:09:44 +00:00
});
}
return controller.signal;
}
// Http Error
export class HttpError extends Error {
status: number;
statusText: string;
response: Response;
constructor(response: Response) {
super(`${response.status} ${response.statusText}`);
this.name = "HttpError";
this.status = response.status;
this.statusText = response.statusText;
this.response = response;
}
}
export function isTimeoutError(error: any) {
return (
(error instanceof Error && error.name === "TimeoutError") ||
(error instanceof HttpError && [408, 499].indexOf(error.status) !== -1)
);
}
export function isCanceledError(error: any) {
return error instanceof Error && error.name === "AbortError";
2023-05-29 02:09:44 +00:00
}