Add configurable max_history_entries option (default 5)
This commit is contained in:
parent
fc8af3bd24
commit
5a3d0173ed
14
README.md
14
README.md
@ -79,7 +79,7 @@ The selected field is copied to the clipboard using `wl-copy` (Wayland) or `xcli
|
|||||||
|
|
||||||
## History
|
## History
|
||||||
|
|
||||||
Selections are saved to `$XDG_CACHE_HOME/bw-menu/history.json` (defaults to `~/.cache/bw-menu/history.json`), up to 10 entries. The most recent selection is at index 0.
|
Selections are saved to `$XDG_CACHE_HOME/bw-menu/history.json` (defaults to `~/.cache/bw-menu/history.json`), up to `max_history_entries` entries (default 5). The most recent selection is at index 0.
|
||||||
|
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
@ -88,6 +88,7 @@ Config file location: `$XDG_CONFIG_HOME/bw-menu/config.yaml` (or `config.yml`),
|
|||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
rbw_path: '~/.cargo/bin/rbw'
|
rbw_path: '~/.cargo/bin/rbw'
|
||||||
|
max_history_entries: 5
|
||||||
|
|
||||||
keybindings:
|
keybindings:
|
||||||
password: 'Return'
|
password: 'Return'
|
||||||
@ -95,10 +96,11 @@ keybindings:
|
|||||||
totp: 'Shift+Return'
|
totp: 'Shift+Return'
|
||||||
```
|
```
|
||||||
|
|
||||||
| Key | Description | Default |
|
| Key | Description | Default |
|
||||||
|----------------|----------------------------------------------------|---------|
|
|------------------------|----------------------------------------------------|------------|
|
||||||
| `rbw_path` | Path to the `rbw` binary (`~` is expanded) | `rbw` |
|
| `rbw_path` | Path to the `rbw` binary (`~` is expanded) | `rbw` |
|
||||||
| `keybindings` | Map of vault fields to rofi key names (see below) | see above |
|
| `max_history_entries` | Maximum number of history entries to keep/display | `5` |
|
||||||
|
| `keybindings` | Map of vault fields to rofi key names (see below) | see above |
|
||||||
|
|
||||||
### `keybindings`
|
### `keybindings`
|
||||||
|
|
||||||
@ -110,6 +112,8 @@ The **first** entry is bound to rofi's accept key (`kb-accept-entry`). Additiona
|
|||||||
|
|
||||||
By default, `bw-menu` calls `rbw` from `$PATH`. If `rbw` is not on your PATH (e.g. when launching via `swaymsg exec`), set `rbw_path` to the full path of the binary.
|
By default, `bw-menu` calls `rbw` from `$PATH`. If `rbw` is not on your PATH (e.g. when launching via `swaymsg exec`), set `rbw_path` to the full path of the binary.
|
||||||
|
|
||||||
|
**Note:** `rbw` must be unlocked before launching `bw-menu` from a window manager keybinding. `rbw unlock` requires pinentry, which cannot run without a terminal. Run `rbw unlock` manually in a terminal once after login — the session stays unlocked until the configured idle timeout.
|
||||||
|
|
||||||
|
|
||||||
## Inspired by
|
## Inspired by
|
||||||
|
|
||||||
|
|||||||
@ -10,19 +10,19 @@ def main():
|
|||||||
cfg = config.load()
|
cfg = config.load()
|
||||||
|
|
||||||
if args.command == "select":
|
if args.command == "select":
|
||||||
_handle_select(args, cfg.rbw_path)
|
_handle_select(args, cfg.rbw_path, cfg.max_history_entries)
|
||||||
elif args.command == "history":
|
elif args.command == "history":
|
||||||
_handle_history(args, cfg.rbw_path)
|
_handle_history(args, cfg.rbw_path, cfg.max_history_entries)
|
||||||
|
|
||||||
|
|
||||||
def _handle_select(args, rbw_path: str):
|
def _handle_select(args, rbw_path: str, max_history_entries: int):
|
||||||
selector = create_selector(args.selector)
|
selector = create_selector(args.selector)
|
||||||
selection = selector.run(args, rbw_path)
|
selection = selector.run(args, rbw_path, max_history_entries)
|
||||||
if selection is None:
|
if selection is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
value = rbw.get_field(selection.entry, selection.field, rbw_path)
|
value = rbw.get_field(selection.entry, selection.field, rbw_path)
|
||||||
history.add(selection.entry)
|
history.add(selection.entry, max_history_entries)
|
||||||
|
|
||||||
if args.print_result:
|
if args.print_result:
|
||||||
print(value)
|
print(value)
|
||||||
@ -30,9 +30,9 @@ def _handle_select(args, rbw_path: str):
|
|||||||
clipboard.copy(value)
|
clipboard.copy(value)
|
||||||
|
|
||||||
|
|
||||||
def _handle_history(args, rbw_path: str):
|
def _handle_history(args, rbw_path: str, max_history_entries: int):
|
||||||
if args.history_command == "list":
|
if args.history_command == "list":
|
||||||
for i, entry in enumerate(history.load()):
|
for i, entry in enumerate(history.load()[:max_history_entries]):
|
||||||
print(f"{i}: {format_entry(entry)}")
|
print(f"{i}: {format_entry(entry)}")
|
||||||
|
|
||||||
elif args.history_command == "get":
|
elif args.history_command == "get":
|
||||||
|
|||||||
@ -20,6 +20,7 @@ class Config:
|
|||||||
default_factory=lambda: dict(DEFAULT_KEYBINDINGS)
|
default_factory=lambda: dict(DEFAULT_KEYBINDINGS)
|
||||||
)
|
)
|
||||||
rbw_path: str = "rbw"
|
rbw_path: str = "rbw"
|
||||||
|
max_history_entries: int = 5
|
||||||
|
|
||||||
|
|
||||||
def __config_path() -> Path | None:
|
def __config_path() -> Path | None:
|
||||||
@ -84,4 +85,19 @@ def load() -> Config:
|
|||||||
else:
|
else:
|
||||||
keybindings = parsed
|
keybindings = parsed
|
||||||
|
|
||||||
return Config(keybindings=keybindings, rbw_path=rbw_path)
|
max_history_entries = 5
|
||||||
|
if "max_history_entries" in data:
|
||||||
|
raw = data["max_history_entries"]
|
||||||
|
if isinstance(raw, int) and raw > 0:
|
||||||
|
max_history_entries = raw
|
||||||
|
else:
|
||||||
|
print(
|
||||||
|
f"Warning: invalid max_history_entries in {path}, using default",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
|
||||||
|
return Config(
|
||||||
|
keybindings=keybindings,
|
||||||
|
rbw_path=rbw_path,
|
||||||
|
max_history_entries=max_history_entries,
|
||||||
|
)
|
||||||
|
|||||||
@ -5,7 +5,7 @@ from pathlib import Path
|
|||||||
|
|
||||||
from bw_menu.rbw import Entry
|
from bw_menu.rbw import Entry
|
||||||
|
|
||||||
MAX_ENTRIES = 10
|
DEFAULT_MAX_ENTRIES = 5
|
||||||
|
|
||||||
|
|
||||||
def __history_path() -> Path:
|
def __history_path() -> Path:
|
||||||
@ -42,11 +42,11 @@ def __save(entries: list[Entry]):
|
|||||||
path.chmod(0o600)
|
path.chmod(0o600)
|
||||||
|
|
||||||
|
|
||||||
def add(entry: Entry):
|
def add(entry: Entry, max_entries: int = DEFAULT_MAX_ENTRIES):
|
||||||
entries = load()
|
entries = load()
|
||||||
entries = [e for e in entries if e != entry]
|
entries = [e for e in entries if e != entry]
|
||||||
entries.insert(0, entry)
|
entries.insert(0, entry)
|
||||||
entries = entries[:MAX_ENTRIES]
|
entries = entries[:max_entries]
|
||||||
__save(entries)
|
__save(entries)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -32,9 +32,11 @@ def decode_info(info: str) -> Entry:
|
|||||||
return Entry(parts[0], parts[1], parts[2])
|
return Entry(parts[0], parts[1], parts[2])
|
||||||
|
|
||||||
|
|
||||||
def prepare_entries(rbw_path: str = "rbw") -> tuple[list[Entry], list[Entry]]:
|
def prepare_entries(
|
||||||
|
rbw_path: str = "rbw", max_history_entries: int = 5
|
||||||
|
) -> tuple[list[Entry], list[Entry]]:
|
||||||
all_entries = rbw.list_entries(rbw_path)
|
all_entries = rbw.list_entries(rbw_path)
|
||||||
history_entries = history.load()
|
history_entries = history.load()[:max_history_entries]
|
||||||
|
|
||||||
all_dict = {e: e for e in all_entries}
|
all_dict = {e: e for e in all_entries}
|
||||||
history_in_list = [all_dict[e] for e in history_entries if e in all_dict]
|
history_in_list = [all_dict[e] for e in history_entries if e in all_dict]
|
||||||
|
|||||||
@ -10,8 +10,10 @@ from bw_menu.selector import (
|
|||||||
|
|
||||||
|
|
||||||
class FzfSelector:
|
class FzfSelector:
|
||||||
def run(self, args, rbw_path: str = "rbw") -> Selection | None:
|
def run(
|
||||||
history_in_list, remaining = prepare_entries(rbw_path)
|
self, args, rbw_path: str = "rbw", max_history_entries: int = 5
|
||||||
|
) -> Selection | None:
|
||||||
|
history_in_list, remaining = prepare_entries(rbw_path, max_history_entries)
|
||||||
|
|
||||||
lines = []
|
lines = []
|
||||||
for entry in history_in_list:
|
for entry in history_in_list:
|
||||||
|
|||||||
@ -28,7 +28,9 @@ _TYPE_ICONS: dict[str, str] = {
|
|||||||
|
|
||||||
|
|
||||||
class RofiSelector:
|
class RofiSelector:
|
||||||
def run(self, args, rbw_path: str = "rbw") -> Selection | None:
|
def run(
|
||||||
|
self, args, rbw_path: str = "rbw", max_history_entries: int = 5
|
||||||
|
) -> Selection | None:
|
||||||
cfg = config.load()
|
cfg = config.load()
|
||||||
keybindings = cfg.keybindings
|
keybindings = cfg.keybindings
|
||||||
rofi_retv = os.environ.get("ROFI_RETV")
|
rofi_retv = os.environ.get("ROFI_RETV")
|
||||||
@ -40,7 +42,7 @@ class RofiSelector:
|
|||||||
rofi_retv = int(rofi_retv)
|
rofi_retv = int(rofi_retv)
|
||||||
|
|
||||||
if rofi_retv == 0:
|
if rofi_retv == 0:
|
||||||
self.__print_entries(keybindings, rbw_path)
|
self.__print_entries(keybindings, rbw_path, max_history_entries)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
fields = list(keybindings.keys())
|
fields = list(keybindings.keys())
|
||||||
@ -118,8 +120,10 @@ class RofiSelector:
|
|||||||
|
|
||||||
subprocess.run(cmd)
|
subprocess.run(cmd)
|
||||||
|
|
||||||
def __print_entries(self, keybindings: dict[str, str], rbw_path: str):
|
def __print_entries(
|
||||||
history_in_list, remaining = prepare_entries(rbw_path)
|
self, keybindings: dict[str, str], rbw_path: str, max_history_entries: int
|
||||||
|
):
|
||||||
|
history_in_list, remaining = prepare_entries(rbw_path, max_history_entries)
|
||||||
|
|
||||||
print("\0prompt\x1fSelect item")
|
print("\0prompt\x1fSelect item")
|
||||||
print("\0no-custom\x1ftrue")
|
print("\0no-custom\x1ftrue")
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user