initial commit: confy v1.0
This commit is contained in:
199
main.py
Executable file
199
main.py
Executable file
@ -0,0 +1,199 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import curses
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
|
||||
CONFIG_DIR = Path.home() / ".config" / "confy"
|
||||
TRACKED_FILE = CONFIG_DIR / "tracked.json"
|
||||
|
||||
class Confy:
|
||||
def __init__(self):
|
||||
self.files = []
|
||||
self.selected = 0
|
||||
self.page = 0
|
||||
self.items_per_page = 10
|
||||
self.command_mode = False
|
||||
self.command_buffer = ""
|
||||
self.last_opened = None
|
||||
self.config_dir = str(Path.home() / ".config")
|
||||
self.load_data()
|
||||
|
||||
def load_data(self):
|
||||
if TRACKED_FILE.exists():
|
||||
with open(TRACKED_FILE, 'r') as f:
|
||||
data = json.load(f)
|
||||
self.files = data.get('files', [])
|
||||
self.last_opened = data.get('last_opened')
|
||||
|
||||
def save_data(self):
|
||||
CONFIG_DIR.mkdir(parents=True, exist_ok=True)
|
||||
with open(TRACKED_FILE, 'w') as f:
|
||||
json.dump({
|
||||
'files': self.files,
|
||||
'last_opened': self.last_opened
|
||||
}, f, indent=2)
|
||||
|
||||
def get_file_info(self, filepath):
|
||||
try:
|
||||
stat = os.stat(filepath)
|
||||
mtime = datetime.fromtimestamp(stat.st_mtime).strftime('%Y-%m-%d %H:%M')
|
||||
size = self.format_size(stat.st_size)
|
||||
return mtime, size
|
||||
except:
|
||||
return "unknown", "unknown"
|
||||
|
||||
def format_size(self, size):
|
||||
for unit in ['B', 'KB', 'MB', 'GB']:
|
||||
if size < 1024:
|
||||
return f"{size:.1f}{unit}"
|
||||
size /= 1024
|
||||
return f"{size:.1f}TB"
|
||||
|
||||
def add_config(self):
|
||||
curses.endwin()
|
||||
try:
|
||||
result = subprocess.run(['ranger', '--choosefile=/tmp/confy_pick', self.config_dir])
|
||||
if os.path.exists('/tmp/confy_pick'):
|
||||
with open('/tmp/confy_pick', 'r') as f:
|
||||
filepath = f.read().strip()
|
||||
if filepath and filepath not in self.files:
|
||||
self.files.append(filepath)
|
||||
self.save_data()
|
||||
os.remove('/tmp/confy_pick')
|
||||
except Exception as e:
|
||||
pass
|
||||
curses.doupdate()
|
||||
|
||||
def remove_config(self):
|
||||
if self.files and 0 <= self.selected < len(self.files):
|
||||
del self.files[self.selected]
|
||||
if self.selected >= len(self.files) and self.files:
|
||||
self.selected = len(self.files) - 1
|
||||
self.save_data()
|
||||
|
||||
def open_file(self, filepath):
|
||||
editor = os.environ.get('EDITOR', 'nano')
|
||||
curses.endwin()
|
||||
try:
|
||||
subprocess.run([editor, filepath])
|
||||
except FileNotFoundError:
|
||||
input(f"error: editor '{editor}' not found. press enter to continue...")
|
||||
except Exception as e:
|
||||
input(f"error opening file: {e}. press enter to continue...")
|
||||
curses.doupdate()
|
||||
self.last_opened = filepath
|
||||
self.save_data()
|
||||
|
||||
def draw(self, stdscr):
|
||||
height, width = stdscr.getmaxyx()
|
||||
stdscr.clear()
|
||||
|
||||
# top bar
|
||||
stdscr.addstr(0, 1, "confy")
|
||||
last_text = f"previous: {{{Path(self.last_opened).name if self.last_opened else 'none'}}}"
|
||||
stdscr.addstr(1, 1, last_text)
|
||||
config_text = f"config dir is {self.config_dir}"
|
||||
stdscr.addstr(1, width - len(config_text) - 2, config_text)
|
||||
stdscr.addstr(2, 0, "═" * width)
|
||||
|
||||
# file list
|
||||
start_idx = self.page * self.items_per_page
|
||||
end_idx = min(start_idx + self.items_per_page, len(self.files))
|
||||
|
||||
for i in range(start_idx, end_idx):
|
||||
y = 4 + (i - start_idx)
|
||||
filepath = self.files[i]
|
||||
filename = Path(filepath).name
|
||||
directory = str(Path(filepath).parent)
|
||||
mtime, size = self.get_file_info(filepath)
|
||||
|
||||
line = f"{filename:<20} | {directory:<30} | {mtime:<16} | {size:<10}"
|
||||
if i == self.selected:
|
||||
line = f"{line} <"
|
||||
stdscr.attron(curses.A_REVERSE)
|
||||
stdscr.addstr(y, 1, line[:width-2])
|
||||
if i == self.selected:
|
||||
stdscr.attroff(curses.A_REVERSE)
|
||||
|
||||
# bottom bar
|
||||
total_pages = (len(self.files) + self.items_per_page - 1) // self.items_per_page
|
||||
if total_pages == 0:
|
||||
total_pages = 1
|
||||
bottom_y = height - 2
|
||||
stdscr.addstr(bottom_y, 0, "═" * width)
|
||||
if self.command_mode:
|
||||
page_text = f"page {self.page + 1}/{total_pages} ▌ :{self.command_buffer}"
|
||||
else:
|
||||
page_text = f"page {self.page + 1}/{total_pages} ▌"
|
||||
stdscr.addstr(bottom_y + 1, 1, page_text[:width-2])
|
||||
|
||||
stdscr.refresh()
|
||||
|
||||
def handle_command(self):
|
||||
cmd = self.command_buffer.strip()
|
||||
if cmd == "q":
|
||||
return False
|
||||
elif cmd == "ac":
|
||||
self.add_config()
|
||||
elif cmd == "rm":
|
||||
self.remove_config()
|
||||
elif cmd == "l":
|
||||
if self.last_opened and os.path.exists(self.last_opened):
|
||||
self.open_file(self.last_opened)
|
||||
self.command_buffer = ""
|
||||
self.command_mode = False
|
||||
return True
|
||||
|
||||
def run(self, stdscr):
|
||||
curses.curs_set(0)
|
||||
stdscr.timeout(100)
|
||||
|
||||
while True:
|
||||
self.draw(stdscr)
|
||||
|
||||
try:
|
||||
key = stdscr.getch()
|
||||
except:
|
||||
continue
|
||||
|
||||
if self.command_mode:
|
||||
if key == ord('\n'):
|
||||
if not self.handle_command():
|
||||
break
|
||||
elif key == 27: # ESC
|
||||
self.command_mode = False
|
||||
self.command_buffer = ""
|
||||
elif key in (curses.KEY_BACKSPACE, 127, 8):
|
||||
self.command_buffer = self.command_buffer[:-1]
|
||||
elif 32 <= key <= 126:
|
||||
self.command_buffer += chr(key)
|
||||
else:
|
||||
if key == ord(':'):
|
||||
self.command_mode = True
|
||||
self.command_buffer = ""
|
||||
elif key in (ord('j'), curses.KEY_DOWN):
|
||||
if self.selected < len(self.files) - 1:
|
||||
self.selected += 1
|
||||
if self.selected >= (self.page + 1) * self.items_per_page:
|
||||
self.page += 1
|
||||
elif key in (ord('k'), curses.KEY_UP):
|
||||
if self.selected > 0:
|
||||
self.selected -= 1
|
||||
if self.selected < self.page * self.items_per_page:
|
||||
self.page -= 1
|
||||
elif key == ord('\n'):
|
||||
if self.files and 0 <= self.selected < len(self.files):
|
||||
self.open_file(self.files[self.selected])
|
||||
elif key == ord('q'):
|
||||
break
|
||||
|
||||
def main():
|
||||
app = Confy()
|
||||
curses.wrapper(app.run)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user