feat: track Decman configuration

This commit is contained in:
2026-08-13 23:03:55 +02:00
parent 34acd648c9
commit f99cfeae79
96 changed files with 7120 additions and 0 deletions
@@ -0,0 +1,3 @@
# Used by the Noctalia Pi Status Bridge adapter and the Hyprland launcher.
PI_STATUS_BRIDGE_CLIENT=/home/alex/.npm-global/bin/pi-status-bridge-client
PI_STATUS_UI_BINARY=/home/alex/.local/bin/pi-status-ui
@@ -0,0 +1,44 @@
starship init fish | source
set fish_greeting ""
# Prefer Neovim for both explicit and editor-variable invocations.
alias vim nvim
set -gx EDITOR nvim
set -gx VISUAL nvim
# pyenv setup
while set pyenv_index (contains -i -- "/home/alex/.pyenv/shims" $PATH)
set -eg PATH[$pyenv_index]
end
set -e pyenv_index
set -gx PATH '/home/alex/.pyenv/shims' $PATH
set -gx PYENV_SHELL fish
command pyenv rehash 2>/dev/null
function pyenv
set command $argv[1]
set -e argv[1]
switch "$command"
case rehash shell
source (pyenv "sh-$command" $argv|psub)
case "*"
command pyenv "$command" $argv
end
end
# bun
set --export BUN_INSTALL "$HOME/.bun"
set --export PATH $BUN_INSTALL/bin $PATH
# opencode
fish_add_path /home/alex/.opencode/bin
# Yazi wrapper that keeps the shell in its selected directory.
function y
set tmp (mktemp -t "yazi-cwd.XXXXXX")
command yazi $argv --cwd-file="$tmp"
if read -z cwd <"$tmp"; and [ "$cwd" != "$PWD" ]; and test -d "$cwd"
builtin cd -- "$cwd"
end
command rm -f -- "$tmp"
end
@@ -0,0 +1,6 @@
# Non-locking Wayland screen blanker. It covers every monitor in black and
# dismisses itself on any key press or mouse click; it never changes DPMS.
listener {
timeout = 300
on-timeout = /home/alex/.local/bin/wayshell-blanker
}
@@ -0,0 +1,165 @@
-- Shared Hyprland profile. yadm deploys machine.lua for the active class.
--
-- Scope: preserve launch, media, directional focus/move, workspace, scratchpad,
-- floating, fullscreen, and window-placement muscle memory. i3's split-tree,
-- stacking/tabbed, and resize-mode behavior is intentionally deferred.
local machine = dofile(os.getenv("HOME") .. "/.config/hypr/machine.lua")
for _, monitor in ipairs(machine.monitors or {}) do
hl.monitor(monitor)
end
local main_mod = "SUPER"
local terminal = "kitty"
local input = {
kb_layout = "us",
kb_variant = "intl",
numlock_by_default = true,
}
for key, value in pairs(machine.input or {}) do
input[key] = value
end
hl.config({
input = input,
general = {
gaps_in = 4,
-- Keep a thin black perimeter so the bottom border remains on-screen.
gaps_out = 2,
border_size = 2,
col = {
-- Muted Ember tones: visible focus without a high-brightness OLED edge.
active_border = "rgba(c9782ed9)",
inactive_border = "rgba(2a190fa6)",
},
},
decoration = {
rounding = 0,
},
animations = {
enabled = false,
},
binds = {
-- Selecting the current workspace again returns to the last workspace.
workspace_back_and_forth = true,
},
})
-- Three-finger horizontal swipes change workspaces; touchscreens remain excluded
-- by Hyprland's default workspace_swipe_touch = false behavior.
hl.gesture({
fingers = 3,
direction = "horizontal",
action = "workspace",
})
-- Dynamically hide the border when a workspace has exactly one tiled app.
hl.workspace_rule({
workspace = "w[t1]",
no_border = true,
})
-- Application and session bindings.
local noctalia_ipc = "noctalia msg "
local pi_status_ui_toggle = 'ui="${PI_STATUS_UI_BINARY:-$HOME/.local/bin/pi-status-ui}"; if test -x "$ui"; then setsid -f env TMPDIR=/tmp "$ui" --toggle >/tmp/pi-status-ui.log 2>&1; fi'
-- Tap and release Super on its own to open Noctalia's control-center home.
hl.bind(main_mod .. " + SUPER_L", hl.dsp.exec_cmd(noctalia_ipc .. "panel-toggle control-center"), { release = true })
hl.bind(main_mod .. " + RETURN", hl.dsp.exec_cmd(terminal))
hl.bind(main_mod .. " + CTRL + RETURN", hl.dsp.exec_cmd(terminal .. " -e zellij"))
hl.bind(main_mod .. " + SHIFT + RETURN", hl.dsp.exec_cmd("rofi -show combi"))
hl.bind(main_mod .. " + SPACE", hl.dsp.exec_cmd(pi_status_ui_toggle))
hl.bind(main_mod .. " + N", hl.dsp.exec_cmd(noctalia_ipc .. "panel-toggle alex/knowledge-lookup:panel"))
hl.bind(main_mod .. " + S", hl.dsp.exec_cmd(noctalia_ipc .. "panel-toggle control-center"))
hl.bind(main_mod .. " + 0", hl.dsp.exec_cmd(noctalia_ipc .. "panel-toggle session"))
hl.bind(main_mod .. " + SHIFT + F", hl.dsp.exec_cmd(noctalia_ipc .. "bar-toggle main"))
hl.bind(main_mod .. " + SHIFT + R", hl.dsp.exec_cmd("hyprctl reload"))
hl.bind(main_mod .. " + CTRL + L", hl.dsp.exec_cmd("command -v hyprlock >/dev/null && hyprlock || loginctl lock-session"))
hl.bind(main_mod .. " + CTRL + SHIFT + ALT + S", hl.dsp.exec_cmd("systemctl suspend"))
hl.bind(main_mod .. " + CTRL + SHIFT + ALT + H", hl.dsp.exec_cmd("systemctl hibernate"))
hl.bind(main_mod .. " + SHIFT + C", hl.dsp.window.close())
hl.bind(main_mod .. " + CTRL + SHIFT + F", hl.dsp.exec_cmd("/home/alex/.local/bin/screenshot-select"))
-- Audio, media, brightness, and calculator keys.
hl.bind("XF86AudioLowerVolume", hl.dsp.exec_cmd("wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-"), { locked = true })
hl.bind("XF86AudioRaiseVolume", hl.dsp.exec_cmd("wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%+"), { locked = true })
hl.bind("XF86AudioMute", hl.dsp.exec_cmd("wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle"), { locked = true })
hl.bind(main_mod .. " + XF86AudioMute", hl.dsp.exec_cmd("wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle"), { locked = true })
hl.bind("XF86AudioPlay", hl.dsp.exec_cmd("playerctl play-pause"), { locked = true })
hl.bind("XF86AudioNext", hl.dsp.exec_cmd("playerctl next"), { locked = true })
hl.bind("XF86AudioPrev", hl.dsp.exec_cmd("playerctl previous"), { locked = true })
hl.bind(main_mod .. " + XF86AudioRaiseVolume", hl.dsp.exec_cmd("playerctl next"), { locked = true })
hl.bind(main_mod .. " + XF86AudioLowerVolume", hl.dsp.exec_cmd("playerctl previous"), { locked = true })
hl.bind("XF86MonBrightnessUp", hl.dsp.exec_cmd('light -A 5'), { locked = true })
hl.bind("XF86MonBrightnessDown", hl.dsp.exec_cmd('light -U 5'), { locked = true })
hl.bind(main_mod .. " + XF86MonBrightnessDown", hl.dsp.exec_cmd('light -S 0.0'), { locked = true })
hl.bind(main_mod .. " + XF86MonBrightnessUp", hl.dsp.exec_cmd('light -S 100'), { locked = true })
hl.bind("XF86Calculator", hl.dsp.exec_cmd("qalculate-qt"), { locked = true })
-- Retain i3's hjkl and arrow-key directional workflow.
for _, direction in ipairs({ "left", "down", "up", "right" }) do
local key = ({ left = "H", down = "J", up = "K", right = "L" })[direction]
hl.bind(main_mod .. " + " .. key, hl.dsp.focus({ direction = direction }))
hl.bind(main_mod .. " + " .. direction, hl.dsp.focus({ direction = direction }))
end
for key, direction in pairs({ H = "l", J = "d", K = "u", L = "r", LEFT = "l", DOWN = "d", UP = "u", RIGHT = "r" }) do
hl.bind(main_mod .. " + SHIFT + " .. key, hl.dsp.window.move({ direction = direction }))
end
-- Numbered workspaces match the i3 profile. Shift moves; Ctrl moves and follows.
for workspace = 1, 9 do
local key = tostring(workspace)
hl.bind(main_mod .. " + " .. key, hl.dsp.focus({ workspace = workspace }))
hl.bind(main_mod .. " + SHIFT + " .. key, hl.dsp.window.move({ workspace = workspace }))
hl.bind(main_mod .. " + CTRL + " .. key, hl.dsp.window.move({ workspace = workspace, follow = true }))
end
-- Match i3's back-and-forth workflow: Super+Tab switches to the last workspace;
-- selecting the current numbered workspace also returns to it.
hl.bind(main_mod .. " + TAB", hl.dsp.focus({ workspace = "previous" }))
hl.bind(main_mod .. " + CTRL + RIGHT", hl.dsp.focus({ workspace = "+1" }))
hl.bind(main_mod .. " + CTRL + LEFT", hl.dsp.focus({ workspace = "-1" }))
-- Hyprland special workspaces are the Wayland analogue of the i3 scratchpad.
hl.bind(main_mod .. " + MINUS", hl.dsp.workspace.toggle_special("scratchpad"))
hl.bind(main_mod .. " + SHIFT + MINUS", hl.dsp.window.move({ workspace = "special:scratchpad" }))
-- Keep the i3 floating and fullscreen shortcuts. Layout-specific binds are deferred.
hl.bind(main_mod .. " + F", hl.dsp.window.fullscreen({ action = "toggle" }))
hl.bind(main_mod .. " + SHIFT + SPACE", hl.dsp.window.float({ action = "toggle" }))
hl.bind(main_mod .. " + mouse:272", hl.dsp.window.drag(), { mouse = true })
hl.bind(main_mod .. " + mouse:273", hl.dsp.window.resize(), { mouse = true })
-- i3 for_window/assign translations. Verify Wayland class/title values with `hyprctl clients`.
local function float_rule(name, match, border_size)
hl.window_rule({
name = name,
match = match,
float = true,
border_size = border_size,
})
end
hl.window_rule({ name = "obsidian-workspace", match = { class = "^obsidian$" }, workspace = "8" })
hl.window_rule({ name = "thunderbird-workspace", match = { class = "^Thunderbird$" }, workspace = "9" })
float_rule("pi-status-ui", { class = "^(pi-status-ui|Pi-status-ui)$" }, 1)
float_rule("alsamixer", { title = "^alsamixer$" }, 1)
float_rule("qalculate", { title = "^Qalculate!$" })
float_rule("psensor", { class = "^Psensor$" })
float_rule("lxrandr", { title = "^lxrandr$" })
float_rule("blueman", { class = "^Blueman-manager$" })
float_rule("gparted", { class = "^GParted$" })
float_rule("pavucontrol", { class = "^pavucontrol$" })
float_rule("volume-control", { title = "^Volume Control$" })
float_rule("virtualbox", { class = ".*VirtualBox.*" })
hl.window_rule({ name = "virtualbox-manager-tiles", match = { class = ".*VirtualBox Manager.*" }, tile = true })
hl.window_rule({ name = "virtualbox-machine-tiles", match = { class = ".*VirtualBox Machine.*" }, tile = true })
-- Send music clients to the existing scratchpad without stealing startup focus.
hl.window_rule({ name = "spotify-music", match = { class = "^Spotify$" }, workspace = "special:scratchpad silent", no_initial_focus = true })
hl.window_rule({ name = "deezer-music", match = { class = "^Deezer$" }, workspace = "special:scratchpad silent", no_initial_focus = true })
hl.on("hyprland.start", function()
for _, command in ipairs(machine.startup or {}) do
hl.exec_cmd(command)
end
end)
@@ -0,0 +1,45 @@
# Minimal Hyprlock screen using the current desktop as a blurred backdrop.
$font = Sans
general {
hide_cursor = true
}
background {
monitor =
path = screenshot
blur_passes = 3
}
input-field {
monitor =
size = 20%, 5%
outline_thickness = 2
inner_color = rgba(0, 0, 0, 0.25)
outer_color = rgba(eeeeeecc)
check_color = rgba(8ec07ccc)
fail_color = rgba(fb4934cc)
font_color = rgb(eeeeee)
fade_on_empty = false
rounding = 12
font_family = $font
placeholder_text = Input password...
fail_text = $PAMFAIL
position = 0, -20
halign = center
valign = center
}
label {
monitor =
text = $TIME
font_size = 72
font_family = $font
position = 0, 120
halign = center
valign = center
}
@@ -0,0 +1,35 @@
# Match the active Alacritty look while retaining Kitty's native graphics protocol.
font_family MesloLGS NF
font_size 15.0
# Palette: active ~/.config/alacritty/alacritty.toml plus Noctalia cursor/selection.
foreground #FCFCFA
background #2D2A2E
cursor #fff1e6
cursor_text_color #000000
selection_foreground #fff1e6
selection_background #3b220f
color0 #403E41
color1 #FF6188
color2 #A9DC76
color3 #FFD866
color4 #FC9867
color5 #AB9DF2
color6 #78DCE8
color7 #FCFCFA
color8 #727072
color9 #FF6188
color10 #A9DC76
color11 #FFD866
color12 #FC9867
color13 #AB9DF2
color14 #78DCE8
color15 #FCFCFA
# Preserve Alacritty-like minimal window behavior.
window_padding_width 0
copy_on_select no
# Keep the shell integration and graphics protocol available to Ranger.
shell_integration enabled
@@ -0,0 +1,84 @@
# Shared Noctalia defaults. Keep GUI experiments in state; promote durable choices here.
[shell]
setup_wizard_enabled = false
offline_mode = true
external_ip_enabled = false
telemetry_enabled = false
# Noctalia owns idle handling so ScreenSaver/Wayland idle inhibitors (such as
# browser video playback) prevent these actions. Power displays down after five
# minutes, lock after ten minutes, then suspend-then-hibernate after thirty.
[idle]
behavior_order = ["lock", "screen-off", "suspend-then-hibernate"]
pre_action_fade_seconds = 2.0
[idle.behavior.lock]
action = "lock"
enabled = true
timeout = 600
[idle.behavior.screen-off]
action = "screen_off"
enabled = true
timeout = 300
[idle.behavior.suspend-then-hibernate]
action = "command"
command = "loginctl lock-session && systemctl suspend-then-hibernate"
enabled = true
timeout = 1800
[theme]
mode = "dark"
source = "custom"
custom_palette = "Ember OLED"
pure_black_dark = true
# Noctalia merges files alphabetically. Keep reusable modules here; machine-specific
# layout and hardware settings live in 90-machine.toml, while 99-extend.toml is local.
[plugins]
enabled = ["alex/system-status", "alex/knowledge-lookup"]
[widget.cpu]
type = "alex/system-status:cpu"
[widget.ram]
type = "alex/system-status:memory"
[widget.network-rate]
type = "alex/system-status:network-rate"
[widget.idle-inhibitors]
type = "alex/system-status:inhibitors"
[widget.knowledge-lookup]
type = "alex/knowledge-lookup:bar"
[widget.temp]
type = "sysmon"
stat = "cpu_temp"
visualization = "none"
show_value = true
[widget.disk]
type = "sysmon"
stat = "disk_used_pct"
visualization = "none"
show_value = true
path = "/"
[desktop_widgets]
enabled = false
[wallpaper]
enabled = true
fill_color = "#000000"
[wallpaper.default]
path = "color:#000000"
[nightlight]
enabled = true
force = false
temperature_day = 6500
temperature_night = 2500
@@ -0,0 +1,48 @@
{
"dark": {
"mPrimary": "#ff8800",
"mOnPrimary": "#1a0d00",
"mSecondary": "#ffb05c",
"mOnSecondary": "#2b1600",
"mTertiary": "#ffcc99",
"mOnTertiary": "#2b1600",
"mError": "#ff6b4a",
"mOnError": "#2b0700",
"mSurface": "#050403",
"mOnSurface": "#fff1e6",
"mSurfaceVariant": "#1a110c",
"mOnSurfaceVariant": "#f4c7a1",
"mOutline": "#6e4a31",
"mShadow": "#000000",
"mHover": "#2a190f",
"mOnHover": "#fff1e6",
"terminal": {
"background": "#050403",
"foreground": "#fff1e6",
"cursor": "#fff1e6",
"cursorText": "#050403",
"selectionBg": "#3b220f",
"selectionFg": "#fff1e6",
"normal": {
"black": "#050403",
"red": "#ff6b4a",
"green": "#c98a45",
"yellow": "#ffb05c",
"blue": "#c97c45",
"magenta": "#d98a5c",
"cyan": "#e8a76a",
"white": "#fff1e6"
},
"bright": {
"black": "#6e4a31",
"red": "#ff8f72",
"green": "#e8ad68",
"yellow": "#ffca80",
"blue": "#e8a06a",
"magenta": "#f0ad82",
"cyan": "#ffd0a0",
"white": "#ffffff"
}
}
}
}
@@ -0,0 +1,12 @@
[Unit]
Description=Track session ScreenSaver idle inhibitors for Noctalia
After=graphical-session.target
PartOf=graphical-session.target
[Service]
ExecStart=%h/.local/bin/idle-inhibitor-monitor
Restart=on-failure
RestartSec=2
[Install]
WantedBy=default.target
@@ -0,0 +1,13 @@
[Unit]
Description=Pi Status Bridge home agent
After=graphical-session.target
[Service]
Type=simple
Environment=PATH=/home/alex/.npm-global/bin:/usr/local/bin:/usr/bin:/bin
ExecStart=/usr/bin/env pi-status-bridge --session-root %h/.local/state/pi-status-bridge/sessions
Restart=on-failure
RestartSec=3
[Install]
WantedBy=default.target
@@ -0,0 +1,10 @@
[Unit]
Description=OpenSSH key agent
[Service]
Type=simple
Environment=SSH_AUTH_SOCK=%t/ssh-agent.socket
ExecStart=/usr/bin/ssh-agent -D -a $SSH_AUTH_SOCK
[Install]
WantedBy=default.target
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 yazi-rs
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 Catppuccin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,36 @@
<div align="center">
<img src="https://github.com/sxyazi/yazi/blob/main/assets/logo.png?raw=true" alt="Yazi logo" width="20%">
</div>
<h3 align="center">
Catppuccin Macchiato Flavor for <a href="https://github.com/sxyazi/yazi">Yazi</a>
</h3>
## 👀 Preview
<img src="preview.png" width="600" />
## 🎨 Installation
```sh
ya pkg add yazi-rs/flavors:catppuccin-macchiato
```
## ⚙️ Usage
Set the content of your `theme.toml` to enable it as your _dark_ flavor:
```toml
[flavor]
dark = "catppuccin-macchiato"
```
Make sure your `theme.toml` doesn't contain anything other than `[flavor]`, unless you want to override certain styles of this flavor.
See the [Yazi flavor documentation](https://yazi-rs.github.io/docs/flavors/overview) for more details.
## 📜 License
The flavor is MIT-licensed, and the included tmTheme is also MIT-licensed.
Check the [LICENSE](LICENSE) and [LICENSE-tmtheme](LICENSE-tmtheme) file for more details.
@@ -0,0 +1,215 @@
# vim:fileencoding=utf-8:foldmethod=marker
# : Manager {{{
[mgr]
cwd = { fg = "#8bd5ca" }
# Find
find_keyword = { fg = "#eed49f", bold = true, italic = true, underline = true }
find_position = { fg = "#f5bde6", bg = "reset", bold = true, italic = true }
# Marker
marker_copied = { fg = "#a6da95", bg = "#a6da95" }
marker_cut = { fg = "#ed8796", bg = "#ed8796" }
marker_marked = { fg = "#8bd5ca", bg = "#8bd5ca" }
marker_selected = { fg = "#eed49f", bg = "#eed49f" }
# Count
count_copied = { fg = "#24273a", bg = "#a6da95" }
count_cut = { fg = "#24273a", bg = "#ed8796" }
count_selected = { fg = "#24273a", bg = "#eed49f" }
# Border
border_symbol = "│"
border_style = { fg = "#8087a2" }
# : }}}
# : Tabs {{{
[tabs]
active = { fg = "#24273a", bg = "#8aadf4", bold = true }
inactive = { fg = "#8aadf4", bg = "#363a4f" }
# : }}}
# : Mode {{{
[mode]
normal_main = { fg = "#24273a", bg = "#8aadf4", bold = true }
normal_alt = { fg = "#8aadf4", bg = "#363a4f" }
# Select mode
select_main = { fg = "#24273a", bg = "#8bd5ca", bold = true }
select_alt = { fg = "#8bd5ca", bg = "#363a4f" }
# Unset mode
unset_main = { fg = "#24273a", bg = "#f0c6c6", bold = true }
unset_alt = { fg = "#f0c6c6", bg = "#363a4f" }
# : }}}
# : Status bar {{{
[status]
# Permissions
perm_sep = { fg = "#8087a2" }
perm_type = { fg = "#8aadf4" }
perm_read = { fg = "#eed49f" }
perm_write = { fg = "#ed8796" }
perm_exec = { fg = "#a6da95" }
# Progress
progress_label = { fg = "#ffffff", bold = true }
progress_normal = { fg = "#a6da95", bg = "#494d64" }
progress_error = { fg = "#eed49f", bg = "#ed8796" }
# : }}}
# : Pick {{{
[pick]
border = { fg = "#8aadf4" }
active = { fg = "#f5bde6", bold = true }
inactive = {}
# : }}}
# : Input {{{
[input]
border = { fg = "#8aadf4" }
title = {}
value = {}
selected = { reversed = true }
# : }}}
# : Completion {{{
[cmp]
border = { fg = "#8aadf4" }
# : }}}
# : Tasks {{{
[tasks]
border = { fg = "#8aadf4" }
title = {}
hovered = { fg = "#f5bde6", bold = true }
# : }}}
# : Which {{{
[which]
border = { fg = "#8aadf4" }
cand = { fg = "#8bd5ca" }
rest = { fg = "#939ab7" }
desc = { fg = "#f5bde6" }
separator = "  "
separator_style = { fg = "#5b6078" }
# : }}}
# : Help {{{
[help]
on = { fg = "#8bd5ca" }
run = { fg = "#f5bde6" }
hovered = { reversed = true, bold = true }
footer = { fg = "#363a4f", bg = "#cad3f5" }
# : }}}
# : Spotter {{{
[spot]
border = { fg = "#8aadf4" }
title = { fg = "#8aadf4" }
tbl_col = { fg = "#8bd5ca" }
tbl_cell = { fg = "#f5bde6", bg = "#494d64" }
# : }}}
# : Notification {{{
[notify]
title_info = { fg = "#a6da95" }
title_warn = { fg = "#eed49f" }
title_error = { fg = "#ed8796" }
# : }}}
# : File-specific styles {{{
[filetype]
rules = [
# Image
{ mime = "**/image/*", fg = "#8bd5ca" },
# Media
{ mime = "**/{audio,video}/*", fg = "#eed49f" },
# Archive
{ mime = "**/application/{zip,rar,7z*,tar,gzip,xz,zstd,bzip*,lzma,compress,archive,cpio,arj,xar,ms-cab*}", fg = "#f5bde6" },
# Document
{ mime = "**/application/{pdf,doc,rtf}", fg = "#a6da95" },
# Virtual file system
{ mime = "vfs/{absent,stale}", fg = "#939ab7" },
# Fallback
{ url = "*", fg = "#cad3f5" },
{ url = "*/", fg = "#8aadf4" },
]
# : }}}
[icon]
dirs = [
{ name = ".config", text = "", fg = "#f5bde6" },
{ name = ".git", text = "", fg = "#8bd5ca" },
{ name = ".github", text = "", fg = "#8aadf4" },
{ name = ".npm", text = "", fg = "#8aadf4" },
{ name = "Desktop", text = "", fg = "#8bd5ca" },
{ name = "Development", text = "", fg = "#8bd5ca" },
{ name = "Documents", text = "", fg = "#8bd5ca" },
{ name = "Downloads", text = "", fg = "#8bd5ca" },
{ name = "Library", text = "", fg = "#8bd5ca" },
{ name = "Movies", text = "", fg = "#8bd5ca" },
{ name = "Music", text = "", fg = "#8bd5ca" },
{ name = "Pictures", text = "", fg = "#8bd5ca" },
{ name = "Public", text = "", fg = "#8bd5ca" },
{ name = "Videos", text = "", fg = "#8bd5ca" },
]
conds = [
# Special files
{ if = "orphan", text = "", fg = "#cad3f5" },
{ if = "link", text = "", fg = "#8087a2" },
{ if = "block", text = "", fg = "#f0c6c6" },
{ if = "char", text = "", fg = "#f0c6c6" },
{ if = "fifo", text = "", fg = "#f0c6c6" },
{ if = "sock", text = "", fg = "#f0c6c6" },
{ if = "sticky", text = "", fg = "#f0c6c6" },
{ if = "dummy", text = "", fg = "#ed8796" },
# Fallback
{ if = "dir & hovered", text = "", fg = "#8aadf4" },
{ if = "dir", text = "", fg = "#8aadf4" },
{ if = "exec", text = "", fg = "#a6da95" },
{ if = "!dir", text = "", fg = "#cad3f5" },
]
Binary file not shown.

After

Width:  |  Height:  |  Size: 674 KiB

@@ -0,0 +1,20 @@
require("git"):setup {
order = 1500,
}
require("githead"):setup {
branch_prefix = "on ",
branch_symbol = "",
branch_borders = "()",
branch_color = "#ffb05c",
remote_branch_color = "#f4c7a1",
tag_color = "#ffcc99",
commit_color = "#f4c7a1",
behind_remote_color = "#ff6b4a",
ahead_remote_color = "#ff8800",
stashes_color = "#c98a45",
state_color = "#ff6b4a",
staged_color = "#ff8800",
unstaged_color = "#ffb05c",
untracked_color = "#f4c7a1",
}
@@ -0,0 +1,16 @@
#:schema https://yazi-rs.github.io/schemas/keymap.json
[[mgr.prepend_keymap]]
on = "l"
run = "plugin smart-enter"
desc = "Enter the child directory, or open the file"
[[mgr.prepend_keymap]]
on = ["g", "d"]
run = "cd ~/downloads"
desc = "Cd to Downloads"
[[mgr.prepend_keymap]]
on = ["g", "D"]
run = "cd ~/cloud/documents"
desc = "Cd to Documents"
@@ -0,0 +1,19 @@
[[plugin.deps]]
use = "yazi-rs/plugins:smart-enter"
rev = "0be29a9"
hash = "187cc58ba7ac3befd49c342129e6f1b6"
[[plugin.deps]]
use = "yazi-rs/plugins:git"
rev = "3f2b882"
hash = "336548d9f67926e12d5584f60b790f1"
[[plugin.deps]]
use = "llanosrocas/githead"
rev = "317d09f"
hash = "41dc37a724b2afd21da4f1ea6c1182d8"
[[flavor.deps]]
use = "yazi-rs/flavors:catppuccin-macchiato"
rev = "be0b21d"
hash = "5b1bf96d1cba26376087c37de993c517"
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 yazi-rs
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,84 @@
# git.yazi
Show the status of Git file changes as linemode in the file list.
https://github.com/user-attachments/assets/34976be9-a871-4ffe-9d5a-c4cdd0bf4576
## Installation
```sh
ya pkg add yazi-rs/plugins:git
```
## Setup
Add the following to your `~/.config/yazi/init.lua`:
```lua
require("git"):setup {
-- Order of status signs showing in the linemode
order = 1500,
}
```
And register it as fetchers in your `~/.config/yazi/yazi.toml`:
```toml
[[plugin.prepend_fetchers]]
url = "*"
run = "git"
group = "git"
[[plugin.prepend_fetchers]]
url = "*/"
run = "git"
group = "git"
```
## Advanced
You can customize the [Style](https://yazi-rs.github.io/docs/configuration/theme#types.style) of the status sign with:
- `[git].unknown` - status cannot/not yet determined
- `[git].modified` - modified file
- `[git].added` - added file
- `[git].untracked` - untracked file
- `[git].ignored` - ignored file
- `[git].deleted` - deleted file
- `[git].updated` - updated file
- `[git].clean` - clean file
For example:
```toml
# theme.toml / flavor.toml
[git]
modified = { fg = "blue" }
deleted = { fg = "red", bold = true }
```
You can also customize the text of the status sign with:
- `[git].unknown_sign` - status cannot/not yet determined
- `[git].modified_sign` - modified file
- `[git].added_sign` - added file
- `[git].untracked_sign` - untracked file
- `[git].ignored_sign` - ignored file
- `[git].deleted_sign` - deleted file
- `[git].updated_sign` - updated file
- `[git].clean_sign` - clean file
For example:
```toml
# theme.toml / flavor.toml
[git]
unknown_sign = " "
modified_sign = "M"
deleted_sign = "D"
clean_sign = "✔"
```
## License
This plugin is MIT-licensed. For more information check the [LICENSE](LICENSE) file.
@@ -0,0 +1,279 @@
--- @since 26.5.6
local WINDOWS = ya.target_family() == "windows"
-- The code of supported git status,
-- also used to determine which status to show for directories when they contain different statuses
-- see `bubble_up`
---@enum CODES
local CODES = {
unknown = 100, -- status cannot/not yet determined
excluded = 99, -- ignored directory
ignored = 6, -- ignored file
untracked = 5,
modified = 4,
added = 3,
deleted = 2,
updated = 1,
clean = 0,
}
local PATTERNS = {
{ "!$", CODES.ignored },
{ "?$", CODES.untracked },
{ "[MT]", CODES.modified },
{ "[AC]", CODES.added },
{ "D", CODES.deleted },
{ "U", CODES.updated },
{ "[AD][AD]", CODES.updated },
}
local function theme()
local t = th.git or {}
return {
[CODES.unknown] = t.unknown or ui.Style(),
[CODES.ignored] = t.ignored or ui.Style():fg("darkgray"),
[CODES.untracked] = t.untracked or ui.Style():fg("magenta"),
[CODES.modified] = t.modified or ui.Style():fg("yellow"),
[CODES.added] = t.added or ui.Style():fg("green"),
[CODES.deleted] = t.deleted or ui.Style():fg("red"),
[CODES.updated] = t.updated or ui.Style():fg("yellow"),
[CODES.clean] = t.clean or ui.Style(),
}, {
[CODES.unknown] = t.unknown_sign or "",
[CODES.ignored] = t.ignored_sign or "",
[CODES.untracked] = t.untracked_sign or "? ",
[CODES.modified] = t.modified_sign or "",
[CODES.added] = t.added_sign or "",
[CODES.deleted] = t.deleted_sign or "",
[CODES.updated] = t.updated_sign or "",
[CODES.clean] = t.clean_sign or "",
}
end
---@param line string
---@return CODES, string
local function match(line)
local signs = line:sub(1, 2)
for _, p in ipairs(PATTERNS) do
local path, pattern, code = nil, p[1], p[2]
if signs:find(pattern) then
path = line:sub(4, 4) == '"' and line:sub(5, -2) or line:sub(4)
path = WINDOWS and path:gsub("/", "\\") or path
end
if not path then
elseif path:find("[/\\]$") then
-- Mark the ignored directory as `excluded`, so we can process it further within `propagate_down`
return code == CODES.ignored and CODES.excluded or code, path:sub(1, -2)
else
return code, path
end
---@diagnostic disable-next-line: missing-return
end
end
---@param cwd Url
---@return string?
local function root(cwd)
local is_worktree = function(url)
local file, head = io.open(tostring(url)), nil
if file then
head = file:read(8)
file:close()
end
return head == "gitdir: "
end
repeat
local next = cwd:join(".git")
local cha = fs.cha(next)
if cha and (cha.is_dir or is_worktree(next)) then
return tostring(cwd)
end
cwd = cwd.parent
until not cwd
end
---@param changed Changes
---@return Changes
local function bubble_up(changed)
local new, empty = {}, Url("")
for path, code in pairs(changed) do
if code ~= CODES.ignored then
local url = Url(path).parent
while url and url ~= empty do
local s = tostring(url)
new[s] = (new[s] or CODES.clean) > code and new[s] or code
url = url.parent
end
end
end
return new
end
---@param excluded string[]
---@param cwd Url
---@param repo Url
---@return Changes
local function propagate_down(excluded, cwd, repo)
local new, rel = {}, cwd:strip_prefix(repo)
for _, path in ipairs(excluded) do
if rel:starts_with(path) then
-- If `cwd` is a subdirectory of an excluded directory, also mark it as `excluded`
new[tostring(cwd)] = CODES.excluded
elseif cwd == repo:join(path).parent then
-- If `path` is a direct subdirectory of `cwd`, mark it as `ignored`
new[path] = CODES.ignored
else
-- Skipping, we only care about `cwd` itself and its direct subdirectories for maximum performance
end
end
return new
end
---@param cwd string
---@param repo string
---@param changed Changes
local add = ya.sync(function(st, cwd, repo, changed)
---@cast st State
st.dirs[cwd] = repo
st.repos[repo] = st.repos[repo] or {}
for path, code in pairs(changed) do
if code == CODES.clean then
st.repos[repo][path] = nil
elseif code == CODES.excluded then
-- Mark the directory with a special value `excluded` so that it can be distinguished during UI rendering
st.dirs[path] = CODES.excluded
else
st.repos[repo][path] = code
end
end
ui.render()
end)
---@param cwd string
local remove = ya.sync(function(st, cwd)
---@cast st State
local repo = st.dirs[cwd]
if not repo then
return
end
ui.render()
st.dirs[cwd] = nil
if not st.repos[repo] then
return
end
for _, r in pairs(st.dirs) do
if r == repo then
return
end
end
st.repos[repo] = nil
end)
---@param st State
---@param opts Options
local function setup(st, opts)
st.dirs = {}
st.repos = {}
opts = opts or {}
opts.order = opts.order or 1500
local styles, signs = theme()
ps.sub("theme", function()
styles, signs = theme()
end)
Linemode:children_add(function(self)
if not self._file.in_current then
return ""
end
local url = self._file.url
local repo = st.dirs[tostring(url.base or url.parent)]
local code = CODES.unknown
if repo then
code = repo == CODES.excluded and CODES.ignored or st.repos[repo][tostring(url):sub(#repo + 2)] or CODES.clean
end
if signs[code] == "" then
return ""
elseif self._file.is_hovered then
return ui.Line { " ", signs[code] }
else
return ui.Line { " ", ui.Span(signs[code]):style(styles[code]) }
end
end, opts.order)
end
---@type UnstableFetcher
local function fetch(_, job)
local cwd = job.files[1].url.base or job.files[1].url.parent
local repo = root(cwd)
if not repo then
remove(tostring(cwd))
return true
end
local paths = {}
for _, file in ipairs(job.files) do
paths[#paths + 1] = tostring(file.url)
end
-- stylua: ignore
local output, err = Command("git")
:cwd(tostring(cwd))
:arg({ "--no-optional-locks", "-c", "core.quotePath=", "status", "--porcelain", "-unormal", "--no-renames", "--ignored=matching" })
:arg(paths)
:output()
if not output then
return true, Err("Cannot spawn `git` command, error: %s", err)
end
local changed, excluded = {}, {}
for line in output.stdout:gmatch("[^\r\n]+") do
local code, path = match(line)
if code == CODES.excluded then
excluded[#excluded + 1] = path
else
changed[path] = code
end
end
if job.files[1].cha.is_dir then
ya.dict_merge(changed, bubble_up(changed))
end
ya.dict_merge(changed, propagate_down(excluded, cwd, Url(repo)))
-- Reset the status of any files that don't appear in the output of `git status` to `clean`,
-- so that cleaning up outdated statuses from `st.repos`
for _, path in ipairs(paths) do
local s = path:sub(#repo + 2)
changed[s] = changed[s] or CODES.clean
end
add(tostring(cwd), repo, changed)
return false
end
-- TODO: remove
local function fetch_compact(self, job)
if ya.throttle then
fetch(self, job)
return ya.co(function()
for _, file in ipairs(job.files) do
coroutine.yield(file, { retry = true })
end
end)
else
return fetch(self, job)
end
end
return { setup = setup, fetch = fetch_compact }
@@ -0,0 +1,12 @@
---@class State
---@field dirs table<string, string|CODES> Mapping between a directory and its corresponding repository
---@field repos table<string, Changes> Mapping between a repository and the status of each of its files
---@class Options
---@field order number The order in which the status is displayed
---@field renamed boolean Whether to include renamed files in the status (or treat them as modified)
-- TODO: move this to `types.yazi` once it's get stable
---@alias UnstableFetcher fun(self: unknown, job: { files: File[] }): boolean, Error?
---@alias Changes table<string, CODES>
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 llanosrocas
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,422 @@
# githead.yazi
Git status header for yazi inspired by [powerlevel10k](https://github.com/romkatv/powerlevel10k?tab=readme-ov-file#what-do-different-symbols-in-git-status-mean).
![preview-default](https://github.com/llanosrocas/githead.yazi/blob/main/.github/images/preview-default.png)
![preview-fullscreen](https://github.com/llanosrocas/githead.yazi/blob/main/.github/images/preview-fullscreen.png)
All supported features are listed [here](#features). More presets are available [here](#presets).
## Requirements
- yazi version >= [26.1.22](https://github.com/sxyazi/yazi/releases/tag/v26.1.22).
- Font with symbol support. For example [Nerd Fonts](https://www.nerdfonts.com/).
## Compatibility
To keep the plugin up to date, there are two branches: `main` and `nightly`.
The `main` branch follows major yazi releases, while `nightly` is linked to specific yazi commits or changes.
This setup allows shipping stable versions on time, while giving early access to "cutting-edge" changes. See matrix below.
<details close>
<summary>Compatibility matrix</summary>
| githead | yazi |
| :-------------------------------------------------------------------------------------------------------: | ----------------------------------------------------------------------------------------- |
| [v2.0.2](https://github.com/llanosrocas/githead.yazi/releases/tag/v2.0.2) | [v26.1.22](https://github.com/sxyazi/yazi/releases/tag/v26.1.22) |
| [v2.0.2](https://github.com/llanosrocas/githead.yazi/releases/tag/v2.0.2) | [v25.12.29](https://github.com/sxyazi/yazi/releases/tag/v25.12.29) |
| [v2.0.2-nightly.292d37c](https://github.com/llanosrocas/githead.yazi/releases/tag/v2.0.2-nightly.292d37c) | [2f66561](https://github.com/sxyazi/yazi/commit/2f66561a8251f8788b2b0fd366af90555ecafc86) |
| [v2.0.2-nightly.292d37c](https://github.com/llanosrocas/githead.yazi/releases/tag/v2.0.2-nightly.292d37c) | [6cfa92f](https://github.com/sxyazi/yazi/commit/6cfa92f11205d212155579b5b76d4cbabe723829) |
| [v2.0.2-nightly.292d37c](https://github.com/llanosrocas/githead.yazi/releases/tag/v2.0.2-nightly.292d37c) | [292d37c](https://github.com/sxyazi/yazi/commit/292d37ccde5fb09dc7318247caf552213d16ec36) |
| [v2.0.1](https://github.com/llanosrocas/githead.yazi/releases/tag/v2.0.1) | [917e1f5](https://github.com/sxyazi/yazi/commit/917e1f54a10445f2e25147c4b81a3c77d8233632) |
| [v2.0.0](https://github.com/llanosrocas/githead.yazi/releases/tag/v2.0.0) | [693dff2](https://github.com/sxyazi/yazi/commit/693dff25e3165e357cc9d0b94ca3f2b176741a36) |
| [v1.7.0](https://github.com/llanosrocas/githead.yazi/releases/tag/v1.7.0) | [693dff2](https://github.com/sxyazi/yazi/tree/693dff25e3165e357cc9d0b94ca3f2b176741a36) |
| [v1.6.0](https://github.com/llanosrocas/githead.yazi/releases/tag/v1.6.0) | [v25.5.31](https://github.com/sxyazi/yazi/releases/tag/v25.5.31) |
</details>
## Installation
1. Using yazi package manager
```sh
ya pkg add llanosrocas/githead
```
_Or manually copy `main.lua` to the `~/.config/yazi/plugins/githead.yazi/main.lua`_
2. Add this line to your `~/.config/yazi/init.lua`:
```lua
require("githead"):setup()
```
## Configuration
This is default config, if you want to see copy-paste presets go to [this section](#presets).
```lua
require("githead"):setup({
order = {
"__spacer__",
"branch",
"remote",
"__spacer__",
"tag",
"__spacer__",
"commit",
"__spacer__",
"behind_ahead_remote",
"__spacer__",
"stashes",
"__spacer__",
"state",
"__spacer__",
"staged",
"__spacer__",
"unstaged",
"__spacer__",
"untracked",
},
show_numbers = true, -- shows staged, unstaged, untracked, stashes count
show_branch = true,
branch_prefix = "",
branch_color = "blue",
branch_symbol = "",
branch_borders = "",
show_remote_branch = true, -- only shown if different from local branch
always_show_remote_branch = false, -- always show remote branch even if it the same as local branch
always_show_remote_repo = false, -- Adds `origin/` if `always_show_remote_branch` is enabled
remote_branch_prefix = ":",
remote_branch_color = "bright magenta",
show_tag = true, -- only shown if branch is not available
always_show_tag = false,
tag_color = "magenta",
tag_symbol = "#",
show_commit = true, -- only shown if branch AND tag are not available
always_show_commit = false,
commit_color = "bright magenta",
commit_symbol = "@",
show_behind_ahead_remote = true,
behind_remote_color = "bright magenta",
behind_remote_symbol = "⇣",
ahead_remote_color = "bright magenta",
ahead_remote_symbol = "⇡",
show_stashes = true,
stashes_color = "bright magenta",
stashes_symbol = "$",
show_state = true,
show_state_prefix = true,
state_color = "red",
state_symbol = "~",
show_staged = true,
staged_color = "bright yellow",
staged_symbol = "+",
show_unstaged = true,
unstaged_color = "bright yellow",
unstaged_symbol = "!",
show_untracked = true,
untracked_color = "blue",
untracked_symbol = "?",
})
```
```
/cwd on ( feature):origin/main #v1.0.0 #1234567 ⇣2⇡3 $1 rebase 1/2 ~2 +4 !1 ?5
| | | | ││ | | | | | | | | | | | |
| | | | ││ | | | | | | | | | | | └─── untracked_symbol
| | | | ││ | | | | | | | | | | └────── unstaged_symbol
| | | | ││ | | | | | | | | | └───────── staged_symbol
| | | | ││ | | | | | | | | └──────────── state_symbol
| | | | ││ | | | | | | | └─────────────────────── state_prefix
| | | | ││ | | | | | | └────────────────────────── stashes_symbol
| | | | ││ | | | | | └───────────────────────────── ahead_symbol
| | | | ││ | | | | └─────────────────────────────── behind_symbol
| | | | ││ | | | └──────────────────────────────────────── commit_symbol
| | | | ││ | | └──────────────────────────────────────────────── tag_symbol
| | | | ││ | └───────────────────────────────────────────────────── remote_branch
| | | | ││ └─────────────────────────────────────────────────────────── remote_repo
| | | | |└───────────────────────────────────────────────────────────── remote_branch_prefix
| | | | └────────────────────────────────────────────────────────────── branch_borders
| | | └──────────────────────────────────────────────────────────────────── branch
| | └─────────────────────────────────────────────────────────────────────── branch_symbol
| └─────────────────────────────────────────────────────────────────────────── branch_prefix
└──────────────────────────────────────────────────────────────────────────────── cwd
```
## Presets
- v1 default (My config)
![preview-fav](https://github.com/llanosrocas/githead.yazi/blob/main/.github/images/preview-fav.png)
<details>
<summary>Config</summary>
```lua
require("githead"):setup({
branch_prefix = "on",
branch_symbol = " ",
branch_borders = "()",
})
```
</details>
- [spaceship-prompt](https://github.com/spaceship-prompt/spaceship-prompt)
![preview-spaceship](https://github.com/llanosrocas/githead.yazi/blob/main/.github/images/preview-spaceship.png)
<details>
<summary>Config</summary>
```lua
require("githead"):setup({
order = {
"__spacer__",
"branch",
"remote",
"__spacer__",
"tag",
"__spacer__",
"commit",
"__spacer__",
"behind_ahead_remote",
"stashes",
"state",
"staged",
"unstaged",
"untracked",
},
show_numbers = false,
branch_symbol = " ",
branch_prefix = "on",
})
```
</details>
- [robertgzr/porcelain](https://github.com/robertgzr/porcelain)
![preview-porcelain](https://github.com/llanosrocas/githead.yazi/blob/main/.github/images/preview-porcelain.png)
<details>
<summary>Config</summary>
```lua
require("githead"):setup({
order = {
"__spacer__",
"branch",
"commit",
"__spacer__",
"behind_ahead_remote",
"__spacer__",
"untracked",
"state",
"unstaged",
"__spacer__",
"staged",
},
show_numbers = false,
show_branch = true,
branch_prefix = "",
branch_color = "#288BD2",
always_show_commit = true,
commit_color = "#859A00",
show_behind_ahead_remote = true,
behind_remote_symbol = "↓",
ahead_remote_symbol = "↑",
behind_remote_color = "#DC322E",
ahead_remote_color = "#4DB6AC",
show_state = true,
show_state_prefix = false,
state_symbol = "!!",
state_color = "#B58901",
staged_symbol = "✔",
staged_color = "green",
unstaged_symbol = "Δ",
unstaged_color = "#288BD2",
untracked_symbol = "?",
untracked_color = "#415F65",
})
```
</details>
- Minimal (No color)
![preview-minimal-no-color](https://github.com/llanosrocas/githead.yazi/blob/main/.github/images/preview-minimal-no-color.png)
<details>
<summary>Config</summary>
```lua
require("githead"):setup({
branch_color = "",
remote_branch_prefix = "@",
remote_branch_color = "",
tag_color = "",
commit_color = "",
ahead_remote_symbol = "+",
ahead_remote_color = "",
behind_remote_symbol = "-",
behind_remote_color = "",
stashes_symbol = "*",
stashes_color = "",
show_state_prefix = false,
state_symbol = "~",
state_color = "",
staged_symbol = "S",
staged_color = "",
unstaged_symbol = "U",
unstaged_color = "",
untracked_symbol = "N",
untracked_color = "",
})
```
</details>
- Vibrant (Full)
![preview-vibrant-full](https://github.com/llanosrocas/githead.yazi/blob/main/.github/images/preview-vibrant-full.png)
<details>
<summary>Config</summary>
```lua
require("githead"):setup({
order = {
"__spacer__",
"stashes",
"__spacer__",
"state",
"__spacer__",
"staged",
"__spacer__",
"unstaged",
"__spacer__",
"untracked",
"__spacer__",
"branch",
"remote_branch",
"__spacer__",
"tag",
"__spacer__",
"commit",
"__spacer__",
"behind_ahead_remote",
"__spacer__",
},
branch_borders = "{}",
branch_prefix = "|",
branch_color = "#7aa2f7",
remote_branch_color = "#9ece6a",
always_show_remote_branch = true,
always_show_remote_repo = true,
tag_symbol = "󰓼",
always_show_tag = true,
tag_color = "#bb9af7",
commit_symbol = "",
always_show_commit = true,
commit_color = "#e0af68",
staged_color = "#73daca",
staged_symbol = "●",
unstaged_color = "#e0af68",
unstaged_symbol = "✗",
untracked_color = "#f7768e",
untracked_symbol = "?",
state_color = "#f5c359",
state_symbol = "󱐋",
stashes_color = "#565f89",
stashes_symbol = "⚑",
})
```
</details>
## Features
- [x] Current branch
- [x] Remote and remote branch
- [x] Latest tag
- [x] Latest commit
- [x] Behind/Ahead of the remote
- [x] Stashes
- [x] States
- [x] merge
- [x] cherry
- [x] rebase (+ done counter)
- [x] revert
- [x] bisect
- [x] Staged
- [x] Unstaged
- [x] Untracked
### Under the hood
The goal is to use minimum amount of shell commands.
- Branch, stashes, staged files, unstaged files, untracked files.
```shell
git status --ignore-submodules=dirty --branch --show-stash --ahead-behind
```
- Remote branch and repo:
```shell
git rev-parse --abbrev-ref --symbolic-full-name @{upstream}
```
- Latest tag and commit:
```shell
git log --format="commit %h%d" -n 1
```
## Credits
- [yazi source code](https://github.com/sxyazi/yazi)
- [powerlevel10k](https://github.com/romkatv/powerlevel10k)
- [twio142](https://github.com/twio142/githead.yazi)
@@ -0,0 +1,598 @@
---@diagnostic disable: undefined-global
local save = ya.sync(function(this, cwd, output)
if cwd:match("%.git[/\\]") or cwd:match("%.git$") then
this.output = nil
return
end
if cx.active.current.cwd == Url(cwd) then
this.output = output
ui.render()
end
end)
return {
setup = function(this, options)
options = options or {}
local config = {
order = options.order or {
"__spacer__",
"branch",
"remote_branch",
"__spacer__",
"tag",
"__spacer__",
"commit",
"__spacer__",
"behind_ahead_remote",
"__spacer__",
"stashes",
"__spacer__",
"state",
"__spacer__",
"staged",
"__spacer__",
"unstaged",
"__spacer__",
"untracked",
},
show_numbers = options.show_numbers == nil and true
or options.show_numbers,
show_branch = options.show_branch == nil and true or options.show_branch,
branch_prefix = options.branch_prefix or "",
branch_color = options.branch_color or "blue",
branch_symbol = options.branch_symbol or "",
branch_borders = options.branch_borders or "",
show_remote_branch = options.show_remote_branch == nil and true
or options.show_remote_branch,
always_show_remote_branch = options.always_show_remote_branch == nil
and false
or options.always_show_remote_branch,
always_show_remote_repo = options.always_show_remote_repo == nil
and false
or options.always_show_remote_repo,
remote_branch_prefix = options.remote_branch_prefix or ":",
remote_branch_color = options.remote_branch_color or "bright magenta",
show_tag = options.show_tag == nil and true or options.show_tag,
always_show_tag = options.always_show_tag == nil and false
or options.always_show_tag,
tag_color = options.tag_color or "magenta",
tag_symbol = options.tag_symbol == nil and "#" or options.tag_symbol,
show_commit = options.show_commit == nil and true or options.show_commit,
always_show_commit = options.always_show_commit == nil and false
or options.always_show_commit,
commit_color = options.commit_color or "bright magenta",
commit_symbol = options.commit_symbol == nil and "@"
or options.commit_symbol,
show_behind_ahead_remote = options.show_behind_ahead_remote == nil
and true
or options.show_behind_ahead_remote,
behind_remote_color = options.behind_remote_color or "bright magenta",
behind_remote_symbol = options.behind_remote_symbol or "⇣",
ahead_remote_color = options.ahead_remote_color or "bright magenta",
ahead_remote_symbol = options.ahead_remote_symbol or "⇡",
show_stashes = options.show_stashes == nil and true
or options.show_stashes,
stashes_color = options.stashes_color or "bright magenta",
stashes_symbol = options.stashes_symbol or "$",
show_state = options.show_state == nil and true or options.show_state,
show_state_prefix = options.show_state_prefix == nil and true
or options.show_state_prefix,
state_color = options.state_color or "red",
state_symbol = options.state_symbol or "~",
show_staged = options.show_staged == nil and true or options.show_staged,
staged_color = options.staged_color or "bright yellow",
staged_symbol = options.staged_symbol or "+",
show_unstaged = options.show_unstaged == nil and true
or options.show_unstaged,
unstaged_color = options.unstaged_color or "bright yellow",
unstaged_symbol = options.unstaged_symbol or "!",
show_untracked = options.show_untracked == nil and true
or options.show_untracked,
untracked_color = options.untracked_color or "bright blue",
untracked_symbol = options.untracked_symbol or "?",
}
--- @param data GitStatusData
function Header:render_branch(data)
local branch = data.branch
if not branch then
return nil
end
local left_border = config.branch_borders:sub(1, 1)
local right_border = config.branch_borders:sub(2, 2)
local branch_string = ""
if config.branch_symbol == "" then
branch_string = left_border .. branch .. right_border
else
branch_string = left_border
.. config.branch_symbol
.. branch
.. right_border
end
local branch_prefix = config.branch_prefix == "" and ""
or config.branch_prefix .. " "
return ui.Line({
ui.Span(branch_prefix),
ui.Span(branch_string):fg(config.branch_color),
})
end
--- @param data GitStatusData
function Header:render_remote_branch(data)
local branch = data.branch
local remote_branch = data.remote_branch
local remote_repo = data.remote_repo
if not remote_branch then
return nil
end
local show_remote = config.always_show_remote_branch
or branch ~= remote_branch
if not show_remote then
return nil
end
local remote_branch_label = config.always_show_remote_repo
and (remote_repo .. "/" .. remote_branch)
or remote_branch
return ui.Line({
ui.Span(config.remote_branch_prefix),
ui.Span(remote_branch_label):fg(config.remote_branch_color),
})
end
--- @param data GitStatusData
function Header:render_tag(data)
local branch = data.branch
local tag = data.tag
if not tag then
return nil
end
if not branch or config.always_show_tag then
return ui.Line({
ui.Span(config.tag_symbol),
ui.Span(tag):fg(config.tag_color),
})
end
end
--- @param data GitStatusData
function Header:render_commit(data)
local branch = data.branch
local tag = data.tag
local commit = data.commit
if not commit then
return nil
end
if (not branch and not tag) or config.always_show_commit then
return ui.Line({
ui.Span(config.commit_symbol),
ui.Span(commit):fg(config.commit_color),
})
end
end
--- @param data GitStatusData
function Header:render_behind_ahead_remote(data)
local behind = data.behind_remote
local ahead = data.ahead_remote
local behind_label = behind
and behind > 0
and ui.Span(
config.behind_remote_symbol .. (config.show_numbers and behind or "")
):fg(config.behind_remote_color)
local ahead_label = ahead
and ahead > 0
and ui.Span(
config.ahead_remote_symbol .. (config.show_numbers and ahead or "")
):fg(config.ahead_remote_color)
if ahead_label and behind_label then
return ui.Line({
behind_label,
ahead_label,
})
elseif ahead_label then
return ahead_label
elseif behind_label then
return behind_label
else
return nil
end
end
--- @param data GitStatusData
function Header:render_stashes(data)
local stashes_count = data.stashes
if not stashes_count then
return nil
end
local stashes_label = config.stashes_symbol
if config.show_numbers then
stashes_label = stashes_label .. stashes_count
end
return ui.Span(stashes_label):fg(config.stashes_color)
end
--- @param data GitStatusData
function Header:render_state(data)
local state_prefix = data.state_prefix
local unmerged_count = data.unmerged_count
local rebase_done = data.rebase_done
local rebase_total = data.rebase_total
if state_prefix then
local state_label = ""
if config.show_state_prefix then
state_label = state_prefix
if
state_prefix == "rebase-i"
and config.show_numbers
and rebase_done
and rebase_total
then
state_label = state_label
.. " "
.. rebase_done
.. "/"
.. rebase_total
end
end
local unmerged_label = ""
local symbol = ""
if unmerged_count and unmerged_count > 0 then
symbol = " " .. config.state_symbol
if config.show_numbers then
unmerged_label = tostring(unmerged_count)
end
end
return ui.Span(state_label .. symbol .. unmerged_label)
:fg(config.state_color)
end
return nil
end
--- @param data GitStatusData
function Header:render_staged(data)
local staged = data.staged
if staged then
local staged_label = config.staged_symbol
if config.show_numbers then
local filtered_staged = staged:gsub("^[%s]*%b()[%s]*", "")
local staged_count = 0
for line in filtered_staged:gmatch("[^\r\n]+") do
if line:match("%S") then
staged_count = staged_count + 1
end
end
staged_label = staged_label .. staged_count
end
return ui.Span(staged_label):fg(config.staged_color)
end
return nil
end
--- @param data GitStatusData
function Header:render_unstaged(data)
local unstaged = data.unstaged
if unstaged then
local unstaged_label = config.unstaged_symbol
if config.show_numbers then
local filtered_unstaged =
unstaged:gsub("^[%s]*%b()[\r\n]*", ""):gsub("^[%s]*%b()[\r\n]*", "")
local unstaged_count = 0
for line in filtered_unstaged:gmatch("[^\r\n]+") do
if line:match("%S") then
unstaged_count = unstaged_count + 1
end
end
unstaged_label = unstaged_label .. unstaged_count
end
return ui.Span(unstaged_label):fg(config.unstaged_color)
end
return nil
end
--- @param data GitStatusData
function Header:render_untracked(data)
local untracked = data.untracked
if untracked then
local untracked_label = config.untracked_symbol
if config.show_numbers then
local filtered_untracked = untracked:gsub("^[%s]*%b()[\r\n]*", "")
local untracked_count = 0
for line in filtered_untracked:gmatch("[^\r\n]+") do
if line:match("%S") then
untracked_count = untracked_count + 1
end
end
untracked_label = untracked_label .. untracked_count
end
return ui.Span(untracked_label):fg(config.untracked_color)
end
return nil
end
function Header:githead()
--- @type GitStatusData
local data = this.output
if not data then
return ui.Line({})
end
local head = {}
for _, key in ipairs(config.order) do
if key == "__spacer__" then
if head[#head] ~= " " then
table.insert(head, " ")
end
else
local fn = self["render_" .. key]
local is_shown = config["show_" .. key]
if fn and is_shown then
local value = fn(self, data)
if value then
table.insert(head, value)
end
end
end
end
return ui.Line(head)
end
Header:children_add(Header.githead, 2000, Header.LEFT)
local callback = function()
local cwd = cx.active.current.cwd
ya.emit("plugin", {
this._id,
ya.quote(tostring(cwd), true),
})
end
ps.sub("cd", callback)
ps.sub("rename", callback)
ps.sub("bulk", callback)
ps.sub("move", callback)
ps.sub("trash", callback)
ps.sub("delete", callback)
ps.sub("tab", callback)
end,
entry = function(_, job)
local args = job.args or job
---@class GitStatusData
---@field ahead_remote? integer
---@field behind_remote? integer
---@field branch? string
---@field commit? string
---@field rebase_done? integer
---@field remote_branch? string
---@field remote_repo? string
---@field staged? string
---@field stashes? integer
---@field state_prefix? "bisect"|"cherry"|"merge"|"rebase-i"|"revert"
---@field tag? string
---@field unmerged_count? integer
---@field unstaged? string
---@field untracked? string
local data = {}
--- @param status string
local get_behind_ahead_remote = function(status)
local diverged_ahead, diverged_behind =
status:match("have (%d+) and (%d+) different")
if diverged_ahead and diverged_behind then
data.behind_remote = tonumber(diverged_behind)
data.ahead_remote = tonumber(diverged_ahead)
else
local behind_remote = status:match("behind %S+ by (%d+) commits?")
local ahead_remote = status:match("ahead of %S+ by (%d+) commits?")
data.behind_remote = tonumber(behind_remote)
data.ahead_remote = tonumber(ahead_remote)
end
end
--- @param status string
local get_branch = function(status)
data.branch = status:match("On branch (%S+)")
end
--- @param status string
local get_stashes = function(status)
data.stashes = tonumber(status:match("Your stash currently has (%d+)"))
end
--- @param status string
local get_state = function(status)
local unmerged = status:match("Unmerged paths:%s*(.-)%s*\n\n")
if unmerged then
local filtered_unmerged =
unmerged:gsub("^[%s]*%b()[%s]*", ""):gsub("^[%s]*%b()[%s]*", "")
local count = 0
for line in filtered_unmerged:gmatch("[^\r\n]+") do
if line:match("%S") then
count = count + 1
end
end
data.unmerged_count = count
end
if status:find("git merge") then
data.state_prefix = "merge"
elseif status:find("git cherry%-pick") then
data.state_prefix = "cherry"
elseif status:find("git rebase") then
data.state_prefix = "rebase-i"
local commands_done = status:match("%((%d+) commands? done%)")
if commands_done then
data.rebase_done = commands_done
if not data.unmerged_count then
data.rebase_total = commands_done
else
data.rebase_total = data.unmerged_count
end
end
elseif status:find("git revert") then
data.state_prefix = "revert"
elseif status:find("git bisect") then
data.state_prefix = "bisect"
end
end
--- @param status string
local get_staged = function(status)
data.staged = status:match("Changes to be committed:%s*(.-)%s*\n\n")
end
--- @param status string
local get_unstaged = function(status)
data.unstaged =
status:match("Changes not staged for commit:%s*(.-)%s*\n\n")
end
--- @param status string
local get_untracked = function(status)
data.untracked = status:match("Untracked files:%s*(.-)%s*\n\n")
end
local get_status = function()
local cmd = Command("git")
:arg({
"status",
"--ignore-submodules=dirty",
"--branch",
"--show-stash",
"--ahead-behind",
})
:cwd(args[1])
:env("LANGUAGE", "en_US.UTF-8")
:stdout(Command.PIPED)
local cmd_output = cmd:output()
if cmd_output then
local status = cmd_output.stdout
get_branch(status)
get_behind_ahead_remote(status)
get_stashes(status)
get_state(status)
get_staged(status)
get_unstaged(status)
get_untracked(status)
end
end
local get_remote_branch = function()
local cmd = Command("git")
:arg({
"rev-parse",
"--abbrev-ref",
"--symbolic-full-name",
"@{upstream}",
})
:cwd(args[1])
:env("LANGUAGE", "en_US.UTF-8")
:stdout(Command.PIPED)
local cmd_output = cmd:output()
if cmd_output then
local remote_branch = cmd_output.stdout
data.remote_branch =
remote_branch:gsub("[\r\n]", ""):match("^[^/]+/(.+)")
data.remote_repo = remote_branch:gsub("[\r\n]", ""):match("^([^/]+)/")
end
end
--- @param log string
local get_tag = function(log)
data.tag = log:match("tag: ([^, )]+)")
end
--- @param log string
local get_commit = function(log)
data.commit = log:match("^commit%s+([a-f0-9]+)")
end
local get_git_log = function()
local cmd = Command("git")
:arg({ "log", "--format=commit %h%d", "-n", "1" })
:cwd(args[1])
:env("LANGUAGE", "en_US.UTF-8")
:stdout(Command.PIPED)
local cmd_output = cmd:output()
if cmd_output then
local log = cmd_output.stdout
get_tag(log)
get_commit(log)
end
end
get_status()
get_remote_branch()
get_git_log()
save(args[1], data)
end,
}
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 yazi-rs
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,40 @@
# smart-enter.yazi
[`Open`][open] files or [`enter`][enter] directories all in one key!
## Installation
```sh
ya pkg add yazi-rs/plugins:smart-enter
```
## Usage
Bind your <kbd>l</kbd> key to the plugin, in your `~/.config/yazi/keymap.toml`:
```toml
[[mgr.prepend_keymap]]
on = "l"
run = "plugin smart-enter"
desc = "Enter the child directory, or open the file"
```
## Advanced
By default, `--hovered` is passed to the [`open`][open] action, make the behavior consistent with [`enter`][enter] avoiding accidental triggers,
which means both will only target the currently hovered file.
If you still want `open` to target multiple selected files, add this to your `~/.config/yazi/init.lua`:
```lua
require("smart-enter"):setup {
open_multi = true,
}
```
## License
This plugin is MIT-licensed. For more information check the [LICENSE](LICENSE) file.
[open]: https://yazi-rs.github.io/docs/configuration/keymap/#mgr.open
[enter]: https://yazi-rs.github.io/docs/configuration/keymap/#mgr.enter
@@ -0,0 +1,11 @@
--- @since 25.5.31
--- @sync entry
local function setup(self, opts) self.open_multi = opts.open_multi end
local function entry(self)
local h = cx.active.current.hovered
ya.emit(h and h.cha.is_dir and "enter" or "open", { hovered = not self.open_multi })
end
return { entry = entry, setup = setup }
@@ -0,0 +1,125 @@
# Ember OLED: aligned with Noctalia's "Ember OLED" palette and tmux status line.
[flavor]
dark = "catppuccin-macchiato"
[mgr]
cwd = { fg = "#ffb05c", bold = true }
find_keyword = { fg = "#ffcc99", bold = true, underline = true }
find_position = { fg = "#ff8800", bold = true }
marker_copied = { fg = "#c98a45", bg = "#c98a45" }
marker_cut = { fg = "#ff6b4a", bg = "#ff6b4a" }
marker_marked = { fg = "#ff8800", bg = "#ff8800" }
marker_selected = { fg = "#ffcc99", bg = "#ffcc99" }
count_copied = { fg = "#050403", bg = "#c98a45" }
count_cut = { fg = "#050403", bg = "#ff6b4a" }
count_selected = { fg = "#050403", bg = "#ffcc99" }
border_symbol = "│"
border_style = { fg = "#6e4a31" }
[tabs]
active = { fg = "#fff1e6", bg = "#1a110c", bold = true }
inactive = { fg = "#f4c7a1", bg = "#050403" }
[mode]
normal_main = { fg = "#050403", bg = "#ff8800", bold = true }
normal_alt = { fg = "#ffb05c", bg = "#1a110c" }
select_main = { fg = "#050403", bg = "#ffb05c", bold = true }
select_alt = { fg = "#ffb05c", bg = "#1a110c" }
unset_main = { fg = "#050403", bg = "#ff6b4a", bold = true }
unset_alt = { fg = "#ff6b4a", bg = "#1a110c" }
[status]
perm_sep = { fg = "#6e4a31" }
perm_type = { fg = "#ffb05c" }
perm_read = { fg = "#fff1e6" }
perm_write = { fg = "#ff6b4a" }
perm_exec = { fg = "#c98a45" }
progress_label = { fg = "#fff1e6", bold = true }
progress_normal = { fg = "#ff8800", bg = "#1a110c" }
progress_error = { fg = "#fff1e6", bg = "#ff6b4a" }
[pick]
border = { fg = "#ff8800" }
active = { fg = "#ffb05c", bold = true }
[input]
border = { fg = "#ff8800" }
selected = { fg = "#050403", bg = "#ffb05c" }
[cmp]
border = { fg = "#ff8800" }
[tasks]
border = { fg = "#ff8800" }
title = { fg = "#fff1e6" }
hovered = { fg = "#ffb05c", bold = true }
[which]
border = { fg = "#ff8800" }
cand = { fg = "#ffb05c" }
rest = { fg = "#f4c7a1" }
desc = { fg = "#fff1e6" }
separator = "  "
separator_style = { fg = "#6e4a31" }
[help]
on = { fg = "#ffb05c" }
run = { fg = "#fff1e6" }
hovered = { fg = "#050403", bg = "#ffb05c", bold = true }
footer = { fg = "#fff1e6", bg = "#1a110c" }
[spot]
border = { fg = "#ff8800" }
title = { fg = "#ffb05c" }
tbl_col = { fg = "#ffb05c" }
tbl_cell = { fg = "#fff1e6", bg = "#1a110c" }
[notify]
title_info = { fg = "#c98a45" }
title_warn = { fg = "#ffb05c" }
title_error = { fg = "#ff6b4a" }
[filetype]
rules = [
{ mime = "**/image/*", fg = "#ffcc99" },
{ mime = "**/{audio,video}/*", fg = "#c98a45" },
{ mime = "**/application/{zip,rar,7z*,tar,gzip,xz,zstd,bzip*,lzma,compress,archive,cpio,arj,xar,ms-cab*}", fg = "#ffb05c" },
{ mime = "**/application/{pdf,doc,rtf}", fg = "#f4c7a1" },
{ mime = "vfs/{absent,stale}", fg = "#6e4a31" },
{ url = "*", fg = "#fff1e6" },
{ url = "*/", fg = "#ffb05c" },
]
[icon]
dirs = [
{ name = ".config", text = "", fg = "#ffb05c" },
{ name = ".git", text = "", fg = "#ff8800" },
{ name = ".github", text = "", fg = "#ffb05c" },
{ name = "Desktop", text = "", fg = "#ffb05c" },
{ name = "Documents", text = "", fg = "#ffb05c" },
{ name = "Downloads", text = "", fg = "#ffb05c" },
{ name = "Pictures", text = "", fg = "#ffcc99" },
{ name = "Videos", text = "", fg = "#c98a45" },
]
conds = [
{ if = "orphan", text = "", fg = "#ff6b4a" },
{ if = "link", text = "", fg = "#6e4a31" },
{ if = "dir & hovered", text = "", fg = "#ffcc99" },
{ if = "dir", text = "", fg = "#ffb05c" },
{ if = "exec", text = "", fg = "#c98a45" },
{ if = "!dir", text = "", fg = "#fff1e6" },
]
[git]
modified = { fg = "#ffb05c" }
added = { fg = "#c98a45" }
untracked = { fg = "#f4c7a1" }
deleted = { fg = "#ff6b4a", bold = true }
updated = { fg = "#ff8800" }
ignored = { fg = "#6e4a31" }
modified_sign = "M "
added_sign = "A "
untracked_sign = "? "
deleted_sign = "D "
updated_sign = "U "
ignored_sign = "I "
@@ -0,0 +1,9 @@
[[plugin.prepend_fetchers]]
url = "*"
run = "git"
group = "git"
[[plugin.prepend_fetchers]]
url = "*/"
run = "git"
group = "git"
@@ -0,0 +1,23 @@
-- Desktop-only display and startup profile.
return {
monitors = {
{
output = "HDMI-A-1",
mode = "3840x2160@120",
position = "0x0",
scale = 1,
},
},
startup = {
"noctalia",
"hypridle",
-- Electron's tray icon registers only when the SNI watcher already exists.
"sh -c 'sleep 2; exec mullvad-vpn'",
"obsidian",
"thunderbird",
"nextcloud",
"spotify",
"brave",
"alacritty -e tmux",
},
}
@@ -0,0 +1,91 @@
# Desktop-specific shell layout and hardware telemetry.
# Pi Status Bridge is installed locally from ~/projects/pi-status-bridge.
[plugins]
enabled = ["alex/system-status", "alex/pi-status-bridge", "alex/knowledge-lookup"]
[widget.pi-status]
type = "alex/pi-status-bridge:bridge"
[bar.main]
position = "bottom"
layer = "top"
scale = 0.95
capsule_radius = 5
center = ["group:g3", "notifications"]
end = ["group:g2", "group:g6", "group:g1", "session"]
font_weight = 400
margin_ends = 0
padding = 0
panel_overlap = 3
radius = 0
shadow = false
# Keep the desktop status bar visible and reserve its screen edge.
auto_hide = false
smart_auto_hide = false
reserve_space = true
start = ["tray", "group:g4", "privacy"]
thickness = 39
widget_spacing = 10
[[bar.main.capsule_group]]
enabled = true
fill = "surface_variant"
id = "g1"
members = ["idle-inhibitors", "caffeine", "date", "clock"]
opacity = 1.0
padding = 6.0
[[bar.main.capsule_group]]
enabled = true
fill = "surface_variant"
id = "g2"
members = ["network", "bluetooth"]
opacity = 1.0
padding = 6.0
[[bar.main.capsule_group]]
enabled = true
fill = "surface_variant"
id = "g3"
members = ["launcher", "media", "control-center", "clipboard"]
opacity = 1.0
padding = 6.0
radius = 5.0
[[bar.main.capsule_group]]
enabled = true
fill = "surface_variant"
id = "g4"
members = ["pi-status", "cpu", "temp", "ram", "gpu", "gpu_temp", "disk"]
opacity = 1.0
padding = 6.0
[[bar.main.capsule_group]]
enabled = true
fill = "surface_variant"
id = "g6"
members = ["input_volume", "volume"]
opacity = 1.0
padding = 6.0
radius = 5.0
[widget.gpu]
type = "sysmon"
stat = "gpu_usage"
visualization = "graph"
show_value = true
[widget.gpu_temp]
type = "sysmon"
stat = "gpu_temp"
visualization = "none"
show_value = true
[control_center]
sidebar = "full"
sidebar_section = "full"
width = 880
[system.monitor]
# GPU polling is disabled by Noctalia by default; enable it for the RTX 3090 widgets.
gpu_poll_seconds = 2.0
@@ -0,0 +1,30 @@
-- Laptop profile: leave monitor selection to Hyprland's hotplug support.
-- Noctalia replaces the i3-era Redshift, Polybar, Blueman Applet, and Picom stack.
return {
input = {
touchpad = {
-- Standard laptop behavior: one/two/three-finger taps map to
-- left/right/middle click, and a second tap held down starts a drag.
tap_to_click = true,
tap_button_map = "lrm",
tap_and_drag = true,
drag_lock = false,
clickfinger_behavior = true,
disable_while_typing = true,
-- Preserve natural two-finger scrolling at the default sensitivity.
natural_scroll = true,
scroll_factor = 1.0,
},
},
monitors = {},
startup = {
"noctalia",
-- Start after Noctalia has claimed the StatusNotifier watcher.
"sh -c 'sleep 2; exec mullvad-vpn'",
"obsidian",
"thunderbird",
"nextcloud",
"spotify",
},
}
@@ -0,0 +1,88 @@
# Laptop-specific Noctalia layout. Shared privacy/theme defaults live in 10-base.toml.
[bar.main]
position = "top"
layer = "top"
scale = 0.95
capsule_radius = 5
center = ["group:g3", "notifications"]
end = ["group:g2", "group:g6", "group:g5", "group:g1", "session"]
font_weight = 400
margin_ends = 0
padding = 0
panel_overlap = 3
radius = 0
shadow = false
auto_hide = false
smart_auto_hide = false
reserve_space = true
start = ["tray", "group:g4"]
# Accommodate the battery-rate widget's wattage + ETA lines without clipping.
thickness = 39
widget_spacing = 10
[[bar.main.capsule_group]]
enabled = true
fill = "surface_variant"
id = "g1"
members = ["idle-inhibitors", "caffeine", "date", "clock"]
opacity = 1.0
padding = 6.0
[[bar.main.capsule_group]]
enabled = true
fill = "surface_variant"
id = "g2"
members = ["network", "bluetooth"]
opacity = 1.0
padding = 6.0
[[bar.main.capsule_group]]
enabled = true
fill = "surface_variant"
id = "g3"
members = ["launcher", "media", "control-center", "clipboard"]
opacity = 1.0
padding = 6.0
radius = 5.0
[[bar.main.capsule_group]]
enabled = true
fill = "surface_variant"
id = "g4"
members = ["cpu", "temp", "ram", "gpu-mode", "disk"]
opacity = 1.0
padding = 6.0
[[bar.main.capsule_group]]
enabled = true
fill = "surface_variant"
id = "g5"
members = ["battery-rate", "battery", "power_profile"]
opacity = 1.0
padding = 6.0
radius = 5.0
[[bar.main.capsule_group]]
enabled = true
fill = "surface_variant"
id = "g6"
members = ["input_volume", "volume"]
opacity = 1.0
padding = 6.0
radius = 5.0
[widget.battery-rate]
type = "alex/system-status:battery-rate"
[widget.gpu-mode]
type = "alex/system-status:gpu-mode"
# Only suspend-then-hibernate after 30 minutes when UPower confirms battery power.
# The command fails closed on AC or when UPower is unavailable.
[idle.behavior.suspend-then-hibernate]
command = "busctl get-property org.freedesktop.UPower /org/freedesktop/UPower org.freedesktop.UPower OnBattery 2>/dev/null | grep -qx 'b true' && loginctl lock-session && systemctl suspend-then-hibernate"
[control_center]
sidebar = "full"
sidebar_section = "full"
width = 720
@@ -0,0 +1,59 @@
# Local, untracked Noctalia overrides.
# This file is loaded after the shared base (10-base.toml) and machine profile
# (90-machine.toml). Keep host-only experiments and machine-local adjustments here.
#
# Example:
# [bar.main]
# thickness = 32
[plugins]
enabled = [
"alex/system-status",
"alex/pi-status-bridge",
"alex/knowledge-lookup",
]
[widget.pi-status]
type = "alex/pi-status-bridge:bridge"
[widget.knowledge-lookup]
type = "alex/knowledge-lookup:bar"
[bar.main]
center = ["pi-status", "knowledge-lookup", "group:g3", "notifications"]
end = ["group:g2", "group:g6", "group:g5", "group:g1", "session"]
# Migrated from Noctalia's GUI-managed state so these host preferences survive
# resetting the state override file.
[location]
address = "Leipzig, Germany"
[lockscreen_widgets]
enabled = false
schema_version = 2
widget_order = ["lockscreen-login-box@eDP-1"]
[lockscreen_widgets.grid]
cell_size = 16
major_interval = 4
visible = true
[lockscreen_widgets.widget."lockscreen-login-box@eDP-1"]
box_height = 70.0
box_width = 400.0
cx = 960.0
cy = 1081.0
output = "eDP-1"
rotation = 0.0
type = "login_box"
[lockscreen_widgets.widget."lockscreen-login-box@eDP-1".settings]
background_color = "surface_variant"
background_opacity = 0.88
background_radius = 12.0
center_password_text = false
input_opacity = 1.0
input_radius = 6.0
show_caps_lock = true
show_keyboard_layout = true
show_login_button = true