mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-07-17 10:11:28 +02:00
Fix URL autocomplete replacement (#510)
This commit is contained in:
@@ -39,6 +39,7 @@ import { BinaryFileEditor } from "./BinaryFileEditor";
|
||||
import { ConfirmLargeRequestBody } from "./ConfirmLargeRequestBody";
|
||||
import { CountBadge } from "./core/CountBadge";
|
||||
import type { GenericCompletionConfig } from "./core/Editor/genericCompletion";
|
||||
import { getUrlCompletionConfig } from "./core/Editor/url/completion";
|
||||
import { Editor } from "./core/Editor/LazyEditor";
|
||||
import { InlineCode } from "@yaakapp-internal/ui";
|
||||
import type { Pair } from "./core/PairEditor";
|
||||
@@ -285,16 +286,7 @@ export function HttpRequestPane({ style, fullHeight, className, activeRequest }:
|
||||
const autocompleteUrls = useAtomValue(memoNotActiveRequestUrlsAtom);
|
||||
|
||||
const autocomplete: GenericCompletionConfig = useMemo(
|
||||
() => ({
|
||||
minMatch: 3,
|
||||
options:
|
||||
autocompleteUrls.length > 0
|
||||
? autocompleteUrls
|
||||
: [
|
||||
{ label: "http://", type: "constant" },
|
||||
{ label: "https://", type: "constant" },
|
||||
],
|
||||
}),
|
||||
() => getUrlCompletionConfig(autocompleteUrls),
|
||||
[autocompleteUrls],
|
||||
);
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ import { prepareImportQuerystring } from "../lib/prepareImportQuerystring";
|
||||
import { resolvedModelName } from "../lib/resolvedModelName";
|
||||
import { CountBadge } from "./core/CountBadge";
|
||||
import type { GenericCompletionConfig } from "./core/Editor/genericCompletion";
|
||||
import { getUrlCompletionConfig } from "./core/Editor/url/completion";
|
||||
import { Editor } from "./core/Editor/LazyEditor";
|
||||
import { IconButton } from "./core/IconButton";
|
||||
import type { Pair } from "./core/PairEditor";
|
||||
@@ -130,16 +131,7 @@ export function WebsocketRequestPane({ style, fullHeight, className, activeReque
|
||||
const autocompleteUrls = useAtomValue(memoNotActiveRequestUrlsAtom);
|
||||
|
||||
const autocomplete: GenericCompletionConfig = useMemo(
|
||||
() => ({
|
||||
minMatch: 3,
|
||||
options:
|
||||
autocompleteUrls.length > 0
|
||||
? autocompleteUrls
|
||||
: [
|
||||
{ label: "http://", type: "constant" },
|
||||
{ label: "https://", type: "constant" },
|
||||
],
|
||||
}),
|
||||
() => getUrlCompletionConfig(autocompleteUrls),
|
||||
[autocompleteUrls],
|
||||
);
|
||||
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import type { CompletionContext } from "@codemirror/autocomplete";
|
||||
import type { Completion, CompletionContext } from "@codemirror/autocomplete";
|
||||
import type { GenericCompletionOption } from "@yaakapp-internal/plugins";
|
||||
import { defaultBoost } from "./twig/completion";
|
||||
|
||||
export type GenericCompletion = GenericCompletionOption & {
|
||||
apply?: Completion["apply"];
|
||||
};
|
||||
|
||||
export interface GenericCompletionConfig {
|
||||
minMatch?: number;
|
||||
options: GenericCompletionOption[];
|
||||
options: GenericCompletion[];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import type { Completion } from "@codemirror/autocomplete";
|
||||
import { EditorState, type TransactionSpec } from "@codemirror/state";
|
||||
import type { EditorView } from "@codemirror/view";
|
||||
import { describe, expect, test } from "vite-plus/test";
|
||||
import { applyUrlCompletion, getUrlCompletionConfig } from "./completion";
|
||||
|
||||
describe("applyUrlCompletion", () => {
|
||||
test("consumes an existing protocol suffix and preserves the rest of the URL", () => {
|
||||
expect(applyCompletion("http://rickandmortyapi.com/api/character", "http://", 4)).toBe(
|
||||
"http://rickandmortyapi.com/api/character",
|
||||
);
|
||||
});
|
||||
|
||||
test("inserts a protocol when there is no existing suffix", () => {
|
||||
expect(applyCompletion("htt", "http://", 3)).toBe("http://");
|
||||
});
|
||||
|
||||
test("replaces the full URL when accepting a saved URL", () => {
|
||||
expect(applyCompletion("htt://old.example/path", "https://new.example/api", 3)).toBe(
|
||||
"https://new.example/api",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getUrlCompletionConfig", () => {
|
||||
test("always includes protocols alongside saved URL options", () => {
|
||||
const config = getUrlCompletionConfig([{ label: "https://example.com" }]);
|
||||
|
||||
expect(config.options.map((option) => option.label)).toEqual([
|
||||
"http://",
|
||||
"https://",
|
||||
"https://example.com",
|
||||
]);
|
||||
expect(config.options.every((option) => option.apply === applyUrlCompletion)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
function applyCompletion(document: string, label: string, cursor: number) {
|
||||
let state = EditorState.create({ doc: document, selection: { anchor: cursor } });
|
||||
const view = {
|
||||
state,
|
||||
dispatch: (spec: TransactionSpec) => {
|
||||
state = state.update(spec).state;
|
||||
},
|
||||
} as unknown as EditorView;
|
||||
|
||||
applyUrlCompletion(view, { label } satisfies Completion, 0, cursor);
|
||||
return state.doc.toString();
|
||||
}
|
||||
@@ -1,9 +1,51 @@
|
||||
import { genericCompletion } from "../genericCompletion";
|
||||
import { insertCompletionText, pickedCompletion, type Completion } from "@codemirror/autocomplete";
|
||||
import type { EditorView } from "@codemirror/view";
|
||||
import type { GenericCompletionOption } from "@yaakapp-internal/plugins";
|
||||
import {
|
||||
genericCompletion,
|
||||
type GenericCompletion,
|
||||
type GenericCompletionConfig,
|
||||
} from "../genericCompletion";
|
||||
|
||||
export const completions = genericCompletion({
|
||||
options: [
|
||||
{ label: "http://", type: "constant" },
|
||||
{ label: "https://", type: "constant" },
|
||||
],
|
||||
minMatch: 1,
|
||||
});
|
||||
const protocolOptions: GenericCompletionOption[] = [
|
||||
{ label: "http://", type: "constant" },
|
||||
{ label: "https://", type: "constant" },
|
||||
];
|
||||
|
||||
export function getUrlCompletionConfig(
|
||||
options: GenericCompletionOption[],
|
||||
minMatch = 3,
|
||||
): GenericCompletionConfig {
|
||||
const urlOptions = [
|
||||
...protocolOptions,
|
||||
...options.filter(
|
||||
(option) => !protocolOptions.some((protocol) => protocol.label === option.label),
|
||||
),
|
||||
];
|
||||
return {
|
||||
minMatch,
|
||||
options: urlOptions.map<GenericCompletion>((option) => ({
|
||||
...option,
|
||||
apply: applyUrlCompletion,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
export function applyUrlCompletion(
|
||||
view: EditorView,
|
||||
completion: Completion,
|
||||
from: number,
|
||||
to: number,
|
||||
) {
|
||||
const isProtocol = /^https?:\/\/$/.test(completion.label);
|
||||
const replaceTo = isProtocol
|
||||
? to + (view.state.sliceDoc(to, to + 3) === "://" ? 3 : 0)
|
||||
: view.state.doc.length;
|
||||
|
||||
view.dispatch({
|
||||
...insertCompletionText(view.state, completion.label, from, replaceTo),
|
||||
annotations: pickedCompletion.of(completion),
|
||||
});
|
||||
}
|
||||
|
||||
export const completions = genericCompletion(getUrlCompletionConfig([], 1));
|
||||
|
||||
Reference in New Issue
Block a user