Compare commits
5 Commits
9db392e8ae
...
v2.0.0
| Author | SHA1 | Date | |
|---|---|---|---|
| f3b86af05b | |||
| ab92ccc6f4 | |||
| 4b89ec8025 | |||
| 7d936beb2a | |||
| 57550692c1 |
145
README.md
145
README.md
@ -1,64 +1,165 @@
|
|||||||
# confy
|
# confy
|
||||||
|
|
||||||
a config manager for linux/unix based systems including macos (unix).
|
a config manager for linux/unix based systems including macos (unix) and windows.
|
||||||
|
|
||||||
simple tui for keeping track of all your config files in one place. no more hunting through ~/.config.
|
simple tui for keeping track of all your config files in one place. no more hunting through ~/.config.
|
||||||
|
|
||||||
<img width="1918" height="1081" alt="image" src="https://github.com/user-attachments/assets/a6736759-d430-433f-b93a-cd319dc61277" />
|

|
||||||
|
|
||||||
|
|
||||||
## features
|
## features
|
||||||
|
|
||||||
- track config files from anywhere
|
* **organize with groups** - create folders to organize your configs (hyprland/, nvim/, etc)
|
||||||
- open in $EDITOR with one keypress
|
* **collapsible groups** - expand/collapse groups to keep your view clean
|
||||||
- remembers last edited file
|
* **search** - real-time fuzzy search through all your configs
|
||||||
- vim-style keybinds
|
* **multiple sort modes** - sort by name, date modified, or file size
|
||||||
- lightweight and fast
|
* **open in $EDITOR** - edit files with one keypress
|
||||||
- works on linux, macos, bsd, whatever
|
* **remembers last file** - quick access to recently edited configs
|
||||||
|
* **customizable config dir** - change base directory for file picker
|
||||||
|
* **vim-style keybinds** - j/k navigation, command mode
|
||||||
|
* **lightweight and fast** - pure python with curses
|
||||||
|
* **cross-platform** - works on linux, macos, bsd, windows
|
||||||
|
|
||||||
## installation
|
## installation
|
||||||
|
|
||||||
|
### from AUR (arch linux)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yay -S confy-tui
|
||||||
|
```
|
||||||
|
|
||||||
|
### manual install
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/Phluxjr23/confy.git
|
git clone https://github.com/Phluxjr23/confy.git
|
||||||
cd confy
|
cd confy
|
||||||
chmod +x main.py
|
chmod +x main.py
|
||||||
sudo mv main.py /usr/local/bin/confy
|
# optionally symlink to PATH
|
||||||
|
sudo ln -s $(pwd)/main.py /usr/local/bin/confy
|
||||||
```
|
```
|
||||||
|
|
||||||
## dependencies
|
## dependencies
|
||||||
|
|
||||||
- python3
|
* python3
|
||||||
- ranger (for file picker)
|
* ranger (for file picker)
|
||||||
- curses (usually included with python)
|
* curses (usually included with python)
|
||||||
|
|
||||||
## usage
|
## usage
|
||||||
|
|
||||||
just run `confy` in your terminal
|
just run `confy` in your terminal
|
||||||
|
|
||||||
### keybinds
|
### navigation
|
||||||
|
|
||||||
- `j/k` or `arrow keys` - navigate
|
* `j/k` or `arrow keys` - move up/down
|
||||||
- `enter` - open selected file in $EDITOR
|
* `enter` - open file in $EDITOR (or toggle group)
|
||||||
- `:` - enter command mode
|
* `space` - toggle group expand/collapse
|
||||||
- `q` - quit
|
* `/` - search mode
|
||||||
|
* `:` - command mode
|
||||||
|
* `q` - quit
|
||||||
|
|
||||||
### commands
|
### commands
|
||||||
|
|
||||||
- `:ac` - add config (opens ranger file picker)
|
#### file management
|
||||||
- `:rm` - remove selected file
|
* `:ac` - add config to ungrouped
|
||||||
- `:l` - open last edited file
|
* `:ac <group>` - add config to specific group
|
||||||
- `:q` - quit
|
* `:rm` - remove selected file
|
||||||
|
* `:l` - open last edited file
|
||||||
|
|
||||||
|
#### group management
|
||||||
|
* `:ag <group>` - add new group
|
||||||
|
* `:mg <group>` - move selected file to group
|
||||||
|
* `:rg <group>` - remove group (moves files to ungrouped)
|
||||||
|
|
||||||
|
#### sorting & filtering
|
||||||
|
* `:sort name` - sort alphabetically
|
||||||
|
* `:sort date` - sort by last modified
|
||||||
|
* `:sort size` - sort by file size
|
||||||
|
* `:reverse` - toggle ascending/descending order
|
||||||
|
* `/` then type - search files and groups in real-time
|
||||||
|
|
||||||
|
#### configuration
|
||||||
|
* `:cd` - change config directory (opens ranger)
|
||||||
|
* `:cd reset` - reset to ~/.config (or default)
|
||||||
|
* `:q` - quit
|
||||||
|
|
||||||
|
### search mode
|
||||||
|
|
||||||
|
press `/` to enter search mode, then start typing:
|
||||||
|
- filters both files and groups in real-time
|
||||||
|
- case-insensitive fuzzy matching
|
||||||
|
- `enter` to accept and keep filtering
|
||||||
|
- `esc` to clear search and show all files
|
||||||
|
|
||||||
|
### groups
|
||||||
|
|
||||||
|
groups are purely organizational - your actual config files stay in their original locations. groups help you organize your list of tracked configs into logical categories like "hyprland", "nvim", "shell", etc.
|
||||||
|
|
||||||
|
groups are collapsible - press `space` or `enter` on a group header to toggle.
|
||||||
|
|
||||||
## why confy?
|
## why confy?
|
||||||
|
|
||||||
tired of doing `cd ~/.config/whatever` a million times a day? same. confy keeps all your important configs in one list so you can jump to them instantly.
|
tired of doing `cd ~/.config/whatever` a million times a day? same. confy keeps all your important configs in one list so you can jump to them instantly.
|
||||||
|
|
||||||
|
organize related configs into groups, search through everything, sort however you want, and open files in your editor with a single keypress.
|
||||||
|
|
||||||
simple, fast, does one thing well.
|
simple, fast, does one thing well.
|
||||||
|
|
||||||
|
## examples
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# start confy
|
||||||
|
confy
|
||||||
|
|
||||||
|
# create some groups
|
||||||
|
:ag hyprland
|
||||||
|
:ag nvim
|
||||||
|
:ag shell
|
||||||
|
|
||||||
|
# add configs to groups
|
||||||
|
:ac hyprland # opens ranger, pick hyprland.conf
|
||||||
|
:ac nvim # opens ranger, pick init.lua
|
||||||
|
|
||||||
|
# move existing files between groups
|
||||||
|
# (select file first, then)
|
||||||
|
:mg shell
|
||||||
|
|
||||||
|
# search for configs
|
||||||
|
/hypr # shows only hyprland-related files
|
||||||
|
|
||||||
|
# sort by recently modified
|
||||||
|
:sort date
|
||||||
|
:reverse # newest first
|
||||||
|
|
||||||
|
# change where ranger starts
|
||||||
|
:cd # pick new directory
|
||||||
|
:cd reset # back to default
|
||||||
|
```
|
||||||
|
|
||||||
|
## tips
|
||||||
|
|
||||||
|
* set `export EDITOR=nvim` in your shell rc for your preferred editor
|
||||||
|
* use groups to organize by application (hyprland/, nvim/, kitty/)
|
||||||
|
* use `:sort date` to quickly find recently edited configs
|
||||||
|
* search with `/` to quickly jump to specific configs
|
||||||
|
* collapse groups you don't use often to keep view clean
|
||||||
|
|
||||||
|
## windows support
|
||||||
|
|
||||||
|
on windows, change the config directory to where you keep your configs:
|
||||||
|
```
|
||||||
|
:cd
|
||||||
|
# navigate to C:\Users\YourName\AppData\Local or wherever
|
||||||
|
```
|
||||||
|
|
||||||
|
ranger should work on windows via WSL or you can modify the code to use a different file picker.
|
||||||
|
|
||||||
## license
|
## license
|
||||||
|
|
||||||
mit
|
mit
|
||||||
|
|
||||||
|
## contributing
|
||||||
|
|
||||||
|
prs welcome! this is a simple tool but if you have ideas for improvements, open an issue or submit a pr.
|
||||||
|
|
||||||
## btw
|
## btw
|
||||||
|
|
||||||
i use arch btw
|
i use arch btw
|
||||||
|
|||||||
255
main.py
Executable file → Normal file
255
main.py
Executable file → Normal file
@ -12,31 +12,85 @@ TRACKED_FILE = CONFIG_DIR / "tracked.json"
|
|||||||
|
|
||||||
class Confy:
|
class Confy:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.files = []
|
self.groups = {"ungrouped": []}
|
||||||
self.selected = 0
|
self.selected = 0
|
||||||
self.page = 0
|
self.page = 0
|
||||||
self.items_per_page = 10
|
self.items_per_page = 10
|
||||||
self.command_mode = False
|
self.command_mode = False
|
||||||
|
self.search_mode = False
|
||||||
self.command_buffer = ""
|
self.command_buffer = ""
|
||||||
|
self.search_buffer = ""
|
||||||
self.last_opened = None
|
self.last_opened = None
|
||||||
self.config_dir = str(Path.home() / ".config")
|
self.config_dir = str(Path.home() / ".config")
|
||||||
|
self.collapsed_groups = set()
|
||||||
|
self.flat_view = []
|
||||||
|
self.sort_mode = "name" # name, date, size
|
||||||
|
self.sort_order = 'asc' # asc or desc
|
||||||
self.load_data()
|
self.load_data()
|
||||||
|
self.rebuild_flat_view()
|
||||||
|
|
||||||
def load_data(self):
|
def load_data(self):
|
||||||
if TRACKED_FILE.exists():
|
if TRACKED_FILE.exists():
|
||||||
with open(TRACKED_FILE, 'r') as f:
|
with open(TRACKED_FILE, 'r') as f:
|
||||||
data = json.load(f)
|
data = json.load(f)
|
||||||
self.files = data.get('files', [])
|
if 'files' in data:
|
||||||
|
self.groups = {"ungrouped": data['files']}
|
||||||
|
else:
|
||||||
|
self.groups = data.get('groups', {"ungrouped": []})
|
||||||
self.last_opened = data.get('last_opened')
|
self.last_opened = data.get('last_opened')
|
||||||
|
self.collapsed_groups = set(data.get('collapsed_groups', []))
|
||||||
|
self.sort_mode = data.get('sort_mode', 'name')
|
||||||
|
self.sort_order = data.get('sort_order', 'asc')
|
||||||
|
|
||||||
def save_data(self):
|
def save_data(self):
|
||||||
CONFIG_DIR.mkdir(parents=True, exist_ok=True)
|
CONFIG_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
with open(TRACKED_FILE, 'w') as f:
|
with open(TRACKED_FILE, 'w') as f:
|
||||||
json.dump({
|
json.dump({
|
||||||
'files': self.files,
|
'groups': self.groups,
|
||||||
'last_opened': self.last_opened
|
'last_opened': self.last_opened,
|
||||||
|
'collapsed_groups': list(self.collapsed_groups),
|
||||||
|
'sort_mode': self.sort_mode,
|
||||||
|
'sort_order': self.sort_order
|
||||||
}, f, indent=2)
|
}, f, indent=2)
|
||||||
|
|
||||||
|
def sort_files(self, files):
|
||||||
|
"""sort files based on current sort mode"""
|
||||||
|
if self.sort_mode == "name":
|
||||||
|
sorted_files = sorted(files, key=lambda f: Path(f).name.lower())
|
||||||
|
elif self.sort_mode == "date":
|
||||||
|
sorted_files = sorted(files, key=lambda f: os.path.getmtime(f) if os.path.exists(f) else 0)
|
||||||
|
elif self.sort_mode == "size":
|
||||||
|
sorted_files = sorted(files, key=lambda f: os.path.getsize(f) if os.path.exists(f) else 0)
|
||||||
|
else:
|
||||||
|
sorted_files = files
|
||||||
|
|
||||||
|
if self.sort_order == "desc":
|
||||||
|
sorted_files = sorted_files[::-1]
|
||||||
|
|
||||||
|
return sorted_files
|
||||||
|
|
||||||
|
def rebuild_flat_view(self):
|
||||||
|
"""rebuild flattened view for navigation"""
|
||||||
|
self.flat_view = []
|
||||||
|
|
||||||
|
# filter by search if active
|
||||||
|
if self.search_buffer:
|
||||||
|
query = self.search_buffer.lower()
|
||||||
|
for group_name in sorted(self.groups.keys()):
|
||||||
|
matching_files = [f for f in self.groups[group_name]
|
||||||
|
if query in Path(f).name.lower() or query in group_name.lower()]
|
||||||
|
if matching_files or query in group_name.lower():
|
||||||
|
self.flat_view.append(('group', group_name))
|
||||||
|
if group_name not in self.collapsed_groups:
|
||||||
|
for filepath in self.sort_files(matching_files):
|
||||||
|
self.flat_view.append(('file', filepath, group_name))
|
||||||
|
else:
|
||||||
|
for group_name in sorted(self.groups.keys()):
|
||||||
|
self.flat_view.append(('group', group_name))
|
||||||
|
if group_name not in self.collapsed_groups:
|
||||||
|
for filepath in self.sort_files(self.groups[group_name]):
|
||||||
|
self.flat_view.append(('file', filepath, group_name))
|
||||||
|
|
||||||
def get_file_info(self, filepath):
|
def get_file_info(self, filepath):
|
||||||
try:
|
try:
|
||||||
stat = os.stat(filepath)
|
stat = os.stat(filepath)
|
||||||
@ -53,27 +107,105 @@ class Confy:
|
|||||||
size /= 1024
|
size /= 1024
|
||||||
return f"{size:.1f}TB"
|
return f"{size:.1f}TB"
|
||||||
|
|
||||||
def add_config(self):
|
def add_config(self, group_name="ungrouped"):
|
||||||
curses.endwin()
|
curses.endwin()
|
||||||
try:
|
try:
|
||||||
result = subprocess.run(['ranger', '--choosefile=/tmp/confy_pick', self.config_dir])
|
result = subprocess.run(['ranger', '--choosefile=/tmp/confy_pick', self.config_dir])
|
||||||
if os.path.exists('/tmp/confy_pick'):
|
if os.path.exists('/tmp/confy_pick'):
|
||||||
with open('/tmp/confy_pick', 'r') as f:
|
with open('/tmp/confy_pick', 'r') as f:
|
||||||
filepath = f.read().strip()
|
filepath = f.read().strip()
|
||||||
if filepath and filepath not in self.files:
|
if filepath:
|
||||||
self.files.append(filepath)
|
for grp_files in self.groups.values():
|
||||||
|
if filepath in grp_files:
|
||||||
|
os.remove('/tmp/confy_pick')
|
||||||
|
curses.doupdate()
|
||||||
|
return
|
||||||
|
if group_name not in self.groups:
|
||||||
|
self.groups[group_name] = []
|
||||||
|
self.groups[group_name].append(filepath)
|
||||||
self.save_data()
|
self.save_data()
|
||||||
|
self.rebuild_flat_view()
|
||||||
os.remove('/tmp/confy_pick')
|
os.remove('/tmp/confy_pick')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
pass
|
pass
|
||||||
curses.doupdate()
|
curses.doupdate()
|
||||||
|
|
||||||
def remove_config(self):
|
def remove_config(self):
|
||||||
if self.files and 0 <= self.selected < len(self.files):
|
if not self.flat_view or self.selected >= len(self.flat_view):
|
||||||
del self.files[self.selected]
|
return
|
||||||
if self.selected >= len(self.files) and self.files:
|
|
||||||
self.selected = len(self.files) - 1
|
item = self.flat_view[self.selected]
|
||||||
|
if item[0] == 'file':
|
||||||
|
filepath = item[1]
|
||||||
|
group_name = item[2]
|
||||||
|
if filepath in self.groups[group_name]:
|
||||||
|
self.groups[group_name].remove(filepath)
|
||||||
self.save_data()
|
self.save_data()
|
||||||
|
self.rebuild_flat_view()
|
||||||
|
if self.selected >= len(self.flat_view) and self.flat_view:
|
||||||
|
self.selected = len(self.flat_view) - 1
|
||||||
|
|
||||||
|
def add_group(self, group_name):
|
||||||
|
if group_name and group_name not in self.groups:
|
||||||
|
self.groups[group_name] = []
|
||||||
|
self.save_data()
|
||||||
|
self.rebuild_flat_view()
|
||||||
|
|
||||||
|
def remove_group(self, group_name):
|
||||||
|
if group_name in self.groups and group_name != "ungrouped":
|
||||||
|
self.groups["ungrouped"].extend(self.groups[group_name])
|
||||||
|
del self.groups[group_name]
|
||||||
|
self.save_data()
|
||||||
|
self.rebuild_flat_view()
|
||||||
|
|
||||||
|
def move_to_group(self, group_name):
|
||||||
|
if not self.flat_view or self.selected >= len(self.flat_view):
|
||||||
|
return
|
||||||
|
|
||||||
|
item = self.flat_view[self.selected]
|
||||||
|
if item[0] == 'file':
|
||||||
|
filepath = item[1]
|
||||||
|
old_group = item[2]
|
||||||
|
|
||||||
|
if group_name not in self.groups:
|
||||||
|
self.groups[group_name] = []
|
||||||
|
|
||||||
|
if filepath in self.groups[old_group]:
|
||||||
|
self.groups[old_group].remove(filepath)
|
||||||
|
|
||||||
|
if filepath not in self.groups[group_name]:
|
||||||
|
self.groups[group_name].append(filepath)
|
||||||
|
|
||||||
|
self.save_data()
|
||||||
|
self.rebuild_flat_view()
|
||||||
|
|
||||||
|
def change_config_dir(self):
|
||||||
|
curses.endwin()
|
||||||
|
try:
|
||||||
|
result = subprocess.run(['ranger', '--choosedir=/tmp/confy_dir'])
|
||||||
|
if os.path.exists('/tmp/confy_dir'):
|
||||||
|
with open('/tmp/confy_dir', 'r') as f:
|
||||||
|
new_dir = f.read().strip()
|
||||||
|
if new_dir and os.path.isdir(new_dir):
|
||||||
|
self.config_dir = new_dir
|
||||||
|
os.remove('/tmp/confy_dir')
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
curses.doupdate()
|
||||||
|
|
||||||
|
def toggle_group(self):
|
||||||
|
if not self.flat_view or self.selected >= len(self.flat_view):
|
||||||
|
return
|
||||||
|
|
||||||
|
item = self.flat_view[self.selected]
|
||||||
|
if item[0] == 'group':
|
||||||
|
group_name = item[1]
|
||||||
|
if group_name in self.collapsed_groups:
|
||||||
|
self.collapsed_groups.remove(group_name)
|
||||||
|
else:
|
||||||
|
self.collapsed_groups.add(group_name)
|
||||||
|
self.save_data()
|
||||||
|
self.rebuild_flat_view()
|
||||||
|
|
||||||
def open_file(self, filepath):
|
def open_file(self, filepath):
|
||||||
editor = os.environ.get('EDITOR', 'nano')
|
editor = os.environ.get('EDITOR', 'nano')
|
||||||
@ -96,22 +228,48 @@ class Confy:
|
|||||||
stdscr.addstr(0, 1, "confy")
|
stdscr.addstr(0, 1, "confy")
|
||||||
last_text = f"previous: {{{Path(self.last_opened).name if self.last_opened else 'none'}}}"
|
last_text = f"previous: {{{Path(self.last_opened).name if self.last_opened else 'none'}}}"
|
||||||
stdscr.addstr(1, 1, last_text)
|
stdscr.addstr(1, 1, last_text)
|
||||||
config_text = f"config dir is {self.config_dir}"
|
|
||||||
|
# show sort mode and config dir
|
||||||
|
sort_text = f"sort: {self.sort_mode} ({self.sort_order})"
|
||||||
|
config_text = f"config dir: {self.config_dir}"
|
||||||
|
stdscr.addstr(1, width - len(config_text) - len(sort_text) - 5, sort_text)
|
||||||
stdscr.addstr(1, width - len(config_text) - 2, config_text)
|
stdscr.addstr(1, width - len(config_text) - 2, config_text)
|
||||||
stdscr.addstr(2, 0, "═" * width)
|
stdscr.addstr(2, 0, "═" * width)
|
||||||
|
|
||||||
# file list
|
# file list with groups
|
||||||
start_idx = self.page * self.items_per_page
|
start_idx = self.page * self.items_per_page
|
||||||
end_idx = min(start_idx + self.items_per_page, len(self.files))
|
end_idx = min(start_idx + self.items_per_page, len(self.flat_view))
|
||||||
|
|
||||||
for i in range(start_idx, end_idx):
|
for i in range(start_idx, end_idx):
|
||||||
y = 4 + (i - start_idx)
|
y = 4 + (i - start_idx)
|
||||||
filepath = self.files[i]
|
item = self.flat_view[i]
|
||||||
|
|
||||||
|
if item[0] == 'group':
|
||||||
|
group_name = item[1]
|
||||||
|
collapsed = "▶" if group_name in self.collapsed_groups else "▼"
|
||||||
|
file_count = len(self.groups[group_name])
|
||||||
|
line = f"{collapsed} {group_name}/ ({file_count} files)"
|
||||||
|
|
||||||
|
if i == self.selected:
|
||||||
|
line = f"{line} <"
|
||||||
|
stdscr.attron(curses.A_REVERSE | curses.A_BOLD)
|
||||||
|
else:
|
||||||
|
stdscr.attron(curses.A_BOLD)
|
||||||
|
|
||||||
|
stdscr.addstr(y, 1, line[:width-2])
|
||||||
|
stdscr.attroff(curses.A_BOLD)
|
||||||
|
if i == self.selected:
|
||||||
|
stdscr.attroff(curses.A_REVERSE)
|
||||||
|
|
||||||
|
elif item[0] == 'file':
|
||||||
|
filepath = item[1]
|
||||||
filename = Path(filepath).name
|
filename = Path(filepath).name
|
||||||
directory = str(Path(filepath).parent)
|
directory = str(Path(filepath).parent)
|
||||||
mtime, size = self.get_file_info(filepath)
|
mtime, size = self.get_file_info(filepath)
|
||||||
|
|
||||||
line = f"{filename:<20} | {directory:<30} | {mtime:<16} | {size:<10}"
|
# dynamically size columns based on terminal width
|
||||||
|
col_width = max(10, (width - 60) // 2)
|
||||||
|
line = f" {filename[:col_width]:<{col_width}} | {directory[:col_width]:<{col_width}} | {mtime} | {size}"
|
||||||
if i == self.selected:
|
if i == self.selected:
|
||||||
line = f"{line} <"
|
line = f"{line} <"
|
||||||
stdscr.attron(curses.A_REVERSE)
|
stdscr.attron(curses.A_REVERSE)
|
||||||
@ -120,13 +278,16 @@ class Confy:
|
|||||||
stdscr.attroff(curses.A_REVERSE)
|
stdscr.attroff(curses.A_REVERSE)
|
||||||
|
|
||||||
# bottom bar
|
# bottom bar
|
||||||
total_pages = (len(self.files) + self.items_per_page - 1) // self.items_per_page
|
total_pages = (len(self.flat_view) + self.items_per_page - 1) // self.items_per_page
|
||||||
if total_pages == 0:
|
if total_pages == 0:
|
||||||
total_pages = 1
|
total_pages = 1
|
||||||
bottom_y = height - 2
|
bottom_y = height - 2
|
||||||
stdscr.addstr(bottom_y, 0, "═" * width)
|
stdscr.addstr(bottom_y, 0, "═" * width)
|
||||||
|
|
||||||
if self.command_mode:
|
if self.command_mode:
|
||||||
page_text = f"page {self.page + 1}/{total_pages} ▌ :{self.command_buffer}"
|
page_text = f"page {self.page + 1}/{total_pages} ▌ :{self.command_buffer}"
|
||||||
|
elif self.search_mode:
|
||||||
|
page_text = f"page {self.page + 1}/{total_pages} ▌ /{self.search_buffer}"
|
||||||
else:
|
else:
|
||||||
page_text = f"page {self.page + 1}/{total_pages} ▌"
|
page_text = f"page {self.page + 1}/{total_pages} ▌"
|
||||||
stdscr.addstr(bottom_y + 1, 1, page_text[:width-2])
|
stdscr.addstr(bottom_y + 1, 1, page_text[:width-2])
|
||||||
@ -135,15 +296,39 @@ class Confy:
|
|||||||
|
|
||||||
def handle_command(self):
|
def handle_command(self):
|
||||||
cmd = self.command_buffer.strip()
|
cmd = self.command_buffer.strip()
|
||||||
|
parts = cmd.split(maxsplit=1)
|
||||||
|
|
||||||
if cmd == "q":
|
if cmd == "q":
|
||||||
return False
|
return False
|
||||||
elif cmd == "ac":
|
elif cmd == "ac":
|
||||||
self.add_config()
|
self.add_config()
|
||||||
|
elif parts[0] == "ac" and len(parts) == 2:
|
||||||
|
self.add_config(parts[1])
|
||||||
elif cmd == "rm":
|
elif cmd == "rm":
|
||||||
self.remove_config()
|
self.remove_config()
|
||||||
|
elif parts[0] == "ag" and len(parts) == 2:
|
||||||
|
self.add_group(parts[1])
|
||||||
|
elif parts[0] == "rg" and len(parts) == 2:
|
||||||
|
self.remove_group(parts[1])
|
||||||
|
elif parts[0] == "mg" and len(parts) == 2:
|
||||||
|
self.move_to_group(parts[1])
|
||||||
elif cmd == "l":
|
elif cmd == "l":
|
||||||
if self.last_opened and os.path.exists(self.last_opened):
|
if self.last_opened and os.path.exists(self.last_opened):
|
||||||
self.open_file(self.last_opened)
|
self.open_file(self.last_opened)
|
||||||
|
elif cmd == "cd":
|
||||||
|
self.change_config_dir()
|
||||||
|
elif cmd == "cd reset":
|
||||||
|
self.config_dir = str(Path.home() / ".config")
|
||||||
|
elif parts[0] == "sort" and len(parts) == 2:
|
||||||
|
if parts[1] in ["name", "date", "size"]:
|
||||||
|
self.sort_mode = parts[1]
|
||||||
|
self.save_data()
|
||||||
|
self.rebuild_flat_view()
|
||||||
|
elif cmd == "reverse":
|
||||||
|
self.sort_order = "desc" if self.sort_order == "asc" else "asc"
|
||||||
|
self.save_data()
|
||||||
|
self.rebuild_flat_view()
|
||||||
|
|
||||||
self.command_buffer = ""
|
self.command_buffer = ""
|
||||||
self.command_mode = False
|
self.command_mode = False
|
||||||
return True
|
return True
|
||||||
@ -171,12 +356,36 @@ class Confy:
|
|||||||
self.command_buffer = self.command_buffer[:-1]
|
self.command_buffer = self.command_buffer[:-1]
|
||||||
elif 32 <= key <= 126:
|
elif 32 <= key <= 126:
|
||||||
self.command_buffer += chr(key)
|
self.command_buffer += chr(key)
|
||||||
|
elif self.search_mode:
|
||||||
|
if key == ord('\n'):
|
||||||
|
self.search_mode = False
|
||||||
|
self.selected = 0
|
||||||
|
self.page = 0
|
||||||
|
elif key == 27: # ESC
|
||||||
|
self.search_mode = False
|
||||||
|
self.search_buffer = ""
|
||||||
|
self.rebuild_flat_view()
|
||||||
|
self.selected = 0
|
||||||
|
self.page = 0
|
||||||
|
elif key in (curses.KEY_BACKSPACE, 127, 8):
|
||||||
|
self.search_buffer = self.search_buffer[:-1]
|
||||||
|
self.rebuild_flat_view()
|
||||||
|
self.selected = 0
|
||||||
|
self.page = 0
|
||||||
|
elif 32 <= key <= 126:
|
||||||
|
self.search_buffer += chr(key)
|
||||||
|
self.rebuild_flat_view()
|
||||||
|
self.selected = 0
|
||||||
|
self.page = 0
|
||||||
else:
|
else:
|
||||||
if key == ord(':'):
|
if key == ord(':'):
|
||||||
self.command_mode = True
|
self.command_mode = True
|
||||||
self.command_buffer = ""
|
self.command_buffer = ""
|
||||||
|
elif key == ord('/'):
|
||||||
|
self.search_mode = True
|
||||||
|
self.search_buffer = ""
|
||||||
elif key in (ord('j'), curses.KEY_DOWN):
|
elif key in (ord('j'), curses.KEY_DOWN):
|
||||||
if self.selected < len(self.files) - 1:
|
if self.selected < len(self.flat_view) - 1:
|
||||||
self.selected += 1
|
self.selected += 1
|
||||||
if self.selected >= (self.page + 1) * self.items_per_page:
|
if self.selected >= (self.page + 1) * self.items_per_page:
|
||||||
self.page += 1
|
self.page += 1
|
||||||
@ -186,8 +395,14 @@ class Confy:
|
|||||||
if self.selected < self.page * self.items_per_page:
|
if self.selected < self.page * self.items_per_page:
|
||||||
self.page -= 1
|
self.page -= 1
|
||||||
elif key == ord('\n'):
|
elif key == ord('\n'):
|
||||||
if self.files and 0 <= self.selected < len(self.files):
|
if self.flat_view and self.selected < len(self.flat_view):
|
||||||
self.open_file(self.files[self.selected])
|
item = self.flat_view[self.selected]
|
||||||
|
if item[0] == 'file':
|
||||||
|
self.open_file(item[1])
|
||||||
|
elif item[0] == 'group':
|
||||||
|
self.toggle_group()
|
||||||
|
elif key == ord(' '):
|
||||||
|
self.toggle_group()
|
||||||
elif key == ord('q'):
|
elif key == ord('q'):
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user