Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 2 additions & 16 deletions autoload/fall/command/Fall.vim
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,11 @@ function! fall#command#Fall#call(args) abort
if denops#plugin#wait('fall') isnot# 0
return
endif
let l:laststatus_saved = &laststatus
try
call s:hide()
call fall#internal#mapping#store()
call fall#internal#picker#setup()
call denops#request('fall', 'picker:command', [a:args])
finally
call s:show()
call fall#internal#tolerant#call({ -> fall#internal#mapping#restore() })
call fall#internal#tolerant#call({ -> fall#internal#popup#closeall() })
call fall#internal#picker#teardown()
endtry
endfunction

Expand All @@ -20,13 +16,3 @@ function! fall#command#Fall#complete(arglead, cmdline, cursorpos) abort
endif
return denops#request('fall', 'picker:command:complete', [a:arglead, a:cmdline, a:cursorpos])
endfunction

function! s:hide() abort
call fall#internal#tolerant#call({ -> fall#internal#msgarea#hide() })
call fall#internal#tolerant#call({ -> fall#internal#cursor#hide() })
endfunction

function! s:show() abort
call fall#internal#tolerant#call({ -> fall#internal#msgarea#show() })
call fall#internal#tolerant#call({ -> fall#internal#cursor#show() })
endfunction
18 changes: 18 additions & 0 deletions autoload/fall/command/FallResume.vim
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
function! fall#command#FallResume#call(filter) abort
if denops#plugin#wait('fall') isnot# 0
return
endif
try
call fall#internal#picker#setup()
call denops#request('fall', 'picker:resume:command', [a:filter])
finally
call fall#internal#picker#teardown()
endtry
endfunction

function! fall#command#FallResume#complete(arglead, cmdline, cursorpos) abort
if denops#plugin#wait('fall') isnot# 0
return []
endif
return denops#request('fall', 'picker:resume:command:complete', [a:arglead, a:cmdline, a:cursorpos])
endfunction
11 changes: 11 additions & 0 deletions autoload/fall/command/FallSession.vim
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
function! fall#command#FallSession#call() abort
if denops#plugin#wait('fall') isnot# 0
return
endif
try
call fall#internal#picker#setup()
call denops#request('fall', 'picker:session:command', [])
finally
call fall#internal#picker#teardown()
endtry
endfunction
12 changes: 12 additions & 0 deletions autoload/fall/internal/picker.vim
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
function! fall#internal#picker#setup() abort
call fall#internal#tolerant#call({ -> fall#internal#msgarea#hide() })
call fall#internal#tolerant#call({ -> fall#internal#cursor#hide() })
call fall#internal#mapping#store()
endfunction

function! fall#internal#picker#teardown() abort
call fall#internal#tolerant#call({ -> fall#internal#msgarea#show() })
call fall#internal#tolerant#call({ -> fall#internal#cursor#show() })
call fall#internal#tolerant#call({ -> fall#internal#mapping#restore() })
call fall#internal#tolerant#call({ -> fall#internal#popup#closeall() })
endfunction
14 changes: 12 additions & 2 deletions denops/fall/component/input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ export type InputComponentParams = ComponentProperties & {
/** The title of the input component */
readonly title?: string;

/** The command line input text */
readonly cmdline?: string;

/** Optional spinner sequence to show during processing */
readonly spinner?: readonly string[];

Expand Down Expand Up @@ -77,11 +80,18 @@ export class InputComponent extends BaseComponent {
#modifiedContent = true;

constructor(
{ title, spinner, headSymbol, failSymbol, ...params }:
InputComponentParams = {},
{
title,
cmdline,
spinner,
headSymbol,
failSymbol,
...params
}: InputComponentParams = {},
) {
super(params);
this.#title = title ?? "";
this.#cmdline = cmdline ?? "";
this.#spinner = new Spinner(spinner ?? SPINNER);
this.#headSymbol = headSymbol ?? HEAD_SYMBOL;
this.#failSymbol = failSymbol ?? FAIL_SYMBOL;
Expand Down
16 changes: 16 additions & 0 deletions denops/fall/extension/action/session.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { Action } from "jsr:@vim-fall/core@^0.3.0/action";
import type { Detail } from "../source/session.ts";

export const defaultSessionActions = {
resume: {
invoke: async (denops, { item }) => {
if (!item) {
return;
}
// we need to use timer_start to avoid nesting pickers
await denops.cmd(
`call timer_start(0, { -> execute('FallResume ${item.value}') })`,
);
},
},
} satisfies Record<string, Action<Detail>>;
52 changes: 52 additions & 0 deletions denops/fall/extension/previewer/session.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import type { PreviewItem } from "jsr:@vim-fall/core@^0.3.0/item";
import type { Previewer } from "jsr:@vim-fall/core@^0.3.0/previewer";
import { definePreviewer } from "jsr:@vim-fall/std@^0.10.0/previewer";
import type { Detail } from "../source/session.ts";
import { decompressPickerSession } from "../../session.ts";

export function session(): Previewer<Detail> {
return definePreviewer(async (_denops, { item }, { signal }) => {
if (!item || signal?.aborted) {
return undefined;
}

try {
// Decompress the session to access its data
const session = await decompressPickerSession(item.detail);

const lines: string[] = [];

// Add session info
lines.push(`# Session: ${item.value}`);
lines.push("");
lines.push(`Source: ${session.name}`);
lines.push(`Query: ${session.context.query || "(empty)"}`);
lines.push(`Total items: ${session.context.collectedItems.length}`);
lines.push(`Filtered items: ${session.context.filteredItems.length}`);
lines.push("");
lines.push("## Filtered Items:");
lines.push("");

// Show filtered items with selection status
const selection = session.context.selection;
for (const filteredItem of session.context.filteredItems) {
const isSelected = selection.has(filteredItem.id);
const prefix = isSelected ? "[x]" : "[ ]";
lines.push(`${prefix} ${filteredItem.value}`);
}

if (session.context.filteredItems.length === 0) {
lines.push("(no items)");
}

return {
content: lines,
filetype: "markdown",
} satisfies PreviewItem;
} catch (error) {
return {
content: [`Error loading session preview: ${error}`],
} satisfies PreviewItem;
}
});
}
23 changes: 23 additions & 0 deletions denops/fall/extension/renderer/session.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type { Denops } from "jsr:@denops/std@^7.3.2";
import type { Renderer } from "jsr:@vim-fall/core@^0.3.0/renderer";
import type { DisplayItem } from "jsr:@vim-fall/core@^0.3.0/item";
import type { Detail } from "../source/session.ts";

export function session(): Renderer<Detail> {
return {
render(
_denops: Denops,
{ items }: { items: DisplayItem<Detail>[] },
{ signal }: { signal?: AbortSignal },
): void {
for (const item of items) {
if (signal?.aborted) break;
item.label = [
item.value,
item.detail.name,
...item.detail.args,
].join(" ");
}
},
};
}
22 changes: 22 additions & 0 deletions denops/fall/extension/source/session.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type { Source } from "jsr:@vim-fall/core@^0.3.0/source";
import type { DetailUnit, IdItem } from "jsr:@vim-fall/core@^0.3.0/item";
import type { PickerSessionCompressed } from "../../session.ts";
import { listPickerSessions } from "../../session.ts";

export type Detail = PickerSessionCompressed<DetailUnit>;

export function session(): Source<Detail> {
return {
collect: async function* (): AsyncIterableIterator<IdItem<Detail>> {
const sessions = listPickerSessions();
yield* sessions.map((session, index) => {
const number = index + 1;
return {
id: index,
value: `#${number}`,
detail: session,
};
});
},
};
}
24 changes: 20 additions & 4 deletions denops/fall/lib/item_belt.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,34 @@
/**
* Options for configuring an ItemBelt instance.
*/
export type ItemBeltOptions = {
/**
* The initial index to set for the ItemBelt.
* If not provided, defaults to 0.
*/
index?: number;
};

/**
* A class representing an item belt, which provides functionality for managing
* and selecting items in a list with wrap-around and bounded selection.
*
* @template T The type of items contained in the belt.
*/
export class ItemBelt<T> {
#index = 0;
#index: number = 0;
#items: readonly T[];

/**
* Creates an instance of ItemBelt with the specified items.
*
* @param items An array of items of type T to initialize the belt.
* @param options Optional configuration for the ItemBelt.
* @param options.index The initial index to set (default is 0).
*/
constructor(items: readonly T[]) {
constructor(items: readonly T[], options?: ItemBeltOptions) {
this.#items = items;
this.index = options?.index ?? 0;
}

/**
Expand Down Expand Up @@ -73,7 +87,9 @@ export class ItemBelt<T> {
* to the nearest valid value.
*/
set index(index: number) {
if (index >= this.#items.length) {
if (this.#items.length === 0) {
index = 0;
} else if (index >= this.#items.length) {
index = this.#items.length - 1;
} else if (index < 0) {
index = 0;
Expand All @@ -85,8 +101,8 @@ export class ItemBelt<T> {
* Selects a new item based on the current index, with an optional offset.
* The selection can optionally cycle through the items list.
*
* @param offset The number of positions to move the index (default is 1).
* @param options Optional configuration for the selection.
* @param options.offset The number of positions to move the index (default is 1).
* @param options.cycle Whether to cycle through the list (default is `false`).
*/
select(offset = 1, { cycle = false } = {}): void {
Expand Down
20 changes: 19 additions & 1 deletion denops/fall/lib/item_belt_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Deno.test("ItemBelt", async (t) => {
assertEquals(belt.count, 0);
});

await t.step("current returns the first itme in default", () => {
await t.step("current returns the first item in default", () => {
const belt = new ItemBelt([0, 1, 2]);
assertEquals(belt.current, 0);
});
Expand All @@ -32,6 +32,24 @@ Deno.test("ItemBelt", async (t) => {
assertEquals(belt.count, 3);
});

await t.step("constructor accepts initial index option", () => {
const belt = new ItemBelt([0, 1, 2], { index: 1 });
assertEquals(belt.index, 1);
assertEquals(belt.current, 1);
});

await t.step("constructor clamps initial index when too large", () => {
const belt = new ItemBelt([0, 1, 2], { index: 5 });
assertEquals(belt.index, 2);
assertEquals(belt.current, 2);
});

await t.step("constructor clamps initial index when negative", () => {
const belt = new ItemBelt([0, 1, 2], { index: -1 });
assertEquals(belt.index, 0);
assertEquals(belt.current, 0);
});

await t.step("changing index changes current", () => {
const belt = new ItemBelt([0, 1, 2]);
belt.index = 1;
Expand Down
Loading