Compare commits
No commits in common. "325c6def66ef50dff6e413cbb96c767f69322d3b" and "50c5b72683a4611f0f44345b004c21e0d1e23cca" have entirely different histories.
325c6def66
...
50c5b72683
|
|
@ -64,7 +64,6 @@ vim.g.airline_section_x = "IP:" .. ipaddr .. " DNS:" .. hostname
|
||||||
vim.g.loaded_ruby_provider = 0
|
vim.g.loaded_ruby_provider = 0
|
||||||
vim.g.loaded_perl_provider = 0
|
vim.g.loaded_perl_provider = 0
|
||||||
|
|
||||||
|
|
||||||
-- ── Editor options ────────────────────────────────────────────────────────────
|
-- ── Editor options ────────────────────────────────────────────────────────────
|
||||||
vim.cmd("filetype plugin indent on")
|
vim.cmd("filetype plugin indent on")
|
||||||
vim.cmd("syntax enable")
|
vim.cmd("syntax enable")
|
||||||
|
|
@ -232,7 +231,7 @@ local function toggle_solo(key, cmd, label)
|
||||||
end
|
end
|
||||||
|
|
||||||
vim.keymap.set("n", "n", function() toggle_solo("n", "terminal alot", "alot") end, { silent = true })
|
vim.keymap.set("n", "n", function() toggle_solo("n", "terminal alot", "alot") end, { silent = true })
|
||||||
vim.keymap.set("n", "g", function() toggle_solo("g", "terminal khal interactive", "khal") end, { silent = true })
|
vim.keymap.set("n", "g", function() toggle_solo("g", "Calendar -position=here", "calendar.vim") end, { silent = true })
|
||||||
vim.keymap.set("n", "f", function() toggle_solo("f", "terminal abook", "abook") end, { silent = true })
|
vim.keymap.set("n", "f", function() toggle_solo("f", "terminal abook", "abook") end, { silent = true })
|
||||||
|
|
||||||
-- r: sideward-T overlay — left column (bar of the T) with three stacked panes,
|
-- r: sideward-T overlay — left column (bar of the T) with three stacked panes,
|
||||||
|
|
@ -249,9 +248,9 @@ local function toggle_pim()
|
||||||
-- full-screen: alot left, abook top-right, calendar bottom-right
|
-- full-screen: alot left, abook top-right, calendar bottom-right
|
||||||
local H = vim.o.lines - 2
|
local H = vim.o.lines - 2
|
||||||
local W = vim.o.columns
|
local W = vim.o.columns
|
||||||
local right_w = math.max(80, math.floor(W * 0.45))
|
local left_w = math.max(1, math.floor(W * 0.55))
|
||||||
local left_w = math.max(1, W - right_w)
|
local right_w = math.max(1, W - left_w)
|
||||||
local top_h = math.max(20, math.floor(H / 2))
|
local top_h = math.max(1, math.floor(H / 2))
|
||||||
local bot_h = math.max(1, H - top_h)
|
local bot_h = math.max(1, H - top_h)
|
||||||
|
|
||||||
local w1 = _pim_float(0, 0, H, left_w)
|
local w1 = _pim_float(0, 0, H, left_w)
|
||||||
|
|
@ -263,22 +262,11 @@ local function toggle_pim()
|
||||||
if not ok then _pim_scratch("abook", err) end
|
if not ok then _pim_scratch("abook", err) end
|
||||||
|
|
||||||
local w3 = _pim_float(top_h, left_w, bot_h, right_w)
|
local w3 = _pim_float(top_h, left_w, bot_h, right_w)
|
||||||
ok, err = pcall(vim.cmd, "terminal khal interactive")
|
ok, err = pcall(vim.cmd, "Calendar -position=here")
|
||||||
if not ok then _pim_scratch("khal", err) end
|
if not ok then _pim_scratch("calendar.vim", err) end
|
||||||
|
|
||||||
_pim_wins = { w1, w2, w3 }
|
_pim_wins = { w1, w2, w3 }
|
||||||
vim.api.nvim_set_current_win(w1)
|
vim.api.nvim_set_current_win(w1)
|
||||||
end
|
end
|
||||||
|
|
||||||
vim.keymap.set("n", "x", toggle_pim, { silent = true })
|
vim.keymap.set("n", "x", toggle_pim, { silent = true })
|
||||||
|
|
||||||
-- ── CalDAV background sync on startup ────────────────────────────────────────
|
|
||||||
vim.api.nvim_create_autocmd("VimEnter", {
|
|
||||||
once = true,
|
|
||||||
callback = function()
|
|
||||||
vim.fn.jobstart(
|
|
||||||
{ "sh", "-c", "vdirsyncer sync && ics-to-calendarim" },
|
|
||||||
{ detach = true }
|
|
||||||
)
|
|
||||||
end,
|
|
||||||
})
|
|
||||||
|
|
|
||||||
|
|
@ -1,212 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
set -euo pipefail
|
|
||||||
source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
|
|
||||||
|
|
||||||
log "Installing CalDAV sync stack..."
|
|
||||||
sudo pacman -S --noconfirm --needed vdirsyncer khal python-icalendar
|
|
||||||
|
|
||||||
# ── Credentials ───────────────────────────────────────────────────────────────
|
|
||||||
read -rp "CalDAV server URL (e.g. https://dav.example.com/cal/): " CALDAV_URL
|
|
||||||
read -rp "Username : " CALDAV_USER
|
|
||||||
read -rsp "Password : " CALDAV_PASS; echo
|
|
||||||
read -rp "Calendar display name [Personal] : " CAL_NAME
|
|
||||||
CAL_NAME="${CAL_NAME:-Personal}"
|
|
||||||
|
|
||||||
CALDAV_DIR="$HOME/.local/share/calendars"
|
|
||||||
mkdir -p "$CALDAV_DIR"
|
|
||||||
|
|
||||||
# ── vdirsyncer ────────────────────────────────────────────────────────────────
|
|
||||||
log "Writing ~/.config/vdirsyncer/config..."
|
|
||||||
mkdir -p ~/.config/vdirsyncer ~/.local/share/vdirsyncer/status
|
|
||||||
cat > ~/.config/vdirsyncer/config << EOF
|
|
||||||
[general]
|
|
||||||
status_path = "$HOME/.local/share/vdirsyncer/status/"
|
|
||||||
|
|
||||||
[pair calendars]
|
|
||||||
a = "local_cals"
|
|
||||||
b = "remote_cals"
|
|
||||||
collections = ["from b"]
|
|
||||||
metadata = ["color", "displayname"]
|
|
||||||
|
|
||||||
[storage local_cals]
|
|
||||||
type = "filesystem"
|
|
||||||
path = "$CALDAV_DIR"
|
|
||||||
fileext = ".ics"
|
|
||||||
|
|
||||||
[storage remote_cals]
|
|
||||||
type = "caldav"
|
|
||||||
url = "$CALDAV_URL"
|
|
||||||
username = "$CALDAV_USER"
|
|
||||||
password = "$CALDAV_PASS"
|
|
||||||
EOF
|
|
||||||
chmod 600 ~/.config/vdirsyncer/config
|
|
||||||
|
|
||||||
log "Discovering CalDAV collections (confirm any prompts with y)..."
|
|
||||||
yes | vdirsyncer discover calendars || true
|
|
||||||
|
|
||||||
log "Running initial sync..."
|
|
||||||
vdirsyncer sync
|
|
||||||
|
|
||||||
# ── khal (CLI calendar companion) ────────────────────────────────────────────
|
|
||||||
log "Writing ~/.config/khal/config (per-calendar entries)..."
|
|
||||||
mkdir -p ~/.config/khal
|
|
||||||
python3 - "$CALDAV_DIR" << 'PYEOF'
|
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
cal_root = Path(sys.argv[1])
|
|
||||||
colors = ["light blue", "light green", "light red", "light magenta", "light cyan"]
|
|
||||||
dirs = sorted(d for d in cal_root.iterdir() if d.is_dir())
|
|
||||||
|
|
||||||
lines = ["[calendars]"]
|
|
||||||
for i, d in enumerate(dirs):
|
|
||||||
lines += [f" [[{d.name}]]", f" path = {d}", f" color = {colors[i % len(colors)]}"]
|
|
||||||
|
|
||||||
lines += [
|
|
||||||
"", "[sqlite]", "path = ~/.local/share/khal/khal.db",
|
|
||||||
"", "[locale]", "timeformat = %H:%M", "dateformat = %Y-%m-%d",
|
|
||||||
"datetimeformat = %Y-%m-%d %H:%M",
|
|
||||||
]
|
|
||||||
|
|
||||||
(Path.home() / ".config/khal/config").write_text("\n".join(lines) + "\n")
|
|
||||||
PYEOF
|
|
||||||
|
|
||||||
# ── ICS → calendar.vim cache converter ───────────────────────────────────────
|
|
||||||
log "Installing ics-to-calendarim converter..."
|
|
||||||
mkdir -p ~/.local/bin
|
|
||||||
cat > ~/.local/bin/ics-to-calendarim << 'PYEOF'
|
|
||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
Convert vdirsyncer ICS files to calendar.vim local event cache.
|
|
||||||
Cache lives at ~/.cache/calendar.vim/local/ in the format:
|
|
||||||
calendarList – list of calendars (JSON)
|
|
||||||
<id>/<YYYY>/<MM>/0 – events per month (JSON)
|
|
||||||
"""
|
|
||||||
import json, sys
|
|
||||||
from pathlib import Path
|
|
||||||
from datetime import datetime, date, timezone
|
|
||||||
|
|
||||||
try:
|
|
||||||
from icalendar import Calendar as iCal
|
|
||||||
except ImportError:
|
|
||||||
sys.exit("python-icalendar is required: sudo pacman -S python-icalendar")
|
|
||||||
|
|
||||||
CALDAV_DIR = Path.home() / ".local/share/calendars"
|
|
||||||
CACHE_DIR = Path.home() / ".cache/calendar.vim/local"
|
|
||||||
KHAL_CFG = Path.home() / ".config/khal/config"
|
|
||||||
|
|
||||||
COLORS = ["#4CAF50", "#2196F3", "#FF5722", "#9C27B0", "#FF9800"]
|
|
||||||
KHAL_COLORS = ["light blue", "light green", "light red", "light magenta", "light cyan"]
|
|
||||||
|
|
||||||
def to_naive(dt):
|
|
||||||
if isinstance(dt, datetime):
|
|
||||||
if dt.tzinfo is not None:
|
|
||||||
dt = dt.astimezone(timezone.utc).replace(tzinfo=None)
|
|
||||||
return dt.strftime("%Y-%m-%dT%H:%M:%S"), True, dt.year, dt.month
|
|
||||||
if isinstance(dt, date):
|
|
||||||
return dt.strftime("%Y-%m-%d"), False, dt.year, dt.month
|
|
||||||
return None, False, None, None
|
|
||||||
|
|
||||||
def main():
|
|
||||||
CACHE_DIR.mkdir(parents=True, exist_ok=True)
|
|
||||||
cal_items = []
|
|
||||||
|
|
||||||
for idx, cal_dir in enumerate(sorted(d for d in CALDAV_DIR.iterdir() if d.is_dir())):
|
|
||||||
cal_id = cal_dir.name
|
|
||||||
cal_summary = cal_dir.name.replace("_", " ").title()
|
|
||||||
cal_items.append({
|
|
||||||
"id": cal_id,
|
|
||||||
"summary": cal_summary,
|
|
||||||
"backgroundColor": COLORS[idx % len(COLORS)],
|
|
||||||
"foregroundColor": "#ffffff",
|
|
||||||
})
|
|
||||||
|
|
||||||
by_month: dict = {}
|
|
||||||
for ics in cal_dir.glob("*.ics"):
|
|
||||||
try:
|
|
||||||
cal = iCal.from_ical(ics.read_bytes())
|
|
||||||
except Exception as e:
|
|
||||||
print(f"warning: skipping {ics.name}: {e}", file=sys.stderr)
|
|
||||||
continue
|
|
||||||
for comp in cal.walk("VEVENT"):
|
|
||||||
dtstart = comp.get("DTSTART")
|
|
||||||
dtend = comp.get("DTEND") or comp.get("DTSTART")
|
|
||||||
if not dtstart:
|
|
||||||
continue
|
|
||||||
s_str, is_timed, yr, mo = to_naive(dtstart.dt)
|
|
||||||
e_str, _, _, _ = to_naive(dtend.dt)
|
|
||||||
if not s_str:
|
|
||||||
continue
|
|
||||||
uid = str(comp.get("UID", ics.stem))
|
|
||||||
summary = str(comp.get("SUMMARY", ""))
|
|
||||||
key = (yr, mo)
|
|
||||||
by_month.setdefault(key, []).append({
|
|
||||||
"id": uid,
|
|
||||||
"summary": summary,
|
|
||||||
"start": {"dateTime": s_str} if is_timed else {"date": s_str},
|
|
||||||
"end": {"dateTime": e_str} if is_timed else {"date": e_str},
|
|
||||||
})
|
|
||||||
|
|
||||||
for (yr, mo), events in by_month.items():
|
|
||||||
out = CACHE_DIR / cal_id / f"{yr:04d}" / f"{mo:02d}"
|
|
||||||
out.mkdir(parents=True, exist_ok=True)
|
|
||||||
(out / "0").write_text(json.dumps({"items": events}))
|
|
||||||
|
|
||||||
(CACHE_DIR / "calendarList").write_text(json.dumps({"items": cal_items}))
|
|
||||||
|
|
||||||
# regenerate khal config so new calendars are picked up automatically
|
|
||||||
cal_dirs = sorted(d for d in CALDAV_DIR.iterdir() if d.is_dir())
|
|
||||||
khal_lines = ["[calendars]"]
|
|
||||||
for i, d in enumerate(cal_dirs):
|
|
||||||
khal_lines += [f" [[{d.name}]]", f" path = {d}",
|
|
||||||
f" color = {KHAL_COLORS[i % len(KHAL_COLORS)]}"]
|
|
||||||
khal_lines += ["", "[sqlite]", "path = ~/.local/share/khal/khal.db",
|
|
||||||
"", "[locale]", "timeformat = %H:%M", "dateformat = %Y-%m-%d",
|
|
||||||
"datetimeformat = %Y-%m-%d %H:%M"]
|
|
||||||
KHAL_CFG.write_text("\n".join(khal_lines) + "\n")
|
|
||||||
|
|
||||||
print(f"calendar.vim cache updated: {len(cal_items)} calendar(s).")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
PYEOF
|
|
||||||
chmod +x ~/.local/bin/ics-to-calendarim
|
|
||||||
|
|
||||||
log "Running initial calendar.vim cache build..."
|
|
||||||
~/.local/bin/ics-to-calendarim
|
|
||||||
|
|
||||||
# ── systemd user timer ────────────────────────────────────────────────────────
|
|
||||||
log "Installing vdirsyncer systemd user timer (every 15 min)..."
|
|
||||||
mkdir -p ~/.config/systemd/user
|
|
||||||
|
|
||||||
cat > ~/.config/systemd/user/vdirsyncer.service << EOF
|
|
||||||
[Unit]
|
|
||||||
Description=Sync CalDAV and rebuild calendar.vim cache
|
|
||||||
After=network-online.target
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=oneshot
|
|
||||||
ExecStart=/usr/bin/vdirsyncer sync
|
|
||||||
ExecStartPost=$HOME/.local/bin/ics-to-calendarim
|
|
||||||
EOF
|
|
||||||
|
|
||||||
cat > ~/.config/systemd/user/vdirsyncer.timer << EOF
|
|
||||||
[Unit]
|
|
||||||
Description=Run vdirsyncer every 15 minutes
|
|
||||||
|
|
||||||
[Timer]
|
|
||||||
OnBootSec=2min
|
|
||||||
OnUnitActiveSec=15min
|
|
||||||
Unit=vdirsyncer.service
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=timers.target
|
|
||||||
EOF
|
|
||||||
|
|
||||||
systemctl --user daemon-reload
|
|
||||||
systemctl --user enable --now vdirsyncer.timer
|
|
||||||
|
|
||||||
log "CalDAV sync configured. Events will appear in calendar.vim automatically."
|
|
||||||
log "Manual sync: vdirsyncer sync && ics-to-calendarim"
|
|
||||||
log "CLI calendar: khal list / khal interactive"
|
|
||||||
log "Timer status: systemctl --user status vdirsyncer.timer"
|
|
||||||
Loading…
Reference in New Issue